Minimizando leituras indexadas com critérios complexos

12

Estou otimizando um banco de dados Firebird 2.5 de tickets de trabalho. Eles são armazenados em uma tabela declarada como tal:

CREATE TABLE TICKETS (
  TICKET_ID id PRIMARY KEY,
  JOB_ID id,
  ACTION_ID id,
  STATUS str256 DEFAULT 'Pending'
);

Geralmente, quero encontrar o primeiro ticket que não foi processado e está no Pendingstatus.

Meu loop de processamento seria:

  1. Recupere o 1º tíquete onde Pending
  2. Trabalhe com o Ticket.
  3. Atualizar status do ticket => Complete
  4. Repetir.

Nada muito chique. Se eu estiver assistindo o banco de dados enquanto esse loop é executado, vejo o número de leituras indexadas aumentar para cada iteração. O desempenho não parece degradar terrivelmente o que posso dizer, mas a máquina em que estou testando é bastante rápida. No entanto, recebi relatórios de degradação do desempenho ao longo do tempo de alguns dos meus usuários.

Eu tenho um índice Status, mas ainda parece que ele varre a Ticket_Idcoluna a cada iteração. Parece que estou ignorando algo, mas não sei ao certo o que. O número crescente de leituras indexadas é algo como isso esperado ou o índice está se comportando de alguma maneira?

- Edições para comentários -

No Firebird você limita a recuperação de linhas como:

Select First 1
  Job_ID, Ticket_Id
From
  Tickets
Where
  Status = 'Pending'

Então, quando digo "primeiro", estou apenas pedindo um conjunto limitado de registros Status = 'Pending'.

gddc
fonte
O que você quer dizer com "primeiro" em "Recuperar 1º ingresso onde 'Pendente'" ?
usar o seguinte comando
Se "primeiro" significa menor ticket_id, você probbaly precisa um índice em(status, ticket_id)
ypercubeᵀᴹ
E você tem certeza de que a degradação do desempenho é causada por este procedimento e não por outras consultas / declarações?
usar o seguinte comando
@ypercube - Não, não tenho certeza de que é aí que está a degradação do desempenho. É por isso que minha pergunta foi "preciso me preocupar com isso, ou é o comportamento normal de um índice?". É algo que eu notei enquanto monitorava o banco de dados e o considerei inesperado. Eu não esperaria que ele continuasse a varrer as linhas anteriores quando forneça uma cláusula where em uma coluna indexada. FWIW, modificar o índice para incluir ticket_idrealmente teve um desempenho pior do que apenas ter o Status indexado.
Gddc
É id(o tipo de dados) um domínio que você definiu?
a_horse_with_no_name

Respostas:

1

A degradação ao longo do tempo ocorre devido ao aumento do número de itens que estão no status "Concluído". Pense nisso por um segundo - você não terá nenhuma degradação no desempenho ao testar, pois provavelmente possui um pequeno número de linhas com o status "Concluído". Mas na produção, eles podem ter milhões de linhas com o status "Concluído" e esse número aumentará com o tempo. Isso essencialmente torna seu índice no Status cada vez menos útil ao longo do tempo. Como tal, o banco de dados provavelmente decide que, como o Status quase sempre tem o valor 'Concluído', ele apenas varrerá a tabela em vez de usar o índice.

No SQL Server (e talvez em outros RDBMSs?), Isso pode ser resolvido usando índices filtrados. No SQL Server, você adicionaria uma condição WHERE ao final da sua definição de índice para dizer "aplicar este índice apenas a registros com um Status <> 'Concluído'". Portanto, qualquer consulta usando esse predicado provavelmente utilizará o índice na pequena quantidade de registros não configurados como 'Concluído'. No entanto, com base na documentação aqui: http://www.firebirdsql.org/refdocs/langrefupd25-ddl-index.html , não parece que o Firebird suporte índices indexados.

Uma solução alternativa é colocar registros 'Concluídos' em uma tabela ArchiveTickets. Crie uma tabela com a mesma definição exata (embora sem qualquer ID gerado automaticamente) que a tabela Tickets e mantenha linhas entre elas pressionando registros 'Concluídos' para a tabela ArchiveTickets. O índice na tabela de tickets terá um número muito menor de registros e um desempenho muito maior. Isso provavelmente significa que você precisará alterar quaisquer relatórios etc. que façam referência a tickets 'Concluídos' para apontar para a tabela Archive ou executar um UNION nos tickets e ArchiveTickets. Isso terá a vantagem de não apenas ser rápido, mas também significará que você pode criar índices específicos para a tabela ArchiveTickets para melhorar o desempenho de outras consultas (por exemplo:

Você deve se preocupar com isso se a sua produção for para as milhares de linhas. O desempenho diminuirá com o tempo e afetará negativamente a experiência do usuário.

blobbles
fonte
0

Se o desempenho é afetado ou não será uma função do volume de dados e da capacidade da máquina. Dada a capacidade do hardware moderno, é difícil imaginar o volume de vendas de ingressos que não pudesse ser tratado pelo design que você descreve. No entanto, existem mudanças que eu recomendaria para correção e podem melhorar o desempenho como um benefício secundário.

Sua primeira consulta pendente de obtenção não é determinística. Primeiro de acordo com que ordem? Uma tabela SQL não tem ordem intrínseca; o First 1hack está apenas dando a você um primeiro arbitrário. Para torná-lo determinístico, por que não processar trabalhos pendentes na ordem Job_ID?

Se você tiver dois índices {Job_ID} e {Status, Job_ID}, essa consulta retornará uma linha de maneira previsível e eficiente:

Select Job_ID, Ticket_Id
From   Tickets
Where Job_ID = ( 
  select min(Job_ID) from Tickets 
  where Status = 'Pending'
);

Como eu não sou usuário do Firebird, você precisará verificar o plano de consulta, mas deve ser eficiente, porque a subconsulta faz referência apenas ao segundo índice, produz um valor para o primeiro. (Pode haver outros truques de eficiência disponíveis para você. Você pode organizar a tabela física como uma árvore B + ou ter acesso a um row_id oculto, por exemplo.)

A outra alteração que eu faria para corrigir é criar Statusum byte restrito e permitir que o aplicativo forneça a string "Pendente". Isso protegerá contra Statusvalores errados e provavelmente diminuirá o índice na barganha. Algo como:

CREATE TABLE TICKETS (
  TICKET_ID id PRIMARY KEY,
  JOB_ID id,
  ACTION_ID id,
  STATUS char(1) not NULL 
     DEFAULT 'P'
     CHECK( STATUS in ('P', 'C', 'X') ) -- whatever the domain is
);

Obviamente, você pode usar uma exibição (ou talvez uma coluna derivada) para fornecer as seqüências canônicas para Status.

James K. Lowden
fonte