【问题标题】:Why scan type is changed from ALL to RANGE when using LIMIT on SQL queries + Optimize query为什么在 SQL 查询 + 优化查询上使用 LIMIT 时扫描类型从 ALL 更改为 RANGE
【发布时间】:2011-03-21 08:17:20
【问题描述】:

我有这个问题

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                                  |
+-----+--------------+--------+---------+-------------------------------------+----------+----------+---------------------------+-------+-----------+----------------------------------------------+

如你所见 type=ALL for d table

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

计划更改为range

+-----+--------------+--------+---------+-------------------------------------+-------------------------+----------+---------------------------+-------+-----------+----------------------------------------------+
| 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 上可见

【问题讨论】:

  • 假设前者更快,您能否不强制索引为 PRIMARY?
  • Schema 急需聚集主键索引——应该使用 innodb !有很多关于优化增强的建议,​​但它们过于广泛,即重写。
  • @f00 如果你能帮助我,我愿意采取行动。我们需要为 FULLTEXT 索引找到一些替代方案。
  • 如果您必须使用全文而不是第 3 方,您可以执行我在此处建议的操作 stackoverflow.com/questions/4732067/… 这为您提供了 innodb 的全部优势,例如集群 PK 索引、事务、行级锁定等...但补充了 myisam 的 FT 功能
  • 在尝试之前先阅读这篇文章,看看你是否明白这一点 stackoverflow.com/questions/4419499/… 你永远不知道,它可能会激励你自己朝着正确的方向前进 :)

标签: mysql sql query-optimization


【解决方案1】:

MySQL 选择 domenii 作为连接的前导表。

此表在(status, tip_domeniu) = (1, 1) 上过滤。

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

我们可以看到 MySQL 期望 120 记录会从 domanii 返回,对于该条件将成立。

当您添加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 

【讨论】:

    【解决方案2】:

    啊,查询优化器的奥秘很多,不为人知……

    一目了然,最明显的优化可能是

    AND Unix_timestamp(TIMESTAMPADD(DAY, 1, CAST(From_unixtime(l.data_limita) 
                                                   AS DATE))) 
    

    条款。

    根据 licitatii_ue 表中的记录数量,这看起来像是一项昂贵的操作,它会绕过任何可用的索引。

    【讨论】:

      【解决方案3】:

      ALL 是表扫描,range 是范围扫描(由于 LIMIT)。这没什么不好,实际上它也会导致使用密钥 (key_status_tip_domeniu)。

      你速度慢的原因很可能是你使用了ORDER BY data_publicarii DESC(这很容易测试,只需删除 ORDER BY 并对查询进行基准测试;预计会有几个数量级)。

      Mysql 承认(在解释的 Extra 列下)它正在使用文件排序(需要 order by,因为它不能或不知道如何使用索引)。添加另一个索引可能会有所帮助,特别是如果您确认 ORDER BY 会使其变慢。

      编辑 实际上,您的查询确实有一个大罪:

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

      如果您可以将任何函数应用于常量,请避免将它们应用于字段值。所以切换它并重写它为

      l.data_limita &lt; some_function('1300683793')

      无论some_function 多么复杂,它只会计算一次。 Mysql planner 会知道它是一个常量。您编写它的方式将强制 mysql 将 unix_timestamptimestampaddcastfrom_unixtime 应用于 each 行的 data_limita 值。现在在 I/O 绑定系统中,这通常只会在等待磁盘旋转时消耗一些额外的 CPU 周期(但是,它可能会变得很重要,您的系统可能会受到 CPU 绑定,这只是一件坏事)。最大的不同是您失去了在data_limita 上使用索引的可能性。

      最后,你所有的索引都是单字段索引,mysql 做了一些index merging,但不是很出色。您可能想尝试创建涵盖所有条件和排序顺序的索引(按目标查询的选择性顺序)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-19
        • 1970-01-01
        • 2014-08-21
        • 2015-12-03
        • 1970-01-01
        相关资源
        最近更新 更多