为什么在第一种情况下不使用索引,而在另一种情况下起作用?

时间:2019-08-04 13:37:42

标签: mysql query-optimization explain

我想证明我的假设是正确的。我有两个表,只是索引顺序不同。

它们看起来像这样:

CREATE TABLE `ipcountry` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `ipFROM` INT(10) UNSIGNED ZEROFILL NOT NULL DEFAULT '0000000000',
    `ipTO` INT(10) UNSIGNED ZEROFILL NOT NULL DEFAULT '0000000000',
    `countrySHORT` CHAR(2) NOT NULL DEFAULT '' COLLATE 'utf8_czech_ci',
    `countryLONG` VARCHAR(255) NOT NULL DEFAULT ' ' COLLATE 'utf8_czech_ci',
    PRIMARY KEY (`id`),
    INDEX `ipINDEX` (`ipTO`, `ipFROM`)
)
COLLATE='utf8_czech_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2490331
;


CREATE TABLE `ipcountry2` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `ipFROM` INT(10) UNSIGNED ZEROFILL NOT NULL DEFAULT '0000000000',
    `ipTO` INT(10) UNSIGNED ZEROFILL NOT NULL DEFAULT '0000000000',
    `countrySHORT` CHAR(2) NOT NULL DEFAULT '' COLLATE 'utf8_czech_ci',
    `countryLONG` VARCHAR(255) NOT NULL DEFAULT ' ' COLLATE 'utf8_czech_ci',
    PRIMARY KEY (`id`),
    INDEX `ipINDEX` (`ipFROM`, `ipTO`)
)
COLLATE='utf8_czech_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2490331
;

两个表的行数完全相同,大约为2,500,000。

执行EXPLAIN SELECT * FROM `ipcountry` WHERE ipFROM<=3548978221 AND ipTO>=3548978221时我得到

{
    "table": "UnknownTable",
    "rows":
    [
        {
            "id": 1,
            "select_type": "SIMPLE",
            "table": "ipcountry",
            "partitions": null,
            "type": "range",
            "possible_keys": "ipINDEX",
            "key": "ipINDEX",
            "key_len": "4",
            "ref": null,
            "rows": 83260,
            "filtered": 33.33,
            "Extra": "Using index condition"
        }
    ]
}

执行EXPLAIN SELECT * FROM `ipcountry2` WHERE ipFROM<=3548978221 AND ipTO>=3548978221时我得到

{
    "table": "UnknownTable",
    "rows":
    [
        {
            "id": 1,
            "select_type": "SIMPLE",
            "table": "ipcountry2",
            "partitions": null,
            "type": "ALL",
            "possible_keys": "ipINDEX",
            "key": null,
            "key_len": null,
            "ref": null,
            "rows": 2515343,
            "filtered": 16.66,
            "Extra": "Using where"
        }
    ]
}

是因为运算符的优先级吗?

2 个答案:

答案 0 :(得分:3)

第一个说明中的通知:

        "key_len": "4",

这表明只有查询才读取索引中的第一个INT(4字节)以进行查找。您可以看到该查询将搜索范围从2.5M缩小到了约83K,选择性约为30:1。

        "rows": 83260,

在查询中有两个范围条件时,MySQL不能将索引的两列都用于B树搜索。它可以在第一列进行B树搜索,但是该索引的后续列不能用于该搜索。

您的查询还会使用index condition pushdown按存储引擎级别的另一列进行过滤,如附加注释所示:

        "Extra": "Using index condition"

这不是B树搜索的一部分,但是通过在将行从存储引擎返回到SQL层之前过滤掉行会有所帮助。

最重要的是,无法使用B树索引搜索来优化同一张表中不同列上的两个范围条件。

如果MySQL估计读取整个表的成本与使用索引大致相同,MySQL也将完全跳过使用索引。与您的条件匹配的行越多,则可能性越大。 InnoDB通过辅助索引读取行是额外的工作,因此,如果InnoDB估计您的索引查找将匹配大量行,则默认执行表扫描。发生这种情况的阈值尚未正式记载,但据我观察,当您的条件至少匹配表中20%的行时,就会发生这种情况。

在第二张表中,由于它也只能在第一列上进行过滤,因此我们可以推断出,仅ipFROM上的条件将与表中的行的较大子集匹配。您正在搜索所有小于3548978221或211.137.28.45的IP地址,这在IPv4地址范围内非常高。不足为奇的是,至少有20%的行的值小于该数字。

因此,MySQL优化器得出结论,在第二个查询中,它不会给使用索引带来足够的好处,因此决定进行表扫描。不使用第一列就不能使用索引的第二列。

答案 1 :(得分:2)

这是由于所选范围内的记录数不同。

使用索引在表中查找数据是一个两步过程。首先,它将使用索引查找满足范围条件的记录到索引的第一列。然后,它将在表中查找整个记录(因为您需要std::initializer_list)(使用主键)。这比从表(或索引)中读取相同数量的行要明显要慢。

因此,如果您仍然必须读取很多记录,那么仅读取所有记录(更快的记录)并丢弃不需要的记录可能比仅读取所需的记录(但是读取的记录更慢)要更快。记录)。您可以预期这种效果大约是表大小的10-20%。

假设自动增量值代表记录数,对于您的第一个查询/表,MySQL估计2490331条记录中的83260条(占3%)满足条件*。通过索引从表中获取完整记录是可行的。对于您的第二个查询/表,估计是必须从ipFROM <= 3548978221的2490331记录(101%,sic)中读取2515343,因此它将只读取整个表,而无需进行较慢的两步查找

您可以通过强制MySQL使用索引来将其与进行索引查找进行比较:

ipTO>=3548978221

如果您只选择索引中存在的列(或作为主键一部分的任何列),例如SELECT * FROM `ipcountry2` force index (`ipINDEX`) WHERE ipFROM<=3548978221 AND ipTO>=3548978221 ,这将使其成为covering index,MySQL无需第二次查找就可以满足您的请求,并且将始终使用该索引。