TL; DR; MySQL认为查询无法获得索引,但由于基数较低,使用所有值的速度仍然更快。有没有办法迫使MySQL总是使用复合索引,即使它认为它不是一个可能的索引?
完整的问题......
我有一个存储在MySQL中的队列系统的状态。队列项目被推送到队列服务器,但我们使用数据库确保仅处理节点上的依赖对象的1次处理。
在同一个表中跟踪多个队列,由queue_name
varchar字段标识。任何项目的状态可以是queued
,processing
,done
或failed
之一。要快速计算或获取未完成的项目,queue_name
+ status
上会有一个综合索引。
queue_name
是一个非常低的基数列(目前只有3个可能的值)。
架构:
CREATE TABLE `queue` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`queue_name` varchar(255) NOT NULL,
`status` enum('queued','processing','done','failed') NOT NULL,
`payload` longtext NOT NULL,
PRIMARY KEY (`id`),
KEY `queue_queue_name_status_index` (`queue_name`,`status`)
) ENGINE=InnoDB;
获取processing
状态的所有项目时,MySQL会进行全表扫描。
EXPLAIN SELECT * FROM queue WHERE status IN ('queued', 'processing');
select_type: SIMPLE
table: queue
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1036882
filtered: 50.00
Extra: Using where
这将是我所期望的,因为没有"合适的"索引来快捷选择此选项。
但是,鉴于我知道queue_name
列的基数非常低,我可以使用相同的查询列出每个可能的queue_name
值:
EXPLAIN SELECT * FROM queue
WHERE queue_name IN ('default', 'email', 'order') /* All values */
AND status IN ('queued', 'processing');
select_type: SIMPLE
table: queue
partitions: NULL
type: range
possible_keys: queue_queue_name_status_index
key: queue_queue_name_status_index
key_len: 767
ref: NULL
rows: 9
filtered: 100.00
Extra: Using index condition; Using where
这正确地使用了复合索引,并根据当前数据从1M行过滤到5-10。
对于相同的结果,这要快得多。我曾尝试告诉MySQL使用该索引,但查询计划程序似乎抛弃它并忽略。 E.g。
EXPLAIN SELECT * FROM queue
FORCE INDEX (queue_queue_name_status_index)
WHERE status IN ('queued', 'processing');
select_type: SIMPLE
table: queue
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1037684
filtered: 50.00
Extra: Using where
这会产生相同的解释和慢查询,因为没有指定索引。索引未显示为可能的索引,未使用。
有没有办法迫使MySQL总是使用复合索引,即使它认为它不是一个可能的索引?查询计划程序总是将其排除在外,因此即使您使用FORCE INDEX
,MySQL也会决定不使用索引并执行全表扫描。这显然要慢得多。
答案 0 :(得分:0)
我认为没有办法强迫它超越你已经做过的事情;虽然用queue_name IN ('default', 'email', 'order')
之类的东西替换“强制”条件queue_name <> 'someimpossiblevalue'
可能更简单(也更快)。
queue_name IS NULL
可能更快,因为该字段定义为NOT NULL
,允许MySQL优化器将其替换为“TRUE”;但它最终可能会忽略指数。值得一试。