为啥相同的查询在从 MySQL 服务器上的解释和执行如此不同?

Posted

技术标签:

【中文标题】为啥相同的查询在从 MySQL 服务器上的解释和执行如此不同?【英文标题】:Why does the same query explain & perform so differently on a slave MySQL server than a master?为什么相同的查询在从 MySQL 服务器上的解释和执行如此不同? 【发布时间】:2016-02-12 12:02:55 【问题描述】:

我有一个主 mysql 服务器和一个从服务器。数据在它们之间复制。

当我在主服务器上运行这个查询时,它需要几个小时;在奴隶上需要几秒钟。 EXPLAIN 计划支持这一点——从服务器检查的行数远少于主服务器。

但是,由于这两个数据库中的结构和数据完全相同(或者至少应该是),并且它们都运行相同版本的 MySQL(5.5.31 Enterprise),所以我不明白什么是造成这种情况。

这与this question(和其他人)的症状相似,但我认为这不是相同的根本原因,因为我的两台服务器通过 MySQL 复制同步,并且结构和数据内容是(或应该是)相同,并且两台服务器上的操作系统和硬件资源完全相同——它们是 VMWare,一个是另一个的映像。

我已经验证了每个表中的行数在两台服务器上完全相同,并且它们的配置相同(除了从站具有指向主站的指令)。没有通过数据本身来查看是否有任何差异,我不确定我还能检查什么,如果有任何建议,我将不胜感激。

查询是

 SELECT COUNT(DISTINCT(cds.company_id))
 FROM   jobsmanager.companies c
 ,      jobsmanager.company_jobsmanager_settings cjs
 ,      jobsmanager.company_details_snapshot cds
 ,      vacancies v
 WHERE  c.company_id = cjs.company_id
 AND    cds.company_id = c.company_id
 AND    cds.company_id = v.jobsmanager_company_id
 AND    cjs.is_post_a_job = 'Y'
 AND    cjs.can_access_jobsmanager = 'Y'
 AND    cjs.account_status != 'suspended'
 AND    v.last_live BETWEEN cds.record_date - INTERVAL 365 DAY AND cds.record_date
 AND    cds.record_date BETWEEN '2016-01-30' AND '2016-02-05';

高手是这样解释的,驱动表300万行,没有key使用,需要一个多小时才能返回结果:

+----+-------------+-------+--------+-------------------------+----------------+---------+---------------------------------+---------+--------------------------+
| id | select_type | table | type   | possible_keys           | key            | key_len | ref                             | rows    | Extra                    |
+----+-------------+-------+--------+-------------------------+----------------+---------+---------------------------------+---------+--------------------------+
|  1 | SIMPLE      | v     | ALL    | job_owner,last_live_idx | NULL           | NULL    | NULL                            | 3465433 |                          |
|  1 | SIMPLE      | c     | eq_ref | PRIMARY                 | PRIMARY        | 4       | s1jobs.v.jobsmanager_company_id |       1 | Using where; Using index |
|  1 | SIMPLE      | cds   | ref    | PRIMARY,company_id_idx  | company_id_idx | 4       | jobsmanager.c.company_id        |     538 | Using where              |
|  1 | SIMPLE      | cjs   | eq_ref | PRIMARY,qidx,qidx2      | PRIMARY        | 4       | jobsmanager.c.company_id        |       1 | Using where              |
+----+-------------+-------+--------+-------------------------+----------------+---------+---------------------------------+---------+--------------------------+

slave 使用不同的驱动表,使用索引,预测大约 310,000 行检查,并在几秒钟内返回结果:

+----+-------------+-------+--------+-------------------------+-----------+---------+----------------------------+--------+--------------------------+
| id | select_type | table | type   | possible_keys           | key       | key_len | ref                        | rows   | Extra                    |
+----+-------------+-------+--------+-------------------------+-----------+---------+----------------------------+--------+--------------------------+
|  1 | SIMPLE      | cds   | range  | PRIMARY,company_id_idx  | PRIMARY   | 3       | NULL                       | 310381 | Using where; Using index |
|  1 | SIMPLE      | c     | eq_ref | PRIMARY                 | PRIMARY   | 4       | jobsmanager.cds.company_id |      1 | Using index              |
|  1 | SIMPLE      | cjs   | eq_ref | PRIMARY,qidx,qidx2      | PRIMARY   | 4       | jobsmanager.c.company_id   |      1 | Using where              |
|  1 | SIMPLE      | v     | ref    | job_owner,last_live_idx | job_owner | 2       | jobsmanager.cds.company_id |     32 | Using where              |
+----+-------------+-------+--------+-------------------------+-----------+---------+----------------------------+--------+--------------------------+

我已经在两台服务器上运行了 ANALYZE TABLE、OPTIMIZE TABLE 和 REPAIR TABLE ... QUICK 以尝试使它们保持一致,但没有运气。

作为临时解决方案,我可以在从属服务器上运行查询,因为它们在 cron 脚本中,即使它们在从属服务器上花费很长时间,它们也不会像运行时那样增加主服务器的负载在主人身上。但是,我将不胜感激有关为什么这些不同的任何其他信息,或者我可以检查/修改的其他信息,这将解释两者之间如此巨大的差异。我唯一能找到的是奴隶有更多的 free 内存,因为它很少使用;仅此一项就可以解释吗?如果不是还有什么?

$ ssh s1-mysql-01 free # master
             total       used       free     shared    buffers     cached
Mem:      99018464   98204624     813840          0     160752   55060632
-/+ buffers/cache:   42983240   56035224
Swap:      4095992    4095992          0
$ ssh s1-mysql-02 free # slave
             total       used       free     shared    buffers     cached
Mem:      99018464   80866420   18152044          0     224772   72575168
-/+ buffers/cache:    8066480   90951984
Swap:      4095992     206056    3889936
$

非常感谢。

【问题讨论】:

这两个解释之间唯一真正大的区别是,在 master 上,空缺表上没有使用索引。您可以尝试在 master 上的 select 中放置一个索引提示(强制索引)以强制使用 job_owner 索引。您还可以尝试在 master 上对上述查询中涉及的所有表运行分析表,以确保更新表和索引统计信息。 好的,谢谢影子会做的。 您最近是否强制它在一台服务器上刷新索引?如果统计数据已过时,则可能会错误地忽略索引。我建议尝试在 master 上做一个优化表(dev.mysql.com/doc/refman/5.1/en/optimize-table.html),看看是否可以改善。 嗨,谢谢,我已经运行优化和分析,没有太大区别。我现在修改它以强制索引 (job_owner) 正如 Shadow 所建议的那样,这使 master 的 EXPLAIN 行数减少到 310k 行,现在它会在几秒钟内返回。感谢您的帮助。 @Shadow - 什么是job_owner?这似乎无关紧要。 @JeremyJones - 请提供SHOW CREATE TABLE 【参考方案1】:

这两个解释之间唯一真正大的区别是,在 master 上,空缺表上没有使用索引。

您可以尝试将index hint(强制索引)放入 master 上的 select 以强制使用 job_owner 索引。

您也可以尝试在主节点上对上述查询中涉及的所有表运行analyze table,以确保更新表和索引统计信息。

【讨论】:

在查询中添加 FORCE INDEX job_owner 成功将 master 上的 EXPLAIN 计划更改为 310k 行,与从属类似。

以上是关于为啥相同的查询在从 MySQL 服务器上的解释和执行如此不同?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不应该在生产服务器上的 mysql 查询中使用 Select *?

MySQL 专家:为啥 2 个查询给出不同的“解释”索引使用结果?

为啥我的 apolloFetch 调用在从 promise.all 中调用时返回一个空查询?

为啥这个查询需要这么长时间?

为啥相同的 MySql 查询在代码和工作台中表现不同?

本地/实时站点上的相同查询,性能大不相同