Her er et MariaDB (10.0.19) benchmark med 10 mio. rækker (ved hjælp af sekvensplugin a> ):
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;
For at måle tiden bruger jeg set profiling=1
og kør show profile
efter at have udført en forespørgsel. Fra profileringsresultatet tager jeg værdien af Sending data
da alt andet er mindre end én msek.
TINYINT indeks:
SELECT COUNT(*) FROM test WHERE is_active = 1;
Kørselstid:~ 738 msek
TIMESTAMP indeks:
SELECT COUNT(*) FROM test WHERE deleted_at is null;
Kørselstid:~ 748 msek
Indeksstørrelse:
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'
Resultat:
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
Bemærk, at mens TIMESTAMP (4 bytes) er 4 gange så lang som TYNYINT (1 byte), er indeksstørrelsen ikke engang dobbelt så stor. Men indeksstørrelsen kan være betydelig, hvis den ikke passer ind i hukommelsen. Så når jeg ændrer innodb_buffer_pool_size
fra 1G
til 50M
jeg får følgende tal:
- TINYINT:~ 960 msek
- TIMESTAMP:~ 1500 msek
Opdater
For at besvare spørgsmålet mere direkte har jeg lavet nogle ændringer i dataene:
- I stedet for TIMESTAMP bruger jeg DATETIME
- Da poster normalt sjældent slettes, bruger jeg
rand(1)<0.99
(1 % slettet) i stedet forrand(1)<0.5
(50 % slettet) - Tabelstørrelsen er ændret fra 10 mio. til 1 mio. rækker.
SELECT COUNT(*)
ændret tilSELECT *
Indeksstørrelse:
index_name | stat_value*@@innodb_page_size
------------------------------------------
PRIMARY | 25739264
deleted_at | 12075008
is_active | 11026432
Siden 99 % af deleted_at
værdier er NULL, der er ingen signifikant forskel i indeksstørrelse, selvom en ikke-tom DATETIME kræver 8 bytes (MariaDB).
SELECT * FROM test WHERE is_active = 1; -- 782 msec
SELECT * FROM test WHERE deleted_at is null; -- 829 msec
Slet begge indekser, udføres begge forespørgsler på omkring 350 msek. Og slippe is_active
kolonnen deleted_at is null
forespørgslen udføres på 280 msek.
Bemærk, at dette stadig ikke er et realistisk scenarie. Du vil næppe vælge 990K rækker ud af 1M og levere det til brugeren. Du vil sandsynligvis også have flere kolonner (måske med tekst) i tabellen. Men det viser, at du sandsynligvis ikke har brug for is_active
kolonne (hvis den ikke tilføjer yderligere information), og at ethvert indeks i bedste fald er ubrugeligt til at vælge ikke-slettede poster.
Et indeks kan dog være nyttigt til at vælge slettede rækker:
SELECT * FROM test WHERE is_active = 0;
Udføres på 10 msek med indeks og på 170 msek uden indeks.
SELECT * FROM test WHERE deleted_at is not null;
Udføres på 11 msek med indeks og på 167 msek uden indeks.
Sletning af is_active
kolonne, den udføres i 4 msek med indeks og i 150 msek uden indeks.
Så hvis dette scenarie på en eller anden måde passer til dine data, ville konklusionen være:Slip is_active
kolonne og opret ikke et indeks på deleted_at
kolonne, hvis du sjældent vælger slettede poster. Eller juster benchmark til dine behov og lav din egen konklusion.