Pho*_*nix 10 mysql index order-by mysql-5.7
这是我的表,其中包含约 10,000,000 行数据
CREATE TABLE `votes` (
`subject_name` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`subject_id` int(11) NOT NULL,
`voter_id` int(11) NOT NULL,
`rate` int(11) NOT NULL,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`subject_name`,`subject_id`,`voter_id`),
KEY `IDX_518B7ACFEBB4B8AD` (`voter_id`),
KEY `subject_timestamp` (`subject_name`,`subject_id`,`updated_at`),
KEY `voter_timestamp` (`voter_id`,`updated_at`),
CONSTRAINT `FK_518B7ACFEBB4B8AD` FOREIGN KEY (`voter_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Run Code Online (Sandbox Code Playgroud)
这是索引基数
所以当我做这个查询时:
SELECT SQL_NO_CACHE * FROM votes WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
Run Code Online (Sandbox Code Playgroud)
我原以为它使用索引,voter_timestamp
但 mysql 选择使用它:
explain select SQL_NO_CACHE * from votes where subject_name = 'medium' and voter_id = 1001 and rate = 1 order by updated_at desc limit 20 offset 100;`
type:
index_merge
possible_keys:
PRIMARY,IDX_518B7ACFEBB4B8AD,subject_timestamp,voter_timestamp
key:
IDX_518B7ACFEBB4B8AD,PRIMARY
key_len:
102,98
ref:
NULL
rows:
9255
filtered:
10.00
Extra:
Using intersect(IDX_518B7ACFEBB4B8AD,PRIMARY); Using where; Using filesort
Run Code Online (Sandbox Code Playgroud)
我得到了 200-400 毫秒的查询时间。
如果我强制它使用正确的索引,例如:
SELECT SQL_NO_CACHE * FROM votes USE INDEX (voter_timestamp) WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
Run Code Online (Sandbox Code Playgroud)
Mysql可以在1-2ms内返回结果
这是解释:
type:
ref
possible_keys:
voter_timestamp
key:
voter_timestamp
key_len:
4
ref:
const
rows:
18714
filtered:
1.00
Extra:
Using where
Run Code Online (Sandbox Code Playgroud)
那么为什么mysql没有voter_timestamp
为我的原始查询选择索引呢?
我尝试过的是analyze table votes
, optimize table votes
, 删除该索引并再次添加它,但 mysql 仍然使用错误的索引。不太明白是什么问题。
MySQL 使用相对简单(比其他 RDBMS 更简单)的成本模型来规划查询,其中过滤数据集具有很高的优先级。在您使用合并索引的第一个查询中,估计需要扫描约 9000 行,而使用索引提示的第二个查询将需要 18000。我敢打赌,这在计算中的权重足以将比例移向合并. 您可以通过打开optimizer_trace
、运行查询并评估结果来确认这一点(或寻找其他原因)。
set global optimizer_trace='enabled=on';
-- run your query
SELECT SQL_NO_CACHE * FROM votes WHERE
voter_id = 1099 AND
rate = 1 AND
subject_name = 'medium'
ORDER BY updated_at DESC
LIMIT 20 OFFSET 100;
select * from information_schema.`OPTIMIZER_TRACE`;
Run Code Online (Sandbox Code Playgroud)
一句话评论index_merge
:在大多数情况下,您会发现它非常昂贵。尽管对于 OLAP 类型的场景非常有用,但它可能不太适合 OLTP,因为该操作可能会占用查询的大量时间,并且正如您所看到的,有时次优执行计划实际上更快。
幸运的是,MySQL 为优化器提供了开关,因此您可以根据需要对其进行自定义。
对于所有选项,您可以运行:
show global variables like 'optimizer_switch';
Run Code Online (Sandbox Code Playgroud)
要更改一个,您不必复制粘贴整个字符串。它的工作原理类似于dict.update()
python。
set global optimizer_switch='index_merge=off';
Run Code Online (Sandbox Code Playgroud)
如果可能的话,我也会看看你的表结构并改进。真的不建议拥有一个约 100 字节的主键和许多辅助键。
您有四个辅助键,其中一些是多余的,例如(voter_id)
index 是(voter_id, updated_at)
对于那个查询,你需要这个索引:
INDEX(voter_id, rate, subject_name, updated_at)
Run Code Online (Sandbox Code Playgroud)
的updated_at
必须是最后一个; 其他三个可以按任何顺序排列。(ypercube 的 3 列索引不是很有用,因为它们WHERE
在击中列之前没有完成ORDER BY
列。)
添加此索引时,您可能可以摆脱所有其他辅助键:
KEY IDX_518B7ACFEBB4B8AD
( voter_id
), -- FK 可以使用我的索引 KEY subject_timestamp
( subject_name
, subject_id
, updated_at
), -- 主要是多余的 KEY voter_timestamp
( voter_id
, updated_at
), -- 可能是你的尝试
使用 4 列索引,您有机会优化“分页”并避免OFFSET
. 看到这个博客。
关于另一个话题......当我看到X_name
和 时X_id
,我认为“规范化”正在进行中。我希望在表格中看到这两列,几乎没有其他内容。我不希望在其他表中看到两者。
(voter_id, updated_at)
不会过去,voter_id
因为它还没有完成过滤 (the WHERE
)。然后,由于其他索引较小,因此将其选中。我的有 3 列来处理过滤,然后是ORDER BY
.
归档时间: |
|
查看次数: |
5270 次 |
最近记录: |