我有一个表包含大约 300 万行,其结构如下:
CREATE TABLE `profiles3m` (
`uid` int(10) unsigned NOT NULL,
`birth_date` date NOT NULL,
`gender` tinyint(4) NOT NULL DEFAULT '0',
`country` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'ID',
`city` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'Makassar',
`created_at` timestamp NULL DEFAULT NULL,
`premium` tinyint(4) NOT NULL DEFAULT '0',
`updated_at` timestamp NULL DEFAULT NULL,
`latitude` double NOT NULL DEFAULT '0',
`longitude` double NOT NULL DEFAULT '0',
`orderid` int(11) NOT NULL,
PRIMARY KEY (`uid`),
KEY `idx_composites_latitude_longitude_gender_birth_date_created_at` (`latitude`,`longitude`,`country`,`city`,`gender`,`birth_date`) USING BTREE,
KEY `idx_composites_country_city_gender_birth_date` (`country`,`city`,`gender`,`birth_date`,`orderid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
我未能告诉 MySQL 优化器使用复合索引定义中的所有列,似乎优化器只是忽略最后一列作为 orderid
进行排序,即您可能知道,InnoDB 表中的 uid
列的副本不能用于排序,因为它可能指示优化器使用 < strong>PRIMARY KEY 作为索引,而不是使用我们的复合索引,这就是创建 orderid
列的想法。
下面的SQL查询,以及Explain JSON,加上Show Index语句来显示表上的所有索引统计信息,可能有助于分析原因。
SELECT
pro.uid
FROM
`profiles3m` AS pro
WHERE
pro.country = 'INDONESIA'
AND pro.city IN ( 'MAKASSAR' )
AND pro.gender = 0
AND ( pro.birth_date BETWEEN ( NOW()- INTERVAL 35 YEAR ) AND ( NOW()- INTERVAL 25 YEAR ) )
AND pro.orderid > 0
ORDER BY
pro.orderid
LIMIT 30
解释一下JSON如下:
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "45278.73"
},
"ordering_operation": {
"using_filesort": true,
"cost_info": {
"sort_cost": "19051.43"
},
"table": {
"table_name": "pro",
"access_type": "range",
"possible_keys": [
"idx_composites_country_city_gender_birth_date"
],
"key": "idx_composites_country_city_gender_birth_date",
"used_key_parts": [
"country",
"city",
"gender",
"birth_date"
],
"key_length": "488",
"rows_examined_per_scan": 57160,
"rows_produced_per_join": 19051,
"filtered": "33.33",
"using_index": true,
"cost_info": {
"read_cost": "22417.02",
"eval_cost": "3810.29",
"prefix_cost": "26227.30",
"data_read_per_join": "9M"
},
"used_columns": [
"uid",
"birth_date",
"gender",
"country",
"city",
"orderid"
],
"attached_condition": "((`restful`.`pro`.`gender` = 0) and (`restful`.`pro`.`country` = 'INDONESIA') and (`restful`.`pro`.`city` = 'MAKASSAR') and (`restful`.`pro`.`birth_date` between <cache>((now() - interval 35 year)) and <cache>((now() - interval 25 year))) and (`restful`.`pro`.`orderid` > 0))"
}
}
}
}
下面是显示索引语句:
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 0 | PRIMARY | 1 | uid | A | 2984412 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_latitude_longitude_gender_birth_date_created_at | 1 | latitude | A | 2934360 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_latitude_longitude_gender_birth_date_created_at | 2 | longitude | A | 2984080 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_latitude_longitude_gender_birth_date_created_at | 3 | country | A | 2984080 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_latitude_longitude_gender_birth_date_created_at | 4 | city | A | 2984080 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_latitude_longitude_gender_birth_date_created_at | 5 | gender | A | 2984080 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_latitude_longitude_gender_birth_date_created_at | 6 | birth_date | A | 2984080 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_country_city_gender_birth_date | 1 | country | A | 1 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_country_city_gender_birth_date | 2 | city | A | 14 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_country_city_gender_birth_date | 3 | gender | A | 29 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_country_city_gender_birth_date | 4 | birth_date | A | 362449 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
| 1 | idx_composites_country_city_gender_birth_date | 5 | orderid | A | 2984412 | | | | BTREE |
+------------+----------------------------------------------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+
在解释 JSON 中真正有趣的是,他们告诉我们,如果优化器只能使用索引操作的四个部分,毫不奇怪,排序操作就是使用文件排序,正如您所知,这意味着执行速度较慢,这对应用程序性能不利。
idx_composites_country_city_gender_birth_date
(country
,city
,gender
,birth_date
,orderid
)
"ordering_operation": {
"using_filesort": true,
.....
"key": "idx_composites_country_city_gender_birth_date",
"used_key_parts": [
"country",
"city",
"gender",
"birth_date"
],
我是否遗漏了什么,是由 WHERE
语句中的 RANGE
子句引起的吗?,我已经使用复合索引序列中的不同列组合进行了测试例如,我正在将 orderid
列更改为 premium
,这是一个仅包含 0 和 1 的标志列类型,并且它有效,MySQL 优化器可以利用所有五个列,那么为什么优化器无法对 orderid
列执行相同操作?与基数有关吗?我不太确定,我唯一可以保证的是,无论如何操作,我都必须使 ORDER BY
正常工作,而不会对应用程序性能产生任何影响。
这几天我一直在寻找答案,但仍然无法解决。 差点忘了提及 MySQL 版本,以防有帮助。
+------------+
| version() |
+------------+
| 5.7.29-log |
+------------+
最佳答案
您注意到它只使用了索引的四列:
"used_key_parts": [
"country",
"city",
"gender",
"birth_date"
],
尽管 WHERE 子句中的条件引用了所有五列:
WHERE
pro.country = 'INDONESIA'
AND pro.city IN ( 'MAKASSAR' )
AND pro.gender = 0
AND ( pro.birth_date BETWEEN ( NOW()- INTERVAL 35 YEAR ) AND ( NOW()- INTERVAL 25 YEAR ) )
AND pro.orderid > 0
但是,这些条件有些不同。 国家
、城市
、性别
条件都是平等条件。一旦搜索找到具有这些值的索引子集,接下来就按 birth_date
对子集进行排序,如果有一些行与 birth_date
相关,则这些行是按 orderid
进一步排序。
就像您阅读电话簿一样,您会找到所有姓氏为“Smith”的人,他们是按名字排序的。如果有多个人也具有相同的名字,则会根据各自的电话号码在电话簿中进行排序。
Smith, Sarah 408-555-1234
Smith, Sarah 408-555-5678
但是,如果您搜索姓氏为 Smith 且名字以“S”开头的所有人员,该怎么办?
Smith, Sam 408-555-3298
Smith, Sarah 408-555-1234
Smith, Sarah 408-555-5678
Smith, Stan 408-555-4224
这些未按电话号码排序。仅当它们在前面的列中出现时,它们才会按姓氏排序,然后按名字排序,最后按电话号码排序。
如果您想按电话号码对它们进行排序,您可以创建一个索引,其中的列按其他顺序排列,例如姓氏、电话号码、名字。
Smith 408-555-1234 Sarah
Smith 408-555-2020 David
Smith 408-555-3298 Sam
Smith 408-555-4100 Charlie
Smith 408-555-4224 Stan
Smith 408-555-5555 Annette
Smith 408-555-5678 Sarah
现在它们按电话号码顺序排列,但其中还有其他名称与您的名字以“S”开头的条件不匹配。它们甚至没有按名字排序,因为只有当前两列并列时,名字的第三列才会被排序。
这指出了索引的一个普遍问题:您只能对涉及相等比较的列重新排序。如果要对结果进行排序,则仅当按索引中的列排序并且索引的所有前面的列仅用于相等比较时才可以使用索引。
在范围比较中引用一列后,搜索和排序时都会忽略索引中的任何后续列。
换句话说:索引可以有任意数量的列用于相等条件,索引的下一列可以用于范围条件或对结果进行排序。但用于这些操作中的任一操作的列数不超过一列。
您无法优化所有内容。
回复您的评论:如果您在不包括 birth_date
的列上有索引:
alter table profiles3m add key bk1 (country, city, gender, orderid);
然后 EXPLAIN 显示没有文件排序:
EXPLAIN SELECT
pro.uid
FROM
`profiles3m` AS pro
WHERE
pro.country = 'INDONESIA'
AND pro.city IN ( 'MAKASSAR' )
AND pro.gender = 0
AND ( pro.birth_date BETWEEN ( NOW()- INTERVAL 35 YEAR ) AND ( NOW()- INTERVAL 25 YEAR ) )
AND pro.orderid > 0
ORDER BY
pro.orderid
LIMIT 30\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: pro
partitions: NULL
type: range
possible_keys: bk1
key: bk1
key_len: 489
ref: NULL
rows: 1
filtered: 100.00
Extra: Using index condition; Using where
(行
看起来很低,因为我正在使用空表进行测试。)
需要注意的是,这使用索引来匹配与 country
、city
、gender
和 orderid< 匹配的所有行
。然后 MySQL 将以困难的方式评估 birth_date
的剩余条件:逐行。
但之后,优化器知道它已经按索引顺序获取了行,因此它知道自然会按 orderid
排序,因此它可以跳过文件排序。
这可能是也可能不是净赢。这取决于有多少行匹配,但必须根据 birth_date
的条件将其丢弃。评估每一行的条件的成本有多大。与使用索引按 birth_date
进行过滤所节省的成本相比,情况如何。
关于MYSQL 优化器只是忽略我在复合索引中用于 ORDER BY 的最后一列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62500996/