这是一个包含 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 时,我得到以下数字:
- TINYINT:~ 960 毫秒
- 时间戳:~ 1500 毫秒
更新
为了更直接地解决这个问题,我对数据做了一些更改:
- 我使用 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 列上创建索引。或者根据您的需要调整基准并做出自己的结论。