【问题标题】:Slow MySQL query using indexes使用索引的慢 MySQL 查询
【发布时间】:2019-06-24 02:25:58
【问题描述】:

我有一个 Providers 表,如下所示:

|编号 |姓氏 |名字 |中间名 | | --- | -------- | --------- | ---------- |

具有以下索引:

  • Providers_lastName
  • Providers_firstName
  • Providers_lastName_firstName
  • Providers_lastName_firstName_middleName

我的所有查询在 lastName 和 firstName 值中都使用尾随通配符:

SELECT * FROM Providers
WHERE lastName LIKE 'smi%'
ORDER BY lastName ASC, firstName ASC, middleName
LIMIT 0, 50
SELECT * FROM Providers
WHERE firstName LIKE 'mar%'
ORDER BY lastName ASC, firstName ASC, middleName
LIMIT 0, 50

我在这个表中有大约 700 万行。我的姓氏查询非常快。但是,firstName 的速度非常慢。我在这里做错了什么吗?在不更改或删除顺序的情况下,我还可以添加哪些其他索引来提高仅 firstName 查询的性能?

编辑 1:

EXPLAINlastName 查询的输出:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "69901.30"
    },
    "ordering_operation": {
      "using_filesort": false,
      "table": {
        "table_name": "Providers",
        "access_type": "range",
        "possible_keys": [
          "Providers_lastName",
          "Providers_lastName_firstName",
          "Providers_lastName_firstName_middleName"
        ],
        "key": "Providers_lastName_firstName_middleName",
        "used_key_parts": [
          "lastName"
        ],
        "key_length": "143",
        "rows_examined_per_scan": 59008,
        "rows_produced_per_join": 59008,
        "filtered": "100.00",
        "index_condition": "(`db_name`.`providers`.`lastName` like 'smi%')",
        "cost_info": {
          "read_cost": "64000.51",
          "eval_cost": "5900.80",
          "prefix_cost": "69901.31",
          "data_read_per_join": "158M"
        },
        "used_columns": [
          "id",
          "firstName",
          "middleName",
          "lastName",
          // OTHER COLUMNS
        ]
      }
    }
  }
}

EXPLAINfirstName 查询的输出:

{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "390813.95"
    },
    "ordering_operation": {
      "using_filesort": false,
      "table": {
        "table_name": "Providers",
        "access_type": "index",
        "possible_keys": [
          "Providers_firstName"
        ],
        "key": "Providers_lastName_firstName_middleName",
        "used_key_parts": [
          "lastName",
          "firstName",
          "middleName"
        ],
        "key_length": "309",
        "rows_examined_per_scan": 948,
        "rows_produced_per_join": 329914,
        "filtered": "5.27",
        "cost_info": {
          "read_cost": "357822.55",
          "eval_cost": "32991.40",
          "prefix_cost": "390813.95",
          "data_read_per_join": "883M"
        },
        "used_columns": [
          "id",
          "firstName",
          "middleName",
          "lastName",
          // OTHER COLUMNS
        ],
        "attached_condition": "(`db_name`.`providers`.`firstName` like 'mar%')"
      }
    }
  }
}

SHOW CREATE TABLE:

CREATE TABLE `Providers` (
  `id` varchar(10) NOT NULL,
  `firstName` varchar(20) DEFAULT NULL,
  `middleName` varchar(20) DEFAULT NULL,
  `lastName` varchar(35) DEFAULT NULL,
  /* Other columns */
  PRIMARY KEY (`id`),
  KEY `Providers_firstName` (`firstName`),
  KEY `Providers_lastName` (`lastName`),
  KEY `Providers_lastName_firstName` (`lastName`,`firstName`),
  KEY `Providers_lastName_firstName_middleName` (`lastName`,`firstName`,`middleName`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

编辑 2:

运行FLUSH STATUSSHOW SESSION STATUS LIKE 'Handler%' 的输出:

查询 1(名字):

{
    "data":
    [
        {
            "Variable_name": "Handler_commit",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_delete",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_discover",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_external_lock",
            "Value": "2"
        },
        {
            "Variable_name": "Handler_mrr_init",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_prepare",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_first",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_read_key",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_read_last",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_next",
            "Value": "1487176"
        },
        {
            "Variable_name": "Handler_read_prev",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd_next",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_update",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_write",
            "Value": "0"
        }
    ]
}

查询 2(姓氏):

{
    "data":
    [
        {
            "Variable_name": "Handler_commit",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_delete",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_discover",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_external_lock",
            "Value": "2"
        },
        {
            "Variable_name": "Handler_mrr_init",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_prepare",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_first",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_key",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_read_last",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_next",
            "Value": "49"
        },
        {
            "Variable_name": "Handler_read_prev",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd_next",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_update",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_write",
            "Value": "0"
        }
    ]
}

编辑 3

使用FORCE_INDEX(Providers_firstName):

EXPLAINfirstName 查询的输出:

{
    "query_block": {
      "select_id": 1,
      "cost_info": {
        "query_cost": "389514.60"
      },
    "ordering_operation": {
        "using_filesort": true,
        "table": {
          "table_name": "Providers",
          "access_type": "range",
          "possible_keys": [
            "Providers_firstName"
          ],
          "key": "Providers_firstName",
          "used_key_parts": [
            "firstName"
          ],
          "key_length": "83",
          "rows_examined_per_scan": 329914,
          "rows_produced_per_join": 329914,
          "filtered": "100.00",
          "index_condition": "(`db_name`.`providers`.`firstName` like 'mar%')",
          "cost_info": {
            "read_cost": "356523.20",
            "eval_cost": "32991.40",
            "prefix_cost": "389514.60",
            "data_read_per_join": "883M"
          },
        "used_columns": [
            "id",
            "firstName",
            "middleName",
            "lastName",
            // Other columns
          ]
      }
    }
  }
}

处理程序计数:

{
    "data":
    [
        {
            "Variable_name": "Handler_commit",
            "Value": "1"
        },
        {
            "Variable_name": "Handler_delete",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_discover",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_external_lock",
            "Value": "2"
        },
        {
            "Variable_name": "Handler_mrr_init",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_prepare",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_first",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_key",
            "Value": "51"
        },
        {
            "Variable_name": "Handler_read_last",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_next",
            "Value": "168497"
        },
        {
            "Variable_name": "Handler_read_prev",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_read_rnd",
            "Value": "50"
        },
        {
            "Variable_name": "Handler_read_rnd_next",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_savepoint_rollback",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_update",
            "Value": "0"
        },
        {
            "Variable_name": "Handler_write",
            "Value": "0"
        }
    ]
}

【问题讨论】:

  • 好奇每个查询的总记录数是多少? (您的样本中的那些,您的退货限制为 50,但两种情况下的行数是多少)
  • 我怀疑 order by 是您的问题。可能会发生表扫描,因为第一个 order by 字段不是您为 fname 查询过滤的 col。尝试将您的记录选择到临时表中,然后从中选择 * order by。
  • @IlanP,firstName 查询的总记录数为 168,497,我第一次运行它大约需要 20 秒。另一个的计数要少得多(30、298),但即使提取更多记录也更有效。例如,检索所有 lastName 以s 开头的记录 (456,194) 只需要 1.5 毫秒。
  • 请提供SHOW CREATE TABLEEXPLAIN SELECT...

标签: mysql indexing


【解决方案1】:

查询 1

WHERE lastName LIKE 'smi%'
ORDER BY lastName ASC, firstName ASC, middleName

可能使用此索引。 (请提供EXPLAIN...):

Providers_lastName_firstName_middleName

这样比较有效,因为它可以遍历索引的smi... 部分。

我假设 SELECT * 只获取 4 列,而 idPRIMARY KEY?? Providers_lastName_firstName_middleNameINDEX(lastName, firstName, middleName),最后加上一个隐含的 id,因为它是 InnoDB??

这意味着整个查询可以在索引中运行。 EXPLAIN 会通过说“使用索引”来确认这一点,这意味着“覆盖索引”。

此外,此查询仅涉及 50 行 - 因为索引非常适合 WHEREORDER BY,它实际上也可以折叠在 LIMIT 50 中。

查询 2

WHERE firstName LIKE 'mar%'
ORDER BY lastName ASC, firstName ASC, middleName

Providers_firstName

也可以遍历mar... 的索引,但随后必须访问数据以获取其余列。

但其他优化(覆盖等)均不适用。您可以添加INDEX(first, last, middle, id) 以使其更快。

此查询无法折叠到LIMIT

备注

在美国,10% 的名称以最常见的字母“S”开头。 (“10%”在全球范围内大致相同,但最受欢迎的字母可能会有所不同。)

优化器有多种方法来执行任何查询,并根据有限的信息选择“最佳”。当一个范围很明显时 (WHERE lastName LIKE 'S%'),它可能选择从使用索引切换到简单地丢弃许多行。我认为这不会发生在这里,但EXPLAIN 会再次告诉我们。

有关创建最佳索引的更多信息:http://mysql.rjweb.org/doc.php/index_cookbook_mysql

解释后

如果我正确阅读了EXPLAINs,他们都使用INDEX(last, first, middle),从而避免了排序。另请注意"using_filesort": false.,这允许查询在LIMIT 50 之后停止。

要收集更多信息,请运行以下命令:

FLUSH STATUS;
SELECT ...
SHOW SESSION STATUS LIKE 'Handler%';

如果Handler_write*0,则没有排序。同时,被触摸的Handler_read* values gives you the number of rows (probably in theINDEX`) 的总和。

我希望查询 1 总共显示 50 个读取,因为它(理论上)可以深入到 smi 处的索引并获取接下来的 50 个(或更少)行。这应该只需要几毫秒。

查询 2 比较混乱,因为它需要扫描大量索引才能找到具有该名字的 50 个索引。它不会是 7M,但可能是 50K 行。如果索引的必要部分被缓存,这可能需要几秒钟;如果不是,则等待几分钟。

没有办法让第二季度的速度和第一季度一样快。这可能对于mar% 更快,但对于m% 更慢:INDEX(first, last, middle)。也就是说,引入这样的索引是有风险的。

在大多数情况下,如果您还拥有INDEX(a,b)INDEX(a) 是多余的。也就是说,您有 2 个可能会被删除的索引。

【讨论】:

  • 感谢您的见解,@rick-james。我编辑了我的问题并添加了EXPLAINSHOW CREATE TABLE 输出详细信息。还有什么建议吗?
  • @Donovan - 我添加了更多。
  • 我添加了会话状态的输出。就像你说的,第一个查询只涉及 50 行,而另一个约 1.5M。如果添加额外的索引没有帮助,您认为最好的解决方案是提高第二个查询的性能吗?
  • FORCE INDEX(Providers_firstName) 有什么用? (检查EXPLAINHandler 计数。)
  • 看起来性能几乎与强制 firstName 索引相同。我添加了输出细节。
猜你喜欢
  • 2012-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-21
  • 1970-01-01
相关资源
最近更新 更多