索引布尔列与Datetime列的查询性能

Ale*_*lex 5 mysql sql indexing performance mariadb

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

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

  • is_activeTINYINT(1),已编入索引
  • deleted_at 约会时间

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

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

Pau*_*gel 9

这是一个包含10M行的MariaDB(10.0.19)基准测试(使用序列插件):

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;
Run Code Online (Sandbox Code Playgroud)

测量我在执行查询后使用set profiling=1和运行的时间show profile.从分析结果我得到的值,Sending data因为其他一切总共不到一毫秒.

TINYINT指数:

SELECT COUNT(*) FROM test WHERE is_active = 1;
Run Code Online (Sandbox Code Playgroud)

运行时间:~ 738毫秒

TIMESTAMP指数:

SELECT COUNT(*) FROM test WHERE  deleted_at is null;
Run Code Online (Sandbox Code Playgroud)

运行时间:~ 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'
Run Code Online (Sandbox Code Playgroud)

结果:

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 
Run Code Online (Sandbox Code Playgroud)

请注意,虽然TIMESTAMP(4字节)是TYNYINT(1字节)的4倍,但索引大小甚至不是两倍大.但如果索引大小不适合内存,则索引大小可能很大.所以,当我innodb_buffer_pool_size从改变1G50M我得到以下数字:

  • TINYINT:~ 960毫秒
  • TIMESTAMP:约1500毫秒

更新

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

  • 而不是TIMESTAMP我使用DATETIME
  • 由于条目通常很少删除,我使用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
Run Code Online (Sandbox Code Playgroud)

由于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
Run Code Online (Sandbox Code Playgroud)

删除两个索引,两个查询都在大约350毫秒内执行.并且在280毫秒内删除查询执行的is_activedeleted_at is null.

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

但是,索引可用于选择已删除的行:

SELECT * FROM test WHERE is_active = 0;
Run Code Online (Sandbox Code Playgroud)

使用索引在10毫秒内执行,在没有索引的情况下执行170毫秒.

SELECT * FROM test WHERE deleted_at is not null;
Run Code Online (Sandbox Code Playgroud)

用索引执行11毫秒,在没有索引的情况下执行167毫秒.

丢弃is_active它在4毫秒内使用索引执行的列和在没有索引的150毫秒内执行的列.

因此,如果此方案以某种方式适合您的数据,则结论将是:删除is_active列,deleted_at如果您很少选择已删除的条目,则不要在列上创建索引.或者根据您的需求调整基准并做出自己的结论.

  • 我很佩服你的回答!非常详细的信息,包括测试和总结!谢谢. (2认同)