MySQL Explain 的行数与慢查询日志的行数不同

cod*_*eak 6 mysql performance explain slow-log

我在慢查询日志中有这个条目:

# User@Host: user[host] @  [ip]
# Thread_id: 1514428  Schema: db  Last_errno: 0  Killed: 0
# Query_time: 2.795454  Lock_time: 0.000116  Rows_sent: 15  Rows_examined: 65207  Rows_affected: 0  Rows_read: 65207
# Bytsent: 26618
SET timestamp=1407511874;

select off.*,translated_title,translated_description 
from ephpb2b_products off  USE INDEX(id_viewed)  
  INNER JOIN ephpb2b_members mem 
    ON  off.uid = mem.id 
  Left Join ephpb2b_product_language_new pol 
    ON  off.id = pol.offer_id                                         
    and pol.language='en'
where off.approved=1 
order by off.viewed  
LIMIT 15; 
Run Code Online (Sandbox Code Playgroud)

当我解释这个查询时,它绝对没问题。

mysql> explain select off.*,translated_title,translated_description from ephpb2b_products off  USE INDEX(id_viewed)  INNER JOIN ephpb2b_members mem ON off.uid = mem.id Left Join ephpb2b_product_language_new pol ON off.id = pol.offer_id and pol.language='en' where off.approved=1 order by off.viewed  LIMIT 15;

+----+-------------+-------+--------+-------------------------+-------------+---------+---------------------------+------+-------------+
| id | select_type | table | type   | possible_keys           | key         | key_len | ref                       | rows | Extra       |
+----+-------------+-------+--------+-------------------------+-------------+---------+---------------------------+------+-------------+
|  1 | SIMPLE      | off   | index  | NULL                    | id_viewed   | 4       | NULL                      |    3 | Using where |
|  1 | SIMPLE      | mem   | eq_ref | PRIMARY                 | PRIMARY     | 4       | db.off.uid |    1 | Using index |
|  1 | SIMPLE      | pol   | ref    | offer_id,id_language | offer_id | 5       | db.off.id  |    4 |             |
+----+-------------+-------+--------+-------------------------+-------------+---------+---------------------------+------+-------------+
3 rows in set (0.17 sec)
Run Code Online (Sandbox Code Playgroud)

如何优化此查询?为什么解释显示 3 行,慢查询日志说它检查了 65207 行。

jyn*_*nus 9

为了回答这个问题,您必须了解解释上的行列是什么意思,以及基于统计和执行后统计的计算之间的区别。

当您运行解释时,行列将告诉您,对于每个表访问,将使用预期过滤器检查多少行。有两种计算方法:索引潜水(通常应该为您提供准确的结果)或使用每个引擎独立存储的近似统计数据 -每个表最多 5.6。虽然第一种方法在可以使用时是首选(单个索引列上的简单过滤器),但在许多情况下,只能使用近似值 - 否则,查询优化器将花费与查询执行本身一样多的时间。

在任何情况下,这些行都是每个表访问要读取(不返回)的计算行。即使它是精确的(并且很多时候有几个数量级的差异,但对于优化器来说仍然足够好),它也不能预测整个连接访问的实际行数。例如,如果您连接表A(精确读取X行)和表B(精确读取Y行),在 order 中A -> B读取的实际行数将为:X + # of rows returned by A (<=X) multiplied by Y,因为标准 mysql 仅支持嵌套循环连接。

慢日志,如处理程序统计信息或其他分析机制,告诉您处理和发送的实际行数,因为这些统计信息是在执行后收集的,因此是精确的

关于您的特定情况,EXPLAIN应该归咎于它,因为它表明第一次访问只会扫描 3 行,而实际上它可能正在执行完整索引扫描(因为它仅使用键进行排序),然后会成倍增加对于执行的每个连接。不信解释。您可以使用:

FLUSH STATUS;
-- Execute your query here
SHOW STATUS like 'Hand%';
Run Code Online (Sandbox Code Playgroud)

检查行操作的实际数量和种类(PK 访问、引用、索引扫描、表扫描)。我会用它来单独测试每个表的访问。

为了获得更具体的帮助,我们需要每个表的表结构和每个过滤条件的近似选择性。

  • +1 表示“不要相信解释”。InnoDB 统计数据总是猜测。即使将 innodb_stats_on_metadata 设置为 0 可以稳定解释计划,它也会使计划更频繁地使用陈旧的统计信息。解释基于统计和执行后统计的计算非常棒。 (2认同)
  • 很好的答案!+1 用于解释指数潜水与统计数据。 (2认同)

cod*_*eak 0

感谢大家的见解。我通过将此查询拆分为两个不同的查询来解决此问题。首先,我查询 id,然后将这些 id 传递到其他表以获取信息。

select id from ephpb2b_products off INNER JOIN ephpb2b_members mem 
    ON  off.uid = mem.id 
where off.approved=1 
order by off.viewed  
LIMIT 15;
Run Code Online (Sandbox Code Playgroud)

然后:

select * from ephpb2b_product_language_new where offer_id IN ({ids from lasts query})
Run Code Online (Sandbox Code Playgroud)

这表现得更好并且不会表现得很奇怪。