索引布尔列与日期时间列的查询性能
Performance of query on indexed Boolean column vs Datetime column
如果在 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;
查询,会不会更慢?
我认为 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_size
从 1G
更改为 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_active
列 deleted_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
列上创建索引删除条目。或者根据您的需要调整基准并做出您自己的结论。
如果在 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;
查询,会不会更慢?
我认为 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_size
从 1G
更改为 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_active
列 deleted_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
列上创建索引删除条目。或者根据您的需要调整基准并做出您自己的结论。