MySQL为什么要更改用于同一查询的索引?

时间:2016-07-12 18:33:58

标签: mysql

我发布这是一个新问题(来自我在这里问到的另一个问题:How to efficiently select and group by a substring of a column),因为我认为出现的新问题需要保证自己的帖子。我不确定这是否是正确的做法,但如果不是,请告诉我。

我目前在MySQL(V5.5.14),InnoDB(V1.1.8)上有3个(简化)表:

1)这个表实际上有四个不同版本,其中每个存储某种类型的数据,但都有这3列

+--------------------------------------------------+
|  PropData                                        |
+--------------+-----------+-----------------------+
|  Id          |  BigTag   |  Date                 |
+--------------+-----------+-----------------------+
|  [SomeGUID]  |  10001AB  |  1000-01-01 00:00:00  |
+--------------+-----------+-----------------------+

2)此表中的记录镜像上表中的数据,除了它只包含主键(id)和BigTag的前5个字符(以便与LimitTags轻松连接)。每个PropData表都有一个这样的表

+------------------------+
|  PropDataTag           |
+--------------+---------+
|  Id          |  Tag    |
+--------------+---------+
|  [SomeGUID]  |  10001  |
+--------------+---------+

3)此表只有一个版本

+-------------------+
|  LimitTags        |
+---------+---------+
|  Tag    |  Model  |
+---------+---------+
|  10001  |  Base   |
+---------+---------+

我基本上试图在一段时间内为一系列数据获取所有独特的标签和模型对。

通过这三个表,我最终提出了以下查询:

SELECT DISTINCT T.Tag, T.Model
FROM PropData P
   JOIN PropDataTag N ON P.Id=N.Id
      JOIN LimitTags T ON N.Tag=T.Tag
WHERE P.Date BETWEEN '0000-01-01 00:00:00' AND '9999-12-31 23:59:59'

结果看起来像这样:

+---------+----------+
|  Tag    |  Model   |
+---------+----------+
|  10001  |  Base    |
|  10002  |  Base    |
|  10003  |  Base    |
|  10004  |  Base    |
|  10001  |  Upgrade |
|  10002  |  Upgrade |
|  10001  |  Crappy  |
+---------+----------+

我有以下索引:

1) PropData PRIMARY (Id), IdxDate (日期), IdxTag (BigTag), IdxIdAndDate (Id,Date)

2) PropDataTag PRIMARY (Id), IdxTag (标记)

3) LimitTags PRIMARY (Id), IdxTag (标记), IdxTagAndModel (标记,型号) )

我第一次跑它,它完美无缺,我在0.016秒内得到了我的结果(696条记录)。我还运行了EXPLAIN命令,得到了以下结果。当我拍摄屏幕截图时,我遗憾地忽略了扩展参考列,因此我不知道其中的两个值是什么,并且无法再现这些结果。

id  select_type  table  type    possible_keys                 key             key_len   ref     rows     Extra 
1   SIMPLE       T      index   IdxTag,IdxTagAndModel         IdxTagAndModel  49        NULL    1427     Using index; Using temporary
1   SIMPLE       N      ref     IdxTag                        IdxTag          7         NoIdea  1238     Using index; Distinct
1   SIMPLE       P      eq_ref  PRIMARY,IdxDate,IdxIdAndDate  IdxDate         38        NoIdea  1        Using where; Distinct

认为一切都很顺利,然后我在所有4个PropData表上尝试完全相同的查询,并将它们联合起来以获得标签/模型对的完整列表。在运行查询之后,我花了超过2分钟才停止了太长时间。我尝试在上面显示的原始查询上运行explain命令,使用完全相同的表,而不是得到相同的结果,我得到以下内容:

id  select_type  table  type    possible_keys                 key         key_len   ref    rows     Extra 
1   SIMPLE       P      range   PRIMARY,IdxDate,IdxIdAndDate  IdxDate     24        NULL   1785585  Using where; Using Index; Using temporary
1   SIMPLE       N      eq_ref  PRIMARY,IdxTag                PRIMARY     38        P.Id   1
1   SIMPLE       T      ref     IdxTag,IdxTagAndModel         IdxTag      7         N.Tag  1

现在运行原始查询需要> 30秒而不是~0.016秒。这些表上的数据和索引肯定没有改变,我为这些查询运行了解释命令大约5分钟。

刚刚发生了什么?我无法弄清楚:

  • 为什么解释输出中行的顺序会发生变化?

  • 为什么MySQL决定突然使用不同的索引?

有没有人有任何想法或意见?我四处寻找其他帖子,但似乎没有人经历过以下结果。

编辑1:

当我尝试取消查询(没有解释)中期执行时,我能够重现这一次,这导致MySQL Workbench崩溃。重新启动时,它第一次工作,立即给我结果。当我用所有4个表运行查询时,索引再次切换,我遇到了与上面相同的现象,但是使用了一组新的EXPLAIN结果:

id  select_type  table  type    possible_keys                 key             key_len   ref    rows     Extra 
1   SIMPLE       P      range   PRIMARY,IdxDate,IdxIdAndDate  IdxDate         24        NULL   1796958  Using where; Using Index; Using temporary
1   SIMPLE       N      eq_ref  PRIMARY,IdxTag                PRIMARY         38        P.Id   1
1   SIMPLE       T      ref     IdxTag,IdxTagAndModel         IdxTagAndModel  7         N.Tag  1        Using index

我尝试强制查询使用所需的第一组EXPLAIN输出中显示的相同索引:

SELECT DISTINCT T.Tag, T.Model
FROM PropData P FORCE INDEX (PRIMARY)
   JOIN PropDataTag N FORCE INDEX (IdxTag) ON P.Id=N.Id
      JOIN LimitTags T FORCE INDEX (IdxTagAndModel) ON N.Tag=T.Tag
WHERE P.Date BETWEEN '0000-01-01 00:00:00' AND '9999-12-31 23:59:59'

我从解释中得到了这些结果:

id  select_type  table  type    possible_keys   key             key_len   ref    rows     Extra 
1   SIMPLE       N      index   IdxTag          PRIMARY         7         P.Id   1        Using index; Using temporary
1   SIMPLE       T      ref     IdxTagAndModel  IdxTagAndModel  7         N.Tag  1        Using index
1   SIMPLE       P      eq_ref  PRIMARY         IdxDate         38        NULL   1796958  Using where; Distinct

我看到这些最新结果与原始工作版本之间的主要区别在于IdxTagAndModel键只有一个key_len为7而不是49,并且表N在额外列中没有Distinct。

要注意的其他差异是表P的行数不同,而且使用临时表在表N而不是T上。

编辑2:

以下是我执行的完整查询,似乎可以切换使用的索引:

SELECT DISTINCT T.Tag, T.Model
FROM PropData1 P
   JOIN PropDataTag1 N ON P.Id=N.Id
      JOIN LimitTags T ON N.Tag=T.Tag
WHERE P.Date BETWEEN '0000-01-01 00:00:00' AND '9999-12-31 23:59:59'

UNION
SELECT DISTINCT T.Tag, T.Model
FROM PropData2 P
   JOIN PropDataTag2 N ON P.Id=N.Id
      JOIN LimitTags T ON N.Tag=T.Tag
WHERE P.Date BETWEEN '0000-01-01 00:00:00' AND '9999-12-31 23:59:59'

UNION
SELECT DISTINCT T.Tag, T.Model
FROM PropData3 P
   JOIN PropDataTag3 N ON P.Id=N.Id
      JOIN LimitTags T ON N.Tag=T.Tag
WHERE P.Date BETWEEN '0000-01-01 00:00:00' AND '9999-12-31 23:59:59'

UNION
SELECT DISTINCT T.Tag, T.Model
FROM PropData4 P
   JOIN PropDataTag4 N ON P.Id=N.Id
      JOIN LimitTags T ON N.Tag=T.Tag
WHERE P.Date BETWEEN '0000-01-01 00:00:00' AND '9999-12-31 23:59:59'

我最初没有包含它,因为它在不同的表上重复了3次相同的查询。每个表都包含不同类型的数据,例如double或BLOB,但在此查询中根本不使用它们。

1 个答案:

答案 0 :(得分:1)

这是大多数问题的根本原因。

  

我目前在MySQL(V5.5.14),InnoDB上有3个(简化)表   (V1.1.8):

     

1)这个表实际上有四个不同版本,其中   每个存储某种类型的数据,但都有这3列

所有三个具有相同列的表意味着这基本上是具有细微差别的相同数据。 RDBMS系统具有内置的机制来处理它。 Partitions

  

...通过使您能够分配各个表的各个部分   文件系统根据您可以根据需要设置的规则。在   效果,表的不同部分存储为单独的表   不同的地点。用户选择的划分规则   完成数据称为分区函数

通过使用分区,您可以立即消除使用UNION的需要。你的工会可以大大简化。

至于为什么简单查询使用一个索引而UNION查询使用另一个索引,这仅仅是因为它们在不同的PropDataTagX表中的行数似乎差异很大。如果它们都具有相似的行数,则可以使用相同的查询计划