mysql - 为什么在对 SQL 查询 + 优化查询使用 LIMIT 时扫描类型从 ALL 更改为 RANGE

标签 mysql sql query-optimization

我有这个问题

SELECT l.licitatii_id, 
       l.nume, 
       l.data_publicarii, 
       l.data_limita 
FROM   licitatii_ue l 
       INNER JOIN domenii_licitatii dl 
         ON l.licitatii_id = dl.licitatii_id 
            AND dl.tip_licitatie = '2' 
       INNER JOIN domenii d 
         ON dl.domenii_id = d.domenii_id 
            AND d.status = 1 
            AND d.tip_domeniu = '1' 
WHERE  l.status = 1 
       AND Unix_timestamp(TIMESTAMPADD(DAY, 1, CAST(From_unixtime(l.data_limita) 
                                               AS DATE))) 
           < '1300683793' 
GROUP  BY l.licitatii_id 
ORDER  BY data_publicarii DESC 

解释输出:

+-----+--------------+--------+---------+-------------------------------------+----------+----------+---------------------------+-------+-----------+----------------------------------------------+
| id  | select_type  | table  | type    | possible_keys                       | key      | key_len  | ref                       | rows  | filtered  | Extra                                        |
| 1   | SIMPLE       | d      | ALL     | PRIMARY,key_status_tip_domeniu      | NULL     | NULL     | NULL                      | 120   | 85.83     | Using where; Using temporary; Using filesort |
| 1   | SIMPLE       | dl     | ref     | PRIMARY,tip_licitatie,licitatii_id  | PRIMARY  | 4        | web61db1.d.domenii_id     | 6180  | 100.00    | Using where; Using index                     |
| 1   | SIMPLE       | l      | eq_ref  | PRIMARY                             | PRIMARY  | 4        | web61db1.dl.licitatii_id  | 1     | 100.00    | Using where                                  |
+-----+--------------+--------+---------+-------------------------------------+----------+----------+---------------------------+-------+-----------+----------------------------------------------+

如你所见,d 表的 type=ALL

现在,如果我将 LIMIT 100 添加到查询中

计划更改范围:

+-----+--------------+--------+---------+-------------------------------------+-------------------------+----------+---------------------------+-------+-----------+----------------------------------------------+
| id  | select_type  | table  | type    | possible_keys                       | key                     | key_len  | ref                       | rows  | filtered  | Extra                                        |
| 1   | SIMPLE       | d      | range   | PRIMARY,key_status_tip_domeniu      | key_status_tip_domeniu  | 9        | NULL                      | 103   | 100.00    | Using where; Using temporary; Using filesort |
| 1   | SIMPLE       | dl     | ref     | PRIMARY,tip_licitatie,licitatii_id  | PRIMARY                 | 4        | web61db1.d.domenii_id     | 6180  | 100.00    | Using where; Using index                     |
| 1   | SIMPLE       | l      | eq_ref  | PRIMARY                             | PRIMARY                 | 4        | web61db1.dl.licitatii_id  | 1     | 100.00    | Using where                                  |
+-----+--------------+--------+---------+-------------------------------------+-------------------------+----------+---------------------------+-------+-----------+----------------------------------------------+

为什么会这样?
这个查询能不能再优化一下,两个查询都需要13秒。

表架构在 gist github 上可见

最佳答案

MySQL 选择 domenii 作为连接的主表。

此表根据 (status, tip_domeniu) = (1, 1) 进行过滤。

这似乎不是一个非常有选择性的条件,因此通常情况下,带过滤的全表扫描比索引扫描更可取。

我们可以看到 MySQL 期望从 domanii 返回 120 条记录,满足此条件。

当您添加 LIMIT 时,预期要处理的记录数会减少,并且 MySQL 认为索引扫描对此更有效。

注意这个条件:

Unix_timestamp(TIMESTAMPADD(DAY, 1, CAST(From_unixtime(l.data_limita) AS DATE))) < '1300683793'

不可优化搜索,因此您禁止优化器在 data_limita 上使用索引。

创建以下索引:

licitatii_ue (status, data_limita)
licitatii_ue (status, data_publicarii)

并像这样重写查询:

SELECT l.licitatii_id, 
       l.nume, 
       l.data_publicarii, 
       l.data_limita 
FROM   licitatii_ue l 
JOIN   domenii_licitatii dl 
ON     l.licitatii_id = dl.licitatii_id 
       AND dl.tip_licitatie = '2' 
JOIN   domenii d 
ON     dl.domenii_id = d.domenii_id 
       AND d.status = 1 
       AND d.tip_domeniu = '1' 
WHERE  l.status = 1
       AND l.data_limita < FROM_UNIXTIME(((1300683793 - 86400) div 86400) * 86400)
GROUP BY
       l.licitatii_id 
ORDER BY
       data_publicarii DESC 

关于mysql - 为什么在对 SQL 查询 + 优化查询使用 LIMIT 时扫描类型从 ALL 更改为 RANGE,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5375226/

相关文章:

mysql - 索引 bool 字段是否有任何性能提升?

mysql - 如何在 MySQL v8 中生成固定数量的行来进行性能测试?

sql - Rails 动态 'Where' 子句

mysql - 优化此 mysql 查询以尽可能使用我的索引?

php - 用于最快查找的最佳 MySQL 表结构

java - 我正在为 Jhipster 全新项目运行 Maven 命令,并尝试配置数据库连接,但失败

MySQL 慢查询和 EXPLAIN 给出了奇怪的答案

sql - 将 LIMIT 和 OFFSET 应用于 MS SQL Server 2008 查询

sql - 直接从 C# 调用 SQL 函数

sql - 甲骨文 SQL : Optimizing LEFT OUTER JOIN of two similar select statements to be smaller and/or more efficient