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

Performance of query on indexed Boolean column vs Datetime column

如果在 datetime 类型列而不是 boolean 类型列上设置索引(并且在该列上进行查询),查询性能是否有显着差异?

在我目前的设计中,我有 2 列:

查询是 SELECT * FROM table WHERE is_active = 1;

如果我在 deleted_at 列上创建索引,并且 运行 像这样 SELECT * FROM table WHERE deleted_at is null; 查询,会不会更慢?

我认为 is_active 会更快,但您可以在一百万行上进行测试。

这是具有 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 的值,因为其他一切都不到一毫秒。

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)<0.99(删除 1%)而不是 rand(1)<0.5(删除 50%)
  • Table 大小从 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_activedeleted_at is null 查询在 280 毫秒内执行。

请注意,这仍然不是现实情况。您不太可能希望 select 1M 中的 990K 行并将其交付给用户。您可能还会在 table 中有更多的列(可能包括文本)。但它表明,您可能不需要 is_active 列(如果它不添加其他信息),并且任何索引在最好的情况下对于 selecting 未删除的条目都是无用的。

然而,索引对 select 删除的行很有用:

SELECT * FROM test WHERE is_active = 0;

有索引在 10 毫秒内执行,没有索引在 170 毫秒内执行。

SELECT * FROM test WHERE deleted_at is not null;

有索引在 11 毫秒内执行,没有索引在 167 毫秒内执行。

删除 is_active 列,它在有索引的情况下在 4 毫秒内执行,在没有索引的情况下在 150 毫秒内执行。

因此,如果这种情况以某种方式适合您的数据,那么结论是:如果您很少 selecting,请删除 is_active 列并且不要在 deleted_at 列上创建索引删除条目。或者根据您的需要调整基准并做出您自己的结论。