Eu tenho essa tabela:
CREATE TABLE transactions
(
id NUMERIC(20, 0) NOT NULL PRIMARY KEY,
amount NUMERIC(18, 2) DEFAULT NULL NULL,
-- Some 100 columns
customer_msisdn VARCHAR(255) DEFAULT NULL NULL,
customer_email VARCHAR(255) DEFAULT NULL NULL,
payment_date DATETIME2 NOT NULL
);
CREATE NONCLUSTERED INDEX msisdn_idx ON transactions (customer_msisdn, payment_date, id);
CREATE NONCLUSTERED INDEX email_idx ON transactions (customer_email, payment_date, id);
Estou indexando cerca de 1 milhão de linhas por mês. Com muita frequência, preciso selecionar as transações dos últimos 3 meses por customer_msisdn
ou por customer_email
99% do tempo de 50 a 1.000 registros.
Aqui está minha consulta para um pouco mais de visão:
SELECT t.*
FROM transactions t
JOIN (SELECT t.id
FROM transactions t
WITH (FORCESEEK)
WHERE t.customer_email = :customerEmail
AND t.payment_date >= :startDate
AND t.payment_date < :endDate
UNION
SELECT t.id
FROM transactions t
WITH (FORCESEEK)
WHERE t.customer_msisdn = :customerMsisdn
AND t.payment_date >= :startDate
AND t.payment_date < :endDate) AS filtered_transactions
ON t.id= filtered_transactions.id
ORDER BY t.payment_date;
E eu sinto que desde que :endDate
é sempre agora (e quando não, pode tolerar falhas) e :startDate
é sempre três meses atrás, eu tenho algum espaço para melhorias. Aqui está o que eu pensei:
Crie uma exibição indexada com um filtro em payment_date
:
CREATE VIEW [dbo].transactions_iv
WITH SCHEMABINDING AS
SELECT [t].id,
-- All the rows
[t].customer_msisdn,
[t].customer_phone,
[t].payment_date
FROM [dbo].[transactions] [t]
WHERE [t].payment_date >= DATEADD(MONTH, -3, CURRENT_TIMESTAMP);
e meus índices:
CREATE NONCLUSTERED INDEX msisdn_iv_idx ON transactions_iv (customer_msisdn, id);
CREATE NONCLUSTERED INDEX phone_iv_idx ON transactions_iv (customer_phone, id);
e elimine as AND t.payment_date >= :startDate AND t.payment_date < :endDate
cláusulas completamente da consulta. A consulta se torna:
SELECT t.*
FROM transactions_iv t
JOIN (SELECT t.id
FROM transactions_iv t
WITH (FORCESEEK)
WHERE t.customer_email = :customerEmail
UNION
SELECT t.id
FROM transactions_iv t
WITH (FORCESEEK)
WHERE t.customer_msisdn = :customerMsisdn) AS filtered_transactions
ON t.id= filtered_transactions.id
ORDER BY t.payment_date;
Como a exibição tem apenas as transações dos últimos 3 meses, estou assumindo que os índices também terão. Essa suposição está correta? O índice seria atualizado apenas para cobrir os registros dos últimos 3 meses e eu obteria meu aumento de desempenho?
Outra alternativa é:
- Crie outra tabela idêntica,
- Preenchê-lo com um gatilho na tabela principal
- Com um cron job, exclua registros com mais de 3 meses todas as noites.
Como essa opção se compara à anterior?
Sua exibição indexada não pode ser criada como escrita porque não é determinística. As linhas cairiam fora de vista com o passar do tempo.
Sua consulta atual provavelmente está produzindo um plano de execução como:
Deixando de lado a questão de tabelas separadas e visualizações indexadas por um momento, experimente a seguinte reescrita menor (usando índices existentes):
Observe as colunas extras
t.payment_date
noUNION
, e oORDER BY
mudou parafiltered_transactions.payment_date
. Isso não é semanticamente diferente da sua consulta, mas ajudará o otimizador a encontrar o melhor plano.Você deve obter um plano de execução bastante eficiente, como:
O otimizador realmente deve selecionar essa forma de plano (ou talvez uma versão paralela), mas se não, uma ou mais dicas podem ser necessárias. Um exemplo extremo: