我有一个Providers表,如下所示:
| id | 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万行。我的lastName查询非常快。但是,firstName的速度非常慢。我在这里做错什么吗?在不更改或删除顺序的情况下,我还可以添加其他哪些索引来提高我的仅用于名字的查询的性能?
编辑1:
对EXPLAIN
查询的 lastName
输出:
{
"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
]
}
}
}
}
对EXPLAIN
查询的 firstName
输出:
{
"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:
运行SHOW SESSION STATUS LIKE 'Handler%'
后输出FLUSH STATUS
:
查询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)
:
EXPLAIN
查询的 firstName
输出:
{
"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"
}
]
}
答案 0 :(得分:0)
查询1
WHERE lastName LIKE 'smi%'
ORDER BY lastName ASC, firstName ASC, middleName
可能使用此索引。 (请提供EXPLAIN...
):
Providers_lastName_firstName_middleName
之所以有效,是因为它可以遍历索引的smi...
部分。
我假设SELECT *
仅提取4列,而id
是PRIMARY KEY
?而Providers_lastName_firstName_middleName
是INDEX(lastName, firstName, middleName)
,最后加上一个隐式id
,因为它是InnoDB ??
这意味着整个查询都可以在索引中运行。 EXPLAIN
可以通过说“使用索引”来确认这一点,这意味着“覆盖索引”。
此外,此查询仅涉及50行-因为索引已针对WHERE
和ORDER 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 the
INDEX`)的总和。
我希望查询1总共显示50个读操作,因为它(理论上)可以深入到smi
处的索引中,并获取下50个(或更少)行。这应该花费几毫秒。
查询2更加混乱,因为在查找具有该名字的50之前,它将需要扫描大量索引。不会是7M,但可能是5万行。如果索引的必要部分已缓存,则可能需要花费几秒钟的时间。或几分钟(如果没有)。
没有办法使Q2快于Q1。对于mar%
,此可能更快,而对于m%
:INDEX(first, last, middle)
,此可能更慢。也就是说,引入这样的索引是有风险的。
在大多数情况下,如果您也有INDEX(a)
,则INDEX(a,b)
是多余的。也就是说,您可能有两个可以删除的索引。