【问题标题】:MySQL Query IN() Clause Slow on Indexed Column索引列上的 MySQL 查询 IN() 子句慢
【发布时间】:2011-06-13 20:24:46
【问题描述】:

我有一个由 PHP 脚本生成的 MySQL 查询,查询看起来像这样:

SELECT * FROM Recipe_Data WHERE 404_Without_200 = 0 AND Failures_Without_Success = 0 AND RHD_No IN (10, 24, 34, 41, 43, 51, 57, 59, 61, 67, 84, 90, 272, 324, 402, 405, 414, 498, 500, 501, 510, 559, 562, 595, 632, 634, 640, 643, 647, 651, 703, 714, 719, 762, 765, 776, 796, 812, 814, 815, 822, 848, 853, 855, 858, 866, 891, 920, 947, 956, 962, 968, 1049, 1054, 1064, 1065, 1070, 1100, 1113, 1119, 1130, 1262, 1287, 1292, 1313, 1320, 1327, 1332, 1333, 1335, 1340, 1343, 1344, 1346, 1349, 1352, 1358, 1362, 1365, 1482, 1495, 1532, 1533, 1537, 1549, 1550, 1569, 1571, 1573, 1574, 1596, 1628, 1691, 1714, 1720, 1735, 1755, 1759, 1829, 1837, 1844, 1881, 1919, 2005, 2022, 2034, 2035, 2039, 2054, 2076, 2079, 2087, 2088, 2089, 2090, 2091, 2092, 2154, 2155, 2156, 2157, 2160, 2162, 2164, 2166, 2169, 2171, 2174, 2176, 2178, 2179, 2183, 2185, 2186, 2187, 2201, 2234, 2236, 2244, 2245, 2250, 2255, 2260, 2272, 2280, 2281, 2282, 2291, 2329, 2357, 2375, 2444, 2451, 2452, 2453, 2454, 2456, 2457, 2460, 2462, 2464, 2465, 2467, 2468, 2469, 2470, 2473, 2474, 2481, 2485, 2487, 2510, 2516, 2519, 2525, 2540, 2545, 2547, 2553, 2571, 2579, 2580, 2587, 2589, 2597, 2602, 2611, 2629, 2660, 2662, 2700, 2756, 2825, 2833, 2835, 2858, 2958, 2963, 2964, 3009, 3090, 3117, 3118, 3120, 3121, 3122, 3123, 3126, 3127, 3129, 3130, 3133, 3135, 3137, 3138, 3139, 3141, 3142, 3145, 3146, 3147, 3151, 3152, 3155, 3193, 3201, 3204, 3219, 3221, 3222, 3223, 3224, 3225, 3226, 3227, 3228, 3229, 3231, 3232, 3233, 3234, 3235, 3237, 3239, 3246, 3250, 3253, 3259, 3261, 3291, 3315, 3328, 3377, 3381, 3383, 3384, 3385, 3387, 3388, 3389, 3390, 3396, 3436, 3463, 3465, 3467, 3470, 3471, 3484, 3507, 3515, 3554, 3572, 3641, 3672, 3683, 3689, 3690, 3692, 3693, 3694, 3697, 3698, 3705, 3711, 3713, 3715, 3716, 3717, 3719, 3720, 3722, 3726, 3727, 3732, 3737, 3763, 3767, 3770, 3771, 3772, 3773, 3803, 3810, 3812, 3816, 3846, 3847, 3848, 3851, 3874, 3882, 3902, 3903, 3906, 3908, 3916, 3924, 3967, 3987, 4006, 4030, 4043, 4045, 4047, 4058, 4067, 4107, 4108, 4114, 4115, 4131, 4132, 4133, 4137, 4138, 4139, 4140, 4141, 4142, 4146, 4150, 4151, 4152, 4153, 4157, 4158, 4160, 4163, 4166, 4167, 4171, 4179, 4183, 4221, 4225, 4242, 4257, 4435, 4437, 4438, 4443, 4446, 4449, 4450, 4451, 4452, 4454, 4460, 4550, 4557, 4618, 4731, 4775, 4804, 4972, 5025, 5026, 5039, 5042, 5294, 5578, 5580, 5599, 5602, 5649, 5726, 5779, 5783, 5931, 5934, 5936, 5939, 5940, 5941, 5978, 6044, 6056, 6113, 6116, 6118, 6122, 6123, 6125, 6127, 6128, 6129, 6130, 6131, 6135, 6141, 6145, 6147, 6150, 6152, 6153, 6154, 6160, 6166, 6169);

RHD_No 列是该数据库的主键,总共大约有 400,000 行。问题是,查询非常慢,通常在 2 秒左右,但我看到它长达 10 秒。

当我尝试解释查询时,一切似乎都应该没问题:

+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
| id | select_type | table       | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+
|  1 | SIMPLE      | Recipe_Data | range | PRIMARY       | PRIMARY | 4       | NULL |  420 | Using where |
+----+-------------+-------------+-------+---------------+---------+---------+------+------+-------------+

当我分析我得到的查询时:

mysql> show profile;
+--------------------------------+----------+
| Status                         | Duration |
+--------------------------------+----------+
| starting                       | 0.000015 |
| checking query cache for query | 0.000266 |
| Opening tables                 | 0.000009 |
| System lock                    | 0.000004 |
| Table lock                     | 0.000006 |
| init                           | 0.000115 |
| optimizing                     | 0.000038 |
| statistics                     | 0.000797 |
| preparing                      | 0.000047 |
| executing                      | 0.000002 |
| Sending data                   | 2.675270 |
| end                            | 0.000007 |
| query end                      | 0.000003 |
| freeing items                  | 0.000071 |
| logging slow query             | 0.000002 |
| logging slow query             | 0.000058 |
| cleaning up                    | 0.000005 |
+--------------------------------+----------+

我已经解决这个问题很长时间了,但我一直无法找到解决方案。这个查询有什么明显的错误吗?我不知道查看 420 行需要 2 多秒。

【问题讨论】:

标签: php mysql performance


【解决方案1】:

您正在按主键访问 420 行,这可能会导致索引访问路径。这可以访问 2 个索引页和每个键的一个数据页。如果这些都在缓存中,则查询应该运行得很快。否则,每次访问磁盘的页面都会产生通常的磁盘延迟。如果我们假设 5 毫秒的磁盘延迟和 80% 的缓存命中率,我们将得到 420*3*0.2*5ms=1.2 秒,这与您所看到的差不多。

【讨论】:

  • 那么我该如何改进我的查询呢?
【解决方案2】:

问题是IN基本上被视为一堆ORs(例如

col IN (1,2,3)

col = 1 OR col = 2 OR col = 3

这比连接慢很多。

你应该做的是生成创建临时表的 SQL 代码,用“IN”子句中的值填充它,然后加入该临时表

CREATE TEMPORARY TABLE numbers (n INT)

然后循环添加

INSERT numbers  VALUES ($next_number)

然后在最后

SELECT * FROM numbers, Recipe_Data 
WHERE numbers.n = RHD_No

【讨论】:

  • 别忘了把临时表放在最后,很明显:)
  • 我认为像这样的IN 子句的性能在服务器之间肯定会有很大差异。这个问题被标记为“mysql”,所以也许这就是你所指的。根据我使用 SQL Server 2000/2005 的经验,用于主键查找的大型 IN 子句非常快(我正在考虑的应用程序中的数据库和查询都经过了高度优化;它都是高流量的代码,大容量网络应用程序)。
  • @Pointy - 它可能是特定于服务器的,我知道 Sybase 在 IN 中具有这种性能行为,至少在旧版本(12 之前)上是这样。
  • 我还看到了大型 IN 子句的性能问题。 MySQL 似乎没有使用索引,因为它可以使用这些子句。
  • 是否有像 OR(IN 函数)这样的多个 AND 函数的函数?
【解决方案3】:

您应该将 IN 子句转换为 INNER JOIN 子句。

您可以像这样转换查询:

SELECT  foo   
FROM    bar   
WHERE bar.stuff IN  
       (SELECT  stuff FROM asdf)

进入一个类似这样的查询:

SELECT  b.foo 
FROM    ( 
        SELECT  DISTINCT stuff 
        FROM    asdf ) a 
JOIN    bar b 
ON      b.stuff = a.stuff

您将获得很多性能。

当 php 生成查询时,尝试一些技巧,例如为 IN 子句中的项目创建临时表。尽可能避免使用 IN 子句,因为它们非常耗时。

【讨论】:

【解决方案4】:

我打算在这里赌一把,建议只执行一次以下查询以创建适合您查询的索引,这样可以将查询时间至少减少一秒...

CREATE INDEX returnstatus ON Recipe_Data(404_Without_200,Failures_Without_Success)

请参阅:http://dev.mysql.com/doc/refman/5.0/en/create-index.html 了解如何创建索引,http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html 了解如何在查询中使用索引。

如果失败,请查看 mysql 上所有正在运行的进程,以查看来自任何来源的当前正在运行的查询是否只是拒绝死亡,同时消耗所有服务器的时间并将其杀死。见:http://dev.mysql.com/doc/refman/5.0/en/kill.html

如果做不到这一点,请确定每条记录可能还有什么共同点,以避免在IN 语句中通过 ID 号单独引用每条记录。如有必要,添加另一个表列以跟踪该共性。然后,将具有该共性的列添加到上述索引中,并在您的 WHERE 子句中按其过滤,而不是使用 IN 语句。例如,如果您只想在页面上打印出这些 ID 号,则将 visible 列作为类型​​:tinyint 排除值 0,并将值 1 包含在搜索结果中,然后添加visible 列到您的索引 WHERE 子句以加快查询速度。你根本不需要IN 声明。

也许您的in 语句是使用先前的查询动态构建的。如果是这种情况,请尝试使用Recipe_Data WHERE 404_Without_200 = 0 AND Failures_Without_Success = 0 拉取所有行。然后在您的 PHP 脚本中,如果 RHD_No 与预期值不匹配,则只需丢弃 fetch 循环中的记录。

【讨论】:

    【解决方案5】:

    对于像我这样使用 SQLAlchemy 的人来说,使用 for-loop 也是一个不错的选择:

    rows=[]
    
    for id in ids:
      row = cls.query.filter(cls.id==id).first()
      if row:
         rows.append(row)
    
    #return rows
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-22
      • 2015-04-02
      • 1970-01-01
      • 2011-05-25
      • 2012-06-11
      • 2014-07-15
      • 2010-10-14
      • 2012-02-12
      相关资源
      最近更新 更多