为什么MySQL会使用索引交集而不是组合索引?

我不时会遇到奇怪的MySQL行为.假设我有索引(type,rel,created),(type),(rel).像这样的查询的最佳选择:

SELECT id FROM tbl
WHERE rel = 3 AND type = 3
ORDER BY created;

将使用索引(类型,rel,创建).
但MySQL决定将索引(类型)和(rel)相交,这会导致性能更差.这是一个例子:

mysql> EXPLAIN
    -> SELECT id FROM tbl
    -> WHERE rel = 3 AND type = 3
    -> ORDER BY created\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tbl
         type: index_merge
possible_keys: idx_type,idx_rel,idx_rel_type_created
          key: idx_type,idx_rel
      key_len: 1,2
          ref: NULL
         rows: 4343
        Extra: Using intersect(idx_type,idx_rel); Using where; Using filesort

和相同的查询,但添加了一个提示:

mysql> EXPLAIN
    -> SELECT id FROM tbl USE INDEX (idx_type_rel_created)
    -> WHERE rel = 3 AND type = 3
    -> ORDER BY created\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: tbl
         type: ref
possible_keys: idx_type_rel_created
          key: idx_type_rel_created
      key_len: 3
          ref: const,const
         rows: 8906
        Extra: Using where

我认为MySQL采用的执行计划在EXPLAIN命令的“rows”列中包含较少的数字.从这个角度来看,4343行的索引交集看起来比使用8906行的组合索引要好得多.那么,也许问题在于这些数字?

mysql> SELECT COUNT(*) FROM tbl WHERE type=3 AND rel=3;
+----------+
| COUNT(*) |
+----------+
|     3056 |
+----------+

由此可以得出结论,MySQL错误地计算了组合索引的近似行数.

那么,我可以做些什么来让MySQL采取正确的执行计划?

我不能使用优化器提示,因为我必须坚持使用Django ORM
我发现的唯一解决方案是删除那些单字段索引.

MySQL版本是5.1.49.

表结构是:

CREATE TABLE tbl (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `type` tinyint(1) NOT NULL,
  `rel` smallint(2) NOT NULL,
  `created` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_type` (`type`),
  KEY `idx_rel` (`rel`),
  KEY `idx_type_rel_created` (`type`,`rel`,`created`)
) ENGINE=MyISAM;
很难确切地说明为什么MySQL会在索引扫描中选择index_merge_intersection,但是您应该注意到,对于复合索引,将为复合索引存储直到给定列的统计信息.

对于复合索引的列类型,information_schema.statistics.cardinality的值将显示(rel,type)的基数,而不是类型本身.

如果rel和type之间存在相关性,则(rel,type)的基数将小于rel的基数和与相应列上的索引分开获取的类型的乘积.

这就是错误计算行数的原因(交叉点的大小不能大于联合).

您可以通过在@@ optimizer_switch中将其设置为off来禁止index_merge_intersection:

SET optimizer_switch = 'index_merge_intersection=off'

转载注明原文:为什么MySQL会使用索引交集而不是组合索引? - 代码日志