索引布尔列与日期时间列的查询性能

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_size1G 更改为 50M 时,我得到以下数字:

TINYINT:~ 960 毫秒 时间戳:~ 1500 毫秒

更新

为了更直接地解决这个问题,我对数据做了一些更改:

我使用 DATETIME 代替 TIMESTAMP 由于条目通常很少被删除,我使用 rand(1)&lt;0.99(1% 已删除)而不是 rand(1)&lt;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 会更快,但您可以在一百万行上进行测试。

【讨论】:

以上是关于索引布尔列与日期时间列的查询性能的主要内容,如果未能解决你的问题,请参考以下文章

如果一个表有一个未索引列与索引列是一对多的关系,如何优化未索引列的查询?

Oracle - 将日期列与 sysdate 进行比较

使用分析函数选择具有 2 个日期列的记录的高性能查询

数据库的日期区间查询方法。

PostgreSQL 在按日期索引的时间戳字段上按日期搜索性能不佳

使用日期字段提高查询执行的性能