mysql 5.7 在中等 sql 中比 mysql 5.6 慢很多

Posted

技术标签:

【中文标题】mysql 5.7 在中等 sql 中比 mysql 5.6 慢很多【英文标题】:mysql 5.7 is much slower than mysql 5.6 in medium sql 【发布时间】:2016-11-24 18:50:44 【问题描述】:

我们正在升级到 mysql 5.7,但发现它比它的 5.6 对应部分慢得多。虽然两者具有几乎相同的配置,但 5.6 版本在毫秒内执行大部分 sql,而另一个需要大约 1 秒或更长时间用于中等复杂 sql,例如下面的。

-- Getting most recent users that are email-verified and not banned 

SELECT
    `u`.*
FROM
    `user` AS `u`
INNER JOIN `user` user_table_alias ON user_table_alias.`id` = `u`.`id`
LEFT JOIN `user_suspend` user_suspend_table_alias ON user_suspend_table_alias.`userId` = `user_table_alias`.`id`
WHERE
    (
        `user_suspend_table_alias`.`id` IS NULL
    )
AND 
    `user_table_alias`.`emailVerify` = 1

ORDER BY
    `u`.`joinStamp` DESC
LIMIT 1, 18

两个表都非常简单且索引良好:

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(128) NOT NULL DEFAULT '',
  `username` varchar(32) NOT NULL DEFAULT '',
  `password` varchar(64) NOT NULL DEFAULT '',
  `joinStamp` int(11) NOT NULL DEFAULT '0',
  `activityStamp` int(11) NOT NULL DEFAULT '0',
  `accountType` varchar(32) NOT NULL DEFAULT '',
  `emailVerify` tinyint(2) NOT NULL DEFAULT '0',
  `joinIp` int(11) unsigned NOT NULL,
  `locationId` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`),
  UNIQUE KEY `username` (`username`),
  KEY `accountType` (`accountType`),
  KEY `joinStamp` (`joinStamp`),
  KEY `activityStamp` (`activityStamp`)
) ENGINE=MyISAM AUTO_INCREMENT=89747 DEFAULT CHARSET=utf8 COMMENT='utf8_general_ci';

-- ----------------------------
-- Table structure for user_suspend
-- ----------------------------
DROP TABLE IF EXISTS `user_suspend`;
CREATE TABLE `user_suspend` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` int(11) DEFAULT NULL,
  `timestamp` int(11) DEFAULT NULL,
  `message` text NOT NULL,
  `expire` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `userId` (`userId`)
) ENGINE=MyISAM AUTO_INCREMENT=513 DEFAULT CHARSET=utf8;

这些表分别有大约 100K 和 1K 行。我注意到我想“修复”两个有趣的行为:

    删除 ORDER BY 将执行时间从 ~1.2 秒缩短到 0.0015 秒!! mysql 5.7没有缓存sql

注意:我们确实有缓存查询:

显示类似“Qcache%”的状态

Qcache_free_blocks  19408
Qcache_free_memory  61782816
Qcache_hits 31437169
Qcache_inserts  2406719
Qcache_lowmem_prunes    133483
Qcache_not_cached   43555
Qcache_queries_in_cache 41691
Qcache_total_blocks 103951

我在谷歌上搜索并发现了许多在 5.7 上报告的问题,但不明白为什么这个 sql 出现了这种奇怪的行为(还有很多其他 sql 在 5.7 上运行得慢得多)。

这是 Neville K 建议的解释:

id  select_type     table               partitions  type        possible_keys   key         key_len     ref rows filtered Extra
1   SIMPLE      user_table_alias        NULL        ALL         PRIMARY     NULL        NULL        NULL 104801 10.00 Using where; Usingtemporary; Usingfilesort
1   SIMPLE      u               NULL        eq_ref      PRIMARY     PRIMARY     4       knn.base_user_table_alias.id 1 100.00 NULL
1   SIMPLE      user_suspend_table_alias    NULL        ref         userId userId           5       knn.base_user_table_alias.id 1 10.00 Using where;

【问题讨论】:

所以.. 如果你想要速度,为什么要使用 MyISAM? 你能发表解释吗? 使用“profile”分析SQL中的时间成本;可以显示每个阶段的时间成本; @Mjh:我认为 MyISAM 是为了速度?用于事务的 InnoDB ?没有? 您拥有的数据量很少,因此如果您使用 innodb 并将魔术变量 innodb_buffer_pool_size 增加到 2GB,您会发现查询速度非常快。 【参考方案1】:

好的,感谢 NevilleK 的解释。

我想出了如何只修复这个 SQL:

 user_table_alias.emailVerify = 1 

u.emailVerify = 1

不知道为什么,但是在MySQL5.6中,都是毫秒级的。

我想我将不得不审查所有 SQL(来自其他开发人员),这要归功于 MySQL 的向后改进

【讨论】:

【参考方案2】:

作为一种解决方法,您可以在第一次选择后尝试使用关键字 STRAIGHT_JOIN。这个关键字强制mysql从左到右加入(MYSQL - What does STRAIGHT_JOIN do in this code?)。

【讨论】:

【参考方案3】:

自联接看起来是多余的。

我认为你可以重写查询如下:

SELECT
    `u`.*
FROM
    `user` AS `u`
LEFT JOIN `user_suspend` user_suspend_table_alias ON user_suspend_table_alias.`userId` = `u`.`id`
WHERE    `user_suspend_table_alias`.`id` IS NULL
AND      `u`.`emailVerify` = 1
ORDER BY
    `u`.`joinStamp` DESC
LIMIT 1, 18

我假设“emailVerify”是一个只有少数值(0 和 1)的列,因此不应被索引。我还假设“joinStamp”是某种时间戳(尽管数据类型是整数)。如果这是真的,您可以创建一个索引来进一步加快速度。

create index id_joinstamp on user (id, joinstamp)

【讨论】:

您的查询看起来好多了,但是,我们的“加入用户表”是一段可重用的代码。这意味着许多其他表,例如“product”、“post”、“transaction”......等也加入了“user-user_suspend”。代码来自其他团队,但我不会抱怨太多。我会尝试采纳你的建议【参考方案4】:

INNER JOIN user user_table_alias ON user_table_alias.id = u.id 看起来没用。它只与自身连接,并且该技术不会在查询的其余部分中使用。

emailVerify 上没有索引。这由 EXPLAIN 的第一行表示。 ('using where'表示不使用索引)

这个查询不能很好地适应表的大小,因为在界定“最近的用户”是什么之前必须查看完整的表。所以可能 myisam 使用的一些内部缓冲区现在溢出了。 这就是“使用临时”的意思。使用 filesort 意味着 order by 太大,它使用临时文件,这对性能不利。

【讨论】:

实际上,我确实更改了user_table_alias.emailVerify = 1 => u.emailVerify = 1,它又快了。不知道为什么,但是在 mysql5.6 中,两者都非常快

以上是关于mysql 5.7 在中等 sql 中比 mysql 5.6 慢很多的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 5.7 密码策略

mysql 5.5升级到5.7版本操作流程

MySQL 5.7 的SSL加密方法

摘录-解压版mysql配置(版本5.7)

centos 7 安装 mysql 5.7

MySQL 5.7 my.cnf配置文件说明