【问题标题】:Higher cardinality column first in an index when involving a range?涉及范围时,索引中的基数较高的列首先?
【发布时间】:2018-10-18 18:17:44
【问题描述】:
CREATE TABLE `files` (
  `did` int(10) unsigned NOT NULL DEFAULT '0',
  `filename` varbinary(200) NOT NULL,
  `ext` varbinary(5) DEFAULT NULL,
  `fsize` double DEFAULT NULL,
  `filetime` datetime DEFAULT NULL,
  PRIMARY KEY (`did`,`filename`),
  KEY `fe` (`filetime`,`ext`),          -- This?
  KEY `ef` (`ext`,`filetime`)           -- or This?
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

表中有一百万行。文件时间大多是不同的。 ext 值的数量是有限的。因此,filetime 具有高基数,ext 具有低得多的基数。

查询涉及extfiletime

WHERE ext = '...'
  AND filetime BETWEEN ... AND ...

这两个索引哪个更好?为什么?

【问题讨论】:

    标签: mysql performance indexing query-optimization mariadb


    【解决方案1】:

    首先,让我们尝试FORCE INDEX 选择effe。时间太短了,无法清楚地了解哪个更快,但 `EXPLAIN 显示了差异:

    首先强制filetime 上的范围。 (注:WHERE 中的顺序没有影响。)

    mysql> EXPLAIN SELECT COUNT(*), AVG(fsize)
        FROM files FORCE INDEX(fe)
        WHERE ext = 'gif' AND filetime >= '2015-01-01'
                          AND filetime <  '2015-01-01' + INTERVAL 1 MONTH;
    +----+-------------+-------+-------+---------------+------+---------+------+-------+-----------------------+
    | id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows  | Extra                 |
    +----+-------------+-------+-------+---------------+------+---------+------+-------+-----------------------+
    |  1 | SIMPLE      | files | range | fe            | fe   | 14      | NULL | 16684 | Using index condition |
    +----+-------------+-------+-------+---------------+------+---------+------+-------+-----------------------+
    

    首先强制低基数ext

    mysql> EXPLAIN SELECT COUNT(*), AVG(fsize)
        FROM files FORCE INDEX(ef)
        WHERE ext = 'gif' AND filetime >= '2015-01-01'
                          AND filetime <  '2015-01-01' + INTERVAL 1 MONTH;
    +----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+
    | id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                 |
    +----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+
    |  1 | SIMPLE      | files | range | ef            | ef   | 14      | NULL |  538 | Using index condition |
    +----+-------------+-------+-------+---------------+------+---------+------+------+-----------------------+
    

    显然,rowsef 更好。但是让我们检查一下优化器跟踪。输出相当庞大;我只会展示有趣的部分。不需要FORCE;跟踪将显示两个选项,然后选择更好的。

                 ...
                 "potential_range_indices": [
                    ...
                    {
                      "index": "fe",
                      "usable": true,
                      "key_parts": [
                        "filetime",
                        "ext",
                        "did",
                        "filename"
                      ]
                    },
                    {
                      "index": "ef",
                      "usable": true,
                      "key_parts": [
                        "ext",
                        "filetime",
                        "did",
                        "filename"
                      ]
                    }
                  ],
    

    ...

                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                        "index": "fe",
                        "ranges": [
                          "2015-01-01 00:00:00 <= filetime < 2015-02-01 00:00:00"
                        ],
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 16684,
                        "cost": 20022,               <-- Here's the critical number
                        "chosen": true
                      },
                      {
                        "index": "ef",
                        "ranges": [
                          "gif <= ext <= gif AND 2015-01-01 00:00:00 <= filetime < 2015-02-01 00:00:00"
                        ],
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 538,
                        "cost": 646.61,               <-- Here's the critical number
                        "chosen": true
                      }
                    ],
    

    ...

              "attached_conditions_computation": [
                {
                  "access_type_changed": {
                    "table": "`files`",
                    "index": "ef",
                    "old_type": "ref",
                    "new_type": "range",
                    "cause": "uses_more_keyparts"   <-- Also interesting
                  }
                }
    

    使用fe(范围列在前),可以使用范围,但它估计要扫描 16684 行来寻找ext='gif'

    使用ef(首先是低基数ext),它可以使用索引的两列并在BTree 中更有效地向下钻取。然后它发现了大约 538 行,所有这些都对查询有用——不需要进一步过滤。

    结论:

    • INDEX(filetime, ext) 只使用了第一列。
    • INDEX(ext, filetime) 使用了两列。
    • 将涉及= 测试的列首先放在索引中无论基数如何
    • 查询计划不会超出第一个“范围”列。
    • “基数”与复合索引和此类查询无关

    (“使用索引条件”表示存储引擎(InnoDB)将使用索引中超出用于过滤的列。)

    【讨论】:

    • 文档中的相关部分:“只要比较运算符为=&lt;=&gt;IS NULL,优化器就会尝试使用其他关键部分来确定区间。如果运算符是&gt;&lt;&gt;=&lt;=!=&lt;&gt;BETWEENLIKE,优化器使用它但不再考虑关键部分。 (Range Optimization)
    • 不错的基准!您说:“它可以使用索引的两列并在 BTree 中更有效地向下钻取”。你知道为什么吗?因为从算法上讲,MySQL 无论如何都可以(并且应该)使用这两个列,对吧?你有没有推荐任何关于这个主题的书(查询/索引的内部工作)?
    • @IsraelFonseca - 可以这样想——两列可以连接在一起形成一列。噗,关于基数的讨论消失了,但是 BTree 的结构和速度几乎是相同的。
    • @RickJames 我对 MySQL 的查询优化有点陌生,您如何获得 MySQL 上的“优化器跟踪”?我希望在我的查询中看到“成本”值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-17
    • 1970-01-01
    • 1970-01-01
    • 2023-02-22
    • 2010-09-23
    • 2014-05-15
    • 2021-03-31
    相关资源
    最近更新 更多