索引布尔列与日期时间列的查询性能
Posted
技术标签:
【中文标题】索引布尔列与日期时间列的查询性能【英文标题】:Performance of query on indexed Boolean column vs Datetime column 【发布时间】:2017-08-10 01:20:30 【问题描述】:如果索引设置在datetime
类型列而不是boolean
类型列(并且在该列上进行查询),查询性能是否有显着差异?
在我目前的设计中,我有 2 列:
is_active
TINYINT(1),已编入索引
deleted_at
日期时间
查询是SELECT * FROM table WHERE is_active = 1;
如果我在 deleted_at
列上创建索引并运行类似 SELECT * FROM table WHERE deleted_at is null;
的查询,会不会更慢?
【问题讨论】:
我预计不会有任何显着差异。但是为什么不运行一些基准测试呢? 布尔标志上的索引实际上是无用的——优化器将决定执行表扫描更快。 (我不知道非布尔列上的NULLs
。)
【参考方案1】:
这是一个包含 1000 万行的 MariaDB (10.0.19) 基准测试(使用sequence plugin):
drop table if exists test;
CREATE TABLE `test` (
`id` MEDIUMINT UNSIGNED NOT NULL,
`is_active` TINYINT UNSIGNED NOT NULL,
`deleted_at` TIMESTAMP NULL,
PRIMARY KEY (`id`),
INDEX `is_active` (`is_active`),
INDEX `deleted_at` (`deleted_at`)
) ENGINE=InnoDB
select seq id
, rand(1)<0.5 as is_active
, case when rand(1)<0.5
then null
else '2017-03-18' - interval floor(rand(2)*1000000) second
end as deleted_at
from seq_1_to_10000000;
为了测量我使用set profiling=1
并在执行查询后运行show profile
的时间。从分析结果中,我取 Sending data
的值,因为其他所有内容都小于 1 毫秒。
TINYINT 索引:
SELECT COUNT(*) FROM test WHERE is_active = 1;
运行时间:~ 738 毫秒
TIMESTAMP索引:
SELECT COUNT(*) FROM test WHERE deleted_at is null;
运行时间:~ 748 毫秒
索引大小:
select database_name, table_name, index_name, stat_value*@@innodb_page_size
from mysql.innodb_index_stats
where database_name = 'tmp'
and table_name = 'test'
and stat_name = 'size'
结果:
database_name | table_name | index_name | stat_value*@@innodb_page_size
-----------------------------------------------------------------------
tmp | test | PRIMARY | 275513344
tmp | test | deleted_at | 170639360
tmp | test | is_active | 97107968
请注意,虽然 TIMESTAMP(4 字节)是 TYNYINT(1 字节)的 4 倍,但索引大小甚至没有两倍大。但是,如果它不适合内存,则索引大小可能会很大。因此,当我将 innodb_buffer_pool_size
从 1G
更改为 50M
时,我得到以下数字:
更新
为了更直接地解决这个问题,我对数据做了一些更改:
我使用 DATETIME 代替 TIMESTAMP 由于条目通常很少被删除,我使用rand(1)<0.99
(1% 已删除)而不是 rand(1)<0.5
(50% 已删除)
表大小从 10M 更改为 1M 行。
SELECT COUNT(*)
改为SELECT *
索引大小:
index_name | stat_value*@@innodb_page_size
------------------------------------------
PRIMARY | 25739264
deleted_at | 12075008
is_active | 11026432
由于 99% 的 deleted_at
值为 NULL,因此索引大小没有显着差异,尽管非空 DATETIME 需要 8 个字节 (MariaDB)。
SELECT * FROM test WHERE is_active = 1; -- 782 msec
SELECT * FROM test WHERE deleted_at is null; -- 829 msec
删除两个索引两个查询在大约 350 毫秒内执行。删除is_active
列后,deleted_at is null
查询将在 280 毫秒内执行。
请注意,这仍然不是一个现实的场景。您不太可能希望从 1M 行中选择 990K 行并将其交付给用户。表格中可能还会有更多列(可能包括文本)。但它表明,您可能不需要 is_active
列(如果它不添加其他信息),并且任何索引在最好的情况下对于选择未删除的条目都是无用的。
然而,索引对于选择已删除的行很有用:
SELECT * FROM test WHERE is_active = 0;
使用索引在 10 毫秒内执行,不使用索引在 170 毫秒内执行。
SELECT * FROM test WHERE deleted_at is not null;
使用索引在 11 毫秒内执行,不使用索引在 167 毫秒内执行。
删除 is_active
列,它在 4 毫秒内执行索引,在 150 毫秒内执行索引。
因此,如果这种情况以某种方式符合您的数据,那么结论将是:如果您很少选择已删除的条目,则删除 is_active
列并且不要在 deleted_at
列上创建索引。或者根据您的需要调整基准并做出自己的结论。
【讨论】:
我很佩服你的回答!很详细的资料,有测试和总结!谢谢。 这很好,但这不是用户询问的内容。 OP 正在做SELECT *
,它不会使用索引(至少对于status
)。你使用SELECT COUNT(*)
会是“使用索引”,即只使用索引。
我认为答案正是我所要求的。我不知道这是怎么回事。
@RickJames 我同意我的答案的第一个版本可能会导致错误的结论。但正如您将在更新中看到的那样,MariaDB 使用的是“布尔”索引,即使它不应该使用。它对于选择已删除的行很有用。
一开始,测试涉及每个方向的 50%。现在您的百分比要小得多,优化器将切换到使用索引。 这个是要指出的。【参考方案2】:
我认为is_active
会更快,但您可以在一百万行上进行测试。
【讨论】:
以上是关于索引布尔列与日期时间列的查询性能的主要内容,如果未能解决你的问题,请参考以下文章
如果一个表有一个未索引列与索引列是一对多的关系,如何优化未索引列的查询?