Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Desempenho da consulta na coluna booleana indexada vs coluna Datetime


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 de rand(1)<0.5 (50% excluído)
  • O tamanho da tabela mudou de 10 milhões para 1 milhão de linhas.
  • SELECT COUNT(*) alterado para SELECT *

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.