复合索引如何工作以及在什么情况下它们会失败?
How do composite indexes work and under what circumstances will they fail?
在建索引的时候,对复合索引有一些疑问
这是我的 SHOW CREATE TABLE
CREATE TABLE `sys_alarm` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`dept_id` bigint(20) NULL DEFAULT NULL,
`device_code` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`attr_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`attr_value` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`content` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
PRIMARY KEY (`alarm_id`) USING BTREE,
INDEX `search`(`dept_id`, `device_code`, `attr_name`, `create_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16610 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
当我执行下面的语句时,结果让我很困惑:
EXPLAIN
select a.alarm_id, a.dept_id, a.device_code, a.type, a.attr_name, a.attr_value, a.content, a.create_time
FROM sys_alarm a
WHERE a.dept_id = 214
and a.create_time > "2021-11-06 15:00:17"
EXPLAIN
select a.alarm_id, a.dept_id, a.device_code, a.type, a.attr_name, a.attr_value, a.content, a.create_time
FROM sys_alarm a
WHERE a.create_time > "2021-11-06 15:00:17"
and a.dept_id = 214
结果:
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE a null ref search search 9 const 1 33.33 Using index condition
我在MySQL documentation上看了这句话:
MySQL cannot use the index to perform lookups if the columns do not form a leftmost prefix of the index. Suppose that you have the SELECT statements shown here
那么,为什么复合索引仍然有效?只有我去掉dept_id
查询条件后,索引才会fail.What是复合索引失效的条件吗?
MySQL 将只使用“最左边的前缀”意味着它将只使用索引的 dept_id
部分,因为它不能跳到 create_time
。 EXPLAIN 输出中 key_len
为 9 的事实说明了这一点。我有点困惑,因为我希望它是 8(BIGINT 的字节长度)。我的理解在这里有一个漏洞。希望有人会在评论中解释这一点。
如果你 运行 你的 EXPLAIN
有和没有 create_time
条件你应该看到相同的结果 -
EXPLAIN SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214;
EXPLAIN SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214
AND `a`.`create_time` > '2021-11-06 15:00:17';
# id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
SIMPLE
a
ref
search
search
9
const
402
100.00
如果您修改查询以添加 device_code
的条件,您应该会看到 key_len
大小更改为 332,稍后我会回来讨论这个问题。
EXPLAIN
SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214
AND `a`.`device_code` = 'ABCDE';
# id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
SIMPLE
a
ref
search
search
332
const
const
1
100.00
并添加attr_name
-
EXPLAIN
SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214
AND `a`.`device_code` = 'ABCDE'
AND `a`.`attr_name` = 'dsgf';
# id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
SIMPLE
a
ref
search
search
535
const
const
const
1
100.00
最后加回去 create_time
-
EXPLAIN
SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214
AND `a`.`device_code` = 'ABCDE'
AND `a`.`attr_name` = 'dsgf'
AND `a`.`create_time` > '2021-11-06 15:00:17';
# id
select_type
table
partitions
type
possible_keys
key
key_len
ref
rows
filtered
Extra
1
SIMPLE
a
range
search
search
539
1
100.00
Using index condition
现在回到 key_len
从 9 到 332 的变化。当仅使用 dept_id
时,它只是 BIGINT。一旦添加 device_code
,它就会跳转到 332。我不是 100% 确定为什么是 332,但它类似于 - 80(varchar 长度)* 4(由于多字节字符集,每个字符的字节数)= 320 .
ROW_FORMAT = Compact
的使用表明您很清楚数据大小,但您正在使用 BIGINT(8 字节)来存储 dept_id
。你真的需要9,223,372,036,854,775,807部门吗?
WHERE
中子句的顺序无关紧要。 INDEX
中的列顺序很重要。
您的两个查询和 nnichols 的前两个查询都会受益于 INDEX(dept_id, create_time)
。
dept_id
由 =
测试,所以它应该排在第一位。
create_time
被一个'range'测试过,所以应该是最后一个。
我会在 http://mysql.rjweb.org/doc.php/index_cookbook_mysql
中讨论更多细节
nnichols 继续有多个 =
子句,加上一个 >
测试。使用 =
测试的列应该以任何顺序在复合 INDEX
中排在第一位,然后是 create_time
.
Key_len 和基数在选择如何排列索引中的列时不很重要。
我同意 BIGINT
很少 需要。 BIGINT NOT NULL
在 Explain 中显示为 8; BIGINT NULL
显示为9,即神秘的1是因为NULLable
。 (这没什么大不了的,只是令人困惑。)同时,DATETIME
将显示为 9 或 10 个字节,具体取决于 NOT NULL
.
另一个注意事项:如果您同时拥有 INDEX(dept_id)
和 INDEX(dept_id, create_time)
,优化器可能会选择前者,即使后者显然更好。所以放弃前一个索引并保留后者。
在建索引的时候,对复合索引有一些疑问
这是我的 SHOW CREATE TABLE
CREATE TABLE `sys_alarm` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`dept_id` bigint(20) NULL DEFAULT NULL,
`device_code` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`attr_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`attr_value` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`content` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
PRIMARY KEY (`alarm_id`) USING BTREE,
INDEX `search`(`dept_id`, `device_code`, `attr_name`, `create_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 16610 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
当我执行下面的语句时,结果让我很困惑:
EXPLAIN
select a.alarm_id, a.dept_id, a.device_code, a.type, a.attr_name, a.attr_value, a.content, a.create_time
FROM sys_alarm a
WHERE a.dept_id = 214
and a.create_time > "2021-11-06 15:00:17"
EXPLAIN
select a.alarm_id, a.dept_id, a.device_code, a.type, a.attr_name, a.attr_value, a.content, a.create_time
FROM sys_alarm a
WHERE a.create_time > "2021-11-06 15:00:17"
and a.dept_id = 214
结果:
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE a null ref search search 9 const 1 33.33 Using index condition
我在MySQL documentation上看了这句话:
MySQL cannot use the index to perform lookups if the columns do not form a leftmost prefix of the index. Suppose that you have the SELECT statements shown here
那么,为什么复合索引仍然有效?只有我去掉dept_id
查询条件后,索引才会fail.What是复合索引失效的条件吗?
MySQL 将只使用“最左边的前缀”意味着它将只使用索引的 dept_id
部分,因为它不能跳到 create_time
。 EXPLAIN 输出中 key_len
为 9 的事实说明了这一点。我有点困惑,因为我希望它是 8(BIGINT 的字节长度)。我的理解在这里有一个漏洞。希望有人会在评论中解释这一点。
如果你 运行 你的 EXPLAIN
有和没有 create_time
条件你应该看到相同的结果 -
EXPLAIN SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214;
EXPLAIN SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214
AND `a`.`create_time` > '2021-11-06 15:00:17';
# id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | a | ref | search | search | 9 | const | 402 | 100.00 |
如果您修改查询以添加 device_code
的条件,您应该会看到 key_len
大小更改为 332,稍后我会回来讨论这个问题。
EXPLAIN
SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214
AND `a`.`device_code` = 'ABCDE';
# id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | a | ref | search | search | 332 | const | const | 1 | 100.00 |
并添加attr_name
-
EXPLAIN
SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214
AND `a`.`device_code` = 'ABCDE'
AND `a`.`attr_name` = 'dsgf';
# id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | a | ref | search | search | 535 | const | const | const | 1 | 100.00 |
最后加回去 create_time
-
EXPLAIN
SELECT `a`.`alarm_id`, `a`.`dept_id`, `a`.`device_code`, `a`.`type`, `a`.`attr_name`, `a`.`attr_value`, `a`.`content`, `a`.`create_time`
FROM `sys_alarm` `a`
WHERE `a`.`dept_id` = 214
AND `a`.`device_code` = 'ABCDE'
AND `a`.`attr_name` = 'dsgf'
AND `a`.`create_time` > '2021-11-06 15:00:17';
# id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | a | range | search | search | 539 | 1 | 100.00 | Using index condition |
现在回到 key_len
从 9 到 332 的变化。当仅使用 dept_id
时,它只是 BIGINT。一旦添加 device_code
,它就会跳转到 332。我不是 100% 确定为什么是 332,但它类似于 - 80(varchar 长度)* 4(由于多字节字符集,每个字符的字节数)= 320 .
ROW_FORMAT = Compact
的使用表明您很清楚数据大小,但您正在使用 BIGINT(8 字节)来存储 dept_id
。你真的需要9,223,372,036,854,775,807部门吗?
WHERE
中子句的顺序无关紧要。 INDEX
中的列顺序很重要。
您的两个查询和 nnichols 的前两个查询都会受益于 INDEX(dept_id, create_time)
。
dept_id
由=
测试,所以它应该排在第一位。create_time
被一个'range'测试过,所以应该是最后一个。
我会在 http://mysql.rjweb.org/doc.php/index_cookbook_mysql
中讨论更多细节nnichols 继续有多个 =
子句,加上一个 >
测试。使用 =
测试的列应该以任何顺序在复合 INDEX
中排在第一位,然后是 create_time
.
Key_len 和基数在选择如何排列索引中的列时不很重要。
我同意 BIGINT
很少 需要。 BIGINT NOT NULL
在 Explain 中显示为 8; BIGINT NULL
显示为9,即神秘的1是因为NULLable
。 (这没什么大不了的,只是令人困惑。)同时,DATETIME
将显示为 9 或 10 个字节,具体取决于 NOT NULL
.
另一个注意事项:如果您同时拥有 INDEX(dept_id)
和 INDEX(dept_id, create_time)
,优化器可能会选择前者,即使后者显然更好。所以放弃前一个索引并保留后者。