Eu tenho um índice que filtra 99% da tabela ou seja ix_magic_composite
(para os argumentos dessa consulta). Quando eu adiciono outro or
filtro ele escolhe o índice errado, ou seja, fTS
mesmo que eu crie um índice que comece com esse campo ele ainda escolhe o índice errado. Os tempos de execução são 20s vs 3s para o melhor índice. ix_magic_composite
index retorna (filtro inicial) para ambos os SQLs em torno de 10 linhas de milhões, enquanto fTS
retorna milhões de volta.
Meio sem noção. Parece-me que as estatísticas não estão dando ao mecanismo a imagem certa com todas essas colunas combinadas.
Simplifiquei a tabela, ela tem muito mais colunas e índices.
SQL com bom plano:
select *
from tblExample
where 1=1
and status = 'okay'
and textCol > ''
and insrBLN = 1
and (magic is NULL or magic = '')
and (itemId is NULL or itemId = '')
and fTS > '2020-01-01'
and fTS > '2020-01-01'
order by fTS
limit 50
+----+-------------+------------+------------+-------------+--------------------------------------------------+---------------------+---------+-------+---------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------------+--------------------------------------------------+---------------------+---------+-------+---------+----------+----------------------------------------------------+
| 1 | SIMPLE | tblExample | NULL | ref_or_null | textCol,status,textCol_4,ix_magic_composite,fTS | ix_magic_composite | 53 | const | 5892974 | 0.24 | Using index condition; Using where; Using filesort |
+----+-------------+------------+------------+-------------+--------------------------------------------------+---------------------+---------+-------+---------+----------+----------------------------------------------------+
SQL com plano ruim:
select *
from tblExample
where 1=1
and status = 'okay'
and textCol > ''
and insrBLN = 1
and (magic is NULL or magic = '' or magic = 'retry')
and (itemId is NULL or itemId = '' or itemId = 'retry')
and fTS > '2020-01-01'
and fTS > '2020-01-01'
order by fTS
limit 50
+----+-------------+------------+------------+-------+-------------------------------------------------+---------+---------+------+---------+----------+------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+-------------------------------------------------+---------+---------+------+---------+----------+------------------------------------+
| 1 | SIMPLE | tblExample | NULL | range | textCol,status,textCol_4,ix_magic_composite,fTS | fTS | 5 | NULL | 6271587 | 0.18 | Using index condition; Using where |
+----+-------------+------------+------------+-------+----------------------------------------- ----+---------+---------+------+---------+----------+------------------------------------+
Mesa:
CREATE TABLE `tblExample` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`fTS` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`status` varchar(50) NOT NULL DEFAULT 'new',
`textCol` varchar(50) DEFAULT NULL,
`insrBLN` tinyint(4) NOT NULL DEFAULT '0',
`itemId` varchar(50) DEFAULT NULL ,
`magic` varchar(50) DEFAULT NULL ,
PRIMARY KEY (`id`),
KEY `ix_magic_composite` (`itemId`,`magic`,`fTS`,`insrBLN`),
KEY `fTS` (`fTS`)
) ENGINE=InnoDB AUTO_INCREMENT=14391289 DEFAULT CHARSET=latin1
EDITAR
Nós refatoramos o código para que a consulta se pareça com:
select *
from tblExample
where 1=1
and status = 'okay'
and textCol > ''
and insrBLN = 1
and (retry = '' or (retry='retry' and retryDT < now() - interval 1 day))
and fTS > '2020-01-01'
order by fTS
limit 50
O problema NÃO foi classificado (também tentei uma ordem de colunas diferente no índice). Parece que ele escolhe o índice certo apenas se eu remover o pedido.
A adição de cláusulas OR torna mais difícil estimar quão bem o índice filtrará. Uma solução é adicionar uma coluna sempre gerada que calcula se os predicados para magic e itemId são satisfeitos e indexar que:
A consulta pode então ser alterada para:
A solução correta é provavelmente corrigir o modelo de dados, mas isso pode não ser possível.
A refatoração não funcionou. Resolvi mudar para o
UNION ALL
que dá os mesmos resultados queuse index
ouforce index
. Eu escolho essa abordagem, pois não exigirá nenhuma alteração de código se o índice for descartado ou renomeado.Há várias coisas que atrapalham a eficiência:
OR
. Mesmo "ref_or_null" está abaixo do ideal. Para começar, você pode evitar ter ambos''
eNULL
para a coluna? Ou seja, limpe os dados e o processamento para usar ou . Dessa forma, você não precisará testar para ambos.''
NULL
(itemId is NULL or itemId = '' or itemId = 'retry')
, eu recomendo escolherNULL
, não''
, para que "ref_or_null_ possa ser usado.UNION
(de preferênciaALL
) é uma solução alternativa paraOR
. Infelizmente, isso fica confuso com mais de umOR
.WHERE
) e não se preocupar com a ordenação (ORDER BY
). Mas quando aWHERE
cláusula fica muito complexa, o Optimizer pode abandonarWHERE
e simplesmente focar emORDER BY
. Sua segunda consulta é um exemplo disso.INDEX(fTS)
paraINDEX(status, insrBLN, fTS)
. Dessa forma, ele pode fazer algumas filtragens antes de classificar; em seguida, termine de filtrar à medida que percorre as linhas.''
ouNULL
, esse índice pode ser alterado paraINDEX(status, insrBLN, magic, itemId, fTS)
.=
testes primeiro noINDEX
, e o "intervalo" e/ouORDER BY
coluna (FTS
) por último. (A ordem das=
colunas não importa.)textcol > ''
) impede que haja um único índice que trate tanto da filtragem quanto da classificação. (Acho que não há solução alternativa.)ix_magic_composite
é abaixo do ideal para todas as consultas apresentadas. O importante é queinsrBLN
(testado com=
) precisa ser antesFTS
(um intervalo), não depois.UNION
, você precisará criar um índice ideal para cada componente doUNION
.