Minimización de lecturas indexadas con criterios complejos

12

Estoy optimizando una base de datos Firebird 2.5 de tickets de trabajo. Se almacenan en una tabla declarada como tal:

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

Generalmente quiero encontrar el primer ticket que no se ha procesado y está en Pendingestado.

Mi ciclo de procesamiento sería:

  1. Recupere el 1er boleto donde Pending
  2. Trabaja con Ticket.
  3. Actualizar estado del ticket => Complete
  4. Repetir.

Nada muy elegante. Si estoy viendo la base de datos mientras se ejecuta este ciclo, veo el número de lecturas indexadas que sube para cada iteración. El rendimiento no parece degradarse terriblemente, puedo decirlo, pero la máquina en la que estoy probando es bastante rápida. Sin embargo, he recibido informes de degradación del rendimiento con el tiempo de algunos de mis usuarios.

Tengo un índice Status, pero todavía parece que escanea la Ticket_Idcolumna cada iteración. Parece que estoy pasando por alto algo, pero no estoy seguro de qué. ¿Se espera el número creciente de lecturas indexadas para algo como esto, o el índice se está comportando de alguna manera?

- Ediciones para comentarios -

En Firebird limitas la recuperación de filas como:

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

Entonces, cuando digo "primero", solo le pido un conjunto de registros limitado donde Status = 'Pending'.

gddc
fuente
¿Qué quiere decir con "primero" en "Recuperar el 1er ticket donde 'Pendiente'" ?
ypercubeᵀᴹ
Si "primero" significa el más pequeño ticket_id, probablemente necesite un índice en(status, ticket_id)
ypercubeᵀᴹ
¿Y qué tan seguro está de que la degradación del rendimiento es causada por este procedimiento y no por otras consultas / declaraciones?
ypercubeᵀᴹ
@ypercube: no, no estoy seguro de que sea allí donde está la degradación del rendimiento. Es por eso que mi pregunta era "¿Necesito preocuparme por esto, o es el comportamiento normal de un índice?". Es algo que noté al monitorear la base de datos, y lo consideré inesperado. No esperaría que continúe escaneando las filas anteriores cuando proporciono una cláusula where en una columna indexada. FWIW, la modificación del índice para incluir en ticket_idrealidad se desempeñó peor que simplemente tener el estado indexado.
gddc
¿Es id(el tipo de datos) un dominio que definió?
a_horse_with_no_name

Respuestas:

1

La degradación con el tiempo ocurre debido al mayor número de elementos que están en el estado "Completo". Piense en esto por un segundo: no obtendrá ninguna degradación del rendimiento durante la prueba, ya que probablemente tenga un pequeño número de filas con el estado "Completo". Pero en producción, pueden tener millones de filas con el estado "Completo" y este número aumentará con el tiempo. Esto, esencialmente, hace que su índice de estado sea cada vez menos útil con el tiempo. Como tal, la base de datos probablemente solo decida que debido a que Status casi siempre tiene el valor 'Completo', simplemente escaneará la tabla en lugar de usar el índice.

En SQL Server (¿y quizás en otros RDBMS?), Esto se puede solucionar utilizando índices filtrados. En SQL Server, agregaría una condición WHERE al final de su definición de índice para decir "aplique este índice solo a registros con un Estado <> 'Completo'". Entonces, cualquier consulta que use este predicado probablemente usará el índice en la pequeña cantidad de registros no configurados en 'Completo'. Sin embargo, según la documentación aquí: http://www.firebirdsql.org/refdocs/langrefupd25-ddl-index.html , no parece que Firebird admita índices filtrados.

Una solución alternativa es colocar registros 'completos' en una tabla ArchiveTickets. Cree una tabla con la misma definición exacta (aunque sin ningún ID generado automáticamente) como su tabla de Tickets y mantenga filas entre ellos empujando los registros 'Completos' a la tabla ArchiveTickets. El índice en su tabla de tickets tendrá un número mucho menor de registros y tendrá un rendimiento mucho mayor. Esto probablemente significará que tendrá que cambiar cualquier informe, etc., que haga referencia a tickets 'Completos' para apuntar a la tabla Archive o realizar una UNION en ambos Tickets y ArchiveTickets. Esto tendrá la ventaja de no solo ser rápido, sino que también significará que puede crear índices específicos para la tabla ArchiveTickets para que funcione mejor para otras consultas (por ejemplo:

Debería preocuparse por esto si su producción va a ir a miles de filas. El rendimiento se degradará con el tiempo e impactará negativamente en su experiencia de usuario.

blobbles
fuente
0

Que el rendimiento se vea afectado o no dependerá del volumen de datos y la capacidad de la máquina. Dada la capacidad del hardware moderno, es difícil imaginar el volumen de ventas de boletos que no podría ser manejado por el diseño que describe. Sin embargo, hay cambios que recomendaría para la corrección, y podrían mejorar el rendimiento como un beneficio secundario.

Su primera consulta pendiente no es determinista. Primero de acuerdo a qué orden? Una tabla SQL no tiene un orden intrínseco; el First 1truco es sólo que dando algunos arbitraria primera. Para hacerlo determinista, ¿por qué no procesar los trabajos pendientes en el orden Job_ID?

Si tiene dos índices {Job_ID} y {Status, Job_ID}, esta consulta devolverá una fila de manera predecible y eficiente:

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

No soy un usuario de Firebird, por lo que tendrá que verificar el plan de consulta, pero debería ser eficiente porque la subconsulta solo hace referencia al segundo índice, produce un valor para el primero. (Puede haber otros trucos de eficiencia disponibles para usted. Es posible que pueda organizar la tabla física como un árbol B + o tener acceso a un row_id oculto, por ejemplo).

El otro cambio que haría para la corrección es hacer Statusun solo byte restringido y dejar que la aplicación suministre la cadena "Pendiente". Eso protegerá contra Statusvalores erróneos y probablemente hará que el índice sea más pequeño en el negocio. 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
);

Por supuesto, puede usar una vista (o tal vez una columna derivada) para proporcionar las cadenas canónicas para el Estado.

James K. Lowden
fuente