Aqui está um benchmark MariaDB (10.0.19) com 10 milhões de linhas (usando o plug-in de sequência 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;
Para medir o tempo eu uso
set profiling=1
e execute show profile
depois de executar uma consulta. Do resultado da criação de perfil, tomo o valor de Sending data
já que todo o resto é totalmente inferior a um ms. TINYINT índice:
SELECT COUNT(*) FROM test WHERE is_active = 1;
Tempo de execução:~ 738 ms
TIMESTAMP índice:
SELECT COUNT(*) FROM test WHERE deleted_at is null;
Tempo de execução:~ 748 ms
Tamanho do índice:
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'
Resultado:
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
Observe que enquanto TIMESTAMP (4 Bytes) é 4 vezes maior que TYNYINT (1 Byte), o tamanho do índice não é nem duas vezes maior. Mas o tamanho do índice pode ser significativo se não couber na memória. Então, quando eu mudo
innodb_buffer_pool_size
de 1G
para 50M
recebo os seguintes números:- TINYINT:~ 960 ms
- TIMESTAMP:~ 1500 ms
Atualizar
Para abordar a questão de forma mais direta, fiz algumas alterações nos dados:
- Em vez de TIMESTAMP, uso DATETIME
- Como as entradas geralmente raramente são excluídas, uso
rand(1)<0.99
(1% excluído) em vez derand(1)<0.5
(50% excluído) - O tamanho da tabela mudou de 10 milhões para 1 milhão de linhas.
SELECT COUNT(*)
alterado paraSELECT *
Tamanho do índice:
index_name | stat_value*@@innodb_page_size
------------------------------------------
PRIMARY | 25739264
deleted_at | 12075008
is_active | 11026432
Desde 99% de
deleted_at
os valores são NULL, não há diferença significativa no tamanho do índice, embora um DATETIME não vazio exija 8 Bytes (MariaDB). SELECT * FROM test WHERE is_active = 1; -- 782 msec
SELECT * FROM test WHERE deleted_at is null; -- 829 msec
Eliminando ambos os índices, ambas as consultas são executadas em cerca de 350 ms. E soltando o
is_active
coluna o deleted_at is null
consulta é executada em 280 ms. Observe que este ainda não é um cenário realista. Você provavelmente não desejará selecionar 990 mil linhas de 1 milhão e entregá-las ao usuário. Você provavelmente também terá mais colunas (talvez incluindo texto) na tabela. Mas mostra que você provavelmente não precisa do
is_active
coluna (se não adicionar informações adicionais) e que qualquer índice é, na melhor das hipóteses, inútil para selecionar entradas não excluídas. No entanto, um índice pode ser útil para selecionar linhas excluídas:
SELECT * FROM test WHERE is_active = 0;
Executa em 10 ms com índice e em 170 ms sem índice.
SELECT * FROM test WHERE deleted_at is not null;
Executa em 11 ms com índice e em 167 ms sem índice.
Descartando o
is_active
coluna ele executa em 4 ms com índice e em 150 ms sem índice. Portanto, se esse cenário de alguma forma se encaixar em seus dados, a conclusão seria:Elimine o
is_active
coluna e não crie um índice em deleted_at
coluna se você raramente seleciona entradas excluídas. Ou ajuste o benchmark às suas necessidades e tire sua própria conclusão.