Oto test porównawczy MariaDB (10.0.19) z 10 milionami wierszy (przy użyciu wtyczki sekwencji ):
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;
Aby zmierzyć czas, używam set profiling=1
i uruchom show profile
po wykonaniu zapytania. Z wyniku profilowania biorę wartość Sending data
ponieważ wszystko inne trwa krócej niż jeden milisekund.
MAŁE indeks:
SELECT COUNT(*) FROM test WHERE is_active = 1;
Czas pracy:~ 738 ms
ZNACZNIK CZASOWY indeks:
SELECT COUNT(*) FROM test WHERE deleted_at is null;
Czas pracy:~ 748 ms
Rozmiar indeksu:
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'
Wynik:
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
Zauważ, że chociaż TIMESTAMP (4 bajty) jest 4 razy dłuższy niż TYNYINT (1 bajt), rozmiar indeksu nie jest nawet dwa razy większy. Ale rozmiar indeksu może być znaczący, jeśli nie mieści się w pamięci. Więc kiedy zmieniam innodb_buffer_pool_size
z 1G
do 50M
otrzymuję następujące numery:
- MAŁYINT:~ 960 ms
- ZNACZNIK CZASOWY:~ 1500 ms
Aktualizacja
Aby odpowiedzieć na pytanie bardziej bezpośrednio, wprowadziłem pewne zmiany w danych:
- Zamiast TIMESTAMP używam DATETIME
- Ponieważ wpisy są zwykle rzadko usuwane, używam
rand(1)<0.99
(1% usunięty) zamiastrand(1)<0.5
(50% usunięte) - Zmieniono rozmiar tabeli z 10 mln na 1 mln wierszy.
SELECT COUNT(*)
zmieniono naSELECT *
Rozmiar indeksu:
index_name | stat_value*@@innodb_page_size
------------------------------------------
PRIMARY | 25739264
deleted_at | 12075008
is_active | 11026432
Od 99% deleted_at
wartości są NULL, nie ma znaczącej różnicy w rozmiarze indeksu, chociaż niepusta DATETIME wymaga 8 bajtów (MariaDB).
SELECT * FROM test WHERE is_active = 1; -- 782 msec
SELECT * FROM test WHERE deleted_at is null; -- 829 msec
Usunięcie obu indeksów, oba zapytania wykonują w około 350 ms. I upuszczając is_active
kolumna deleted_at is null
zapytanie jest wykonywane w ciągu 280 ms.
Zauważ, że to wciąż nie jest realistyczny scenariusz. Prawdopodobnie nie będziesz chciał wybrać 990 tys. wierszy z 1 mln i dostarczyć je użytkownikowi. Prawdopodobnie będziesz mieć również więcej kolumn (może z tekstem) w tabeli. Ale pokazuje, że prawdopodobnie nie potrzebujesz is_active
kolumna (jeśli nie dodaje dodatkowych informacji) i że każdy indeks jest w najlepszym przypadku bezużyteczny do wybierania nieusuniętych wpisów.
Jednak indeks może być przydatny do zaznaczania usuniętych wierszy:
SELECT * FROM test WHERE is_active = 0;
Wykonuje w ciągu 10 ms z indeksem i w ciągu 170 ms bez indeksu.
SELECT * FROM test WHERE deleted_at is not null;
Wykonuje w 11 ms z indeksem i w 167 ms bez indeksu.
Upuszczenie is_active
kolumna jest wykonywana w 4 ms z indeksem i w 150 ms bez indeksu.
Więc jeśli ten scenariusz w jakiś sposób pasuje do twoich danych, wniosek będzie następujący:Usuń is_active
kolumna i nie twórz indeksu na deleted_at
kolumna, jeśli rzadko wybierasz usunięte wpisy. Lub dostosuj benchmark do swoich potrzeb i wyciągnij własne wnioski.