none
Consulta Tablas / Indices y Lockeos RRS feed

  • Pregunta

  • Hola,

    Les comento que tengo un problema de timeouts de accesos a una tabla concurrentemente.

    Por un lado tenemos un proceso que inserta masivamente transacciones con una determinada fecha/hora (a razon de 200 por segundo) desde un WS (INSERT).

    Por otro lado tenemos processos concurrentes que acceden a esa tabla tomando transacciones de hace mas de x cantidad de tiempo (trx insertadas hace mas de 10 minutos) para cambiarlas de estado como procesadas (SELECT + UPDATE)

    La optimizacion de los indices esta bien hecha (por un DBA).

     

    El problema que surge es que concurrentemente se arrojan problemas de Timeouts continuamente cuando en realidad los ambos procesos trabajan en la misma tabla pero con registros totalmente diferentes.

    Asumo que el problema es por un bloqueo de indices o paginas.

    Si el segundo proceso corre sin el primero activo (INSERT) todo funciona perfectamente.

     

    Espero sus comentarios y sugerencias ....

     

    Saludos Cordiales.

     

     

     

    martes, 3 de enero de 2012 17:31

Respuestas

  • Hola.

    El empleo de un campo uniqueidentifier no es necesario, puedes realizar el cambio en todas las tablas que lo empleen, no es una operación tan compleja. Otra cosa es que ahora ya no se quiera prescindir de él, por el impacto que pudiera tener y el coste de las modificaciones y demás. Yo lo haría, por muy alto que pueda parecer el coste porque compromete la escalabilidad de la solución (vamos, que ya no funciona bien). Pero eso ya es decisión tuya y conoces mejor que nadie tu entorno como para poder decidir sobre ello.

    Lo que sí creo que no ofrece dudas, ya que es un cuestión de concepto, son los otros dos puntos que mencionas. Necesitas de un índice clustered en tus tablas (salvo contadísimas excepciones, y tu caso no es una de ellas, esto es así). Que no sea el campo UniqueIdentifier, porque como bien dices, el rendimiento iría a peor, pero crea uno, aunque sea el que te sugerí (Status, ProcessId), o este mismo incluyendo la fecha (el que ya tienes como índice nonclustered).

    Por otra parte, aunque no lo dices expresamente, por si acaso, con la frecuencia que se precise hay que reindexar o reorganizar. De otro modo el rendimiento se iría a pique. Es una cuestión de mantenimiento básico, como hacer backups, chequeos de integridad, etc. Es decir, no puedes no hacerlo. De hecho, como primer paso, reindexa tus tablas una vez cada noche por lo menos. Sólo con eso ya ganarás en rendimiento (y si no lo haces habitualmente, ganarás mucho).

    Luego ya habría que evaluar qué cambio estructural hacer. La opción que yo te recomiendo es prescindir del uniqueidentifier por completo. Si esa opción no la contemplas, podrías optar por crear un identity a tu tabla, definirlo como índice clustered, basar el código y la gestión en ese identity (en la línea de lo que te comenté en el anterior post), aunque conservando el campo actual, que quedaría como un atributo más. Sería un paso transitoria en tanto en cuanto no puedas eliminar el campo.

    Me gustaría ver el plan de ejecución en modo gráfico del proceso de update, no sé si podrías facilitárnoslo. Puede ser muy revelador.


    Alberto López Grande
    SQL Server MVP
    Visita mi blog en http://qwalgrande.blogspot.es/ Sígueme en twitter en http://twitter.com/qwalgrande

    miércoles, 4 de enero de 2012 10:09
    Moderador

Todas las respuestas

  • Hola.

    Me ha hecho gracia eso de que "La optimización está bien hecha (por un DBA)". Curioso comentario, ni que fuéramos notarios, jeje.

    En cuanto a tu cuestión, sin ver el código, yo intentaría mejorar el proceso de update. Bloqueos han de existir, pero si está transaccionalmente bien hecho, podrás reducirlos mucho. En ocasiones, el problema de un update es dar con el registro a modificar (más que la modificación en sí). Si nos compartes el código de dicho update seguramente podamos ayudarte. 

    Pásanos también script de creación de las tablas y de todos sus índices (sin que eso suponga que dude de la validez de la indexación existente).


    Alberto López Grande
    SQL Server MVP
    Visita mi blog en http://qwalgrande.blogspot.es/ Sígueme en twitter en http://twitter.com/qwalgrande

    martes, 3 de enero de 2012 17:44
    Moderador
  • Perdon yo te envie informacion... Te ha llegado?

    No la veo en el foro ... :(

     

    Saludos

    martes, 3 de enero de 2012 18:27
  • Como bien indica Alberto, sin conocer la estructura de tu tabla y el codigo de los procesos o procedimientos que actuan sobre ella, dificil es darte una respuesta certera.

    Pero a bote pronto te comento algunas tecnicas que se me pasan por la cabeza, a las cuales te añado un enlace par que amplies conocimientos: (pero poco podemos aconsejarte sobre ellas, ya que tampoco conocemos las reglas y logica del negocio que se aplica sobre tu aplicación y base de datos, y mas concretamente la de tus procesos sobre la tabla que mencionas)

    Desde SQL Server 2005 se presenta un nuevo nivel de aislamiento de instantáneas (snapshot isolation level)que mejora la simultaneidad de aplicaciones OLTP. En versiones anteriores de SQL Server, la simultaneidad se basaba únicamente en bloqueos, que ocasionaban problemas de bloqueo y estancamiento en algunas aplicaciones. Por el contrario, el aislamiento de instantáneas depende de las mejoras realizadas en la versión de fila y se ha diseñado para mejorar el rendimiento al evitar situaciones de bloqueo de lectura-escritura. http://msdn.microsoft.com/es-es/library/ms173763.aspx y mira este otro enlace: http://msdn.microsoft.com/es-es/library/tcbchxcb(v=vs.80).aspx

    Tambien tienes:

    Lock Escalation: La extensión de bloqueo es el proceso de convertir muchos bloqueos concretos en menos bloqueos más generales, lo que reduce la sobrecarga del sistema al tiempo que aumenta la probabilidad de contención de simultaneidad. Te dejo un enlace para que prfundices: http://msdn.microsoft.com/es-es/library/ms184286.aspx

    tambien:


    READPAST: Especifica que el Motor de base de datos no lea las filas bloqueadas por otras transacciones. Cuando se especifica READPAST, se omiten los bloqueos en el nivel de fila. Es decir, Motor de base de datos omite las filas en lugar de bloquear la transacción actual hasta que se liberen los bloqueos. http://msdn.microsoft.com/es-ec/library/ms187373.aspx

    Y otras formas, pero danos mas información para tratar tu problema...


     Norman M. Pardell 

    ||Microsoft Certified IT Professional|| Database Administrator. Database Developer. SQL Server 2008



    • Editado Normannp martes, 3 de enero de 2012 20:35
    martes, 3 de enero de 2012 20:25
  • Hola, les paso la tabla y el proceso que toma registros

     

    CREATE PROCEDURE [dbo].[_TEST] ( @Top int )   
    AS 
    BEGIN   
     DECLARE   
      @ERStatusPendingToCoordinate tinyint,   
      @ERStatusCoordinating tinyint,   
      @DateTimeToImages Datetime,   
      @DateTimeToCoordinate Datetime,   
      @ProcessID Smallint,   
      @UTCDate DateTime;   

     SET @ERStatusPendingToCoordinate = 1;
     SET @ERStatusCoordinating   = 4;

     SET @ProcessID = @@SPID;   
     SET @UTCDate = GETUTCDate();   
     
     declare @Candidates table(EnforcementRecordID uniqueidentifier unique); 
     
     -- Seleccionamos una cantidad de registros que están disponibles para ser procesados   
     insert into @Candidates(EnforcementRecordID) 
     SELECT TOP (@Top) ER.EnforcementRecordID 
     FROM EnforcementRecords ER WITH (NOLOCK)   
     WHERE   
      ER.[Status] = @ERStatusPendingToCoordinate   
      AND ER.ProcessID IS NULL   
     ORDER BY   
     ER.[Datetime] ASC;   
     
     SET @UTCDate = GETUTCDate();   
       
     -- Ahora marcamos los registros que siguen disponibles para ser procesados, para indicar que están en procesamiento.   
     UPDATE EnforcementRecords  
     SET   
      ProcessID = @ProcessID,   
      StartProcessingDateTime = @UTCDate,   
      [Status]= @ERStatusCoordinating   
     OUTPUT    
      deleted.EnforcementRecordID
     FROM EnforcementRecords ER 
      INNER JOIN @Candidates C ON ER.EnforcementRecordID = C.EnforcementRecordID   
     WHERE ER.ProcessID IS NULL;   

    END

    ----------------------------------------------------------------------------------------------------

    CREATE TABLE [dbo].[EnforcementRecords](
     [EnforcementRecordID] [uniqueidentifier] NOT NULL,
     [DateTime] [datetime] NOT NULL,
     [DateTimeCreated] [datetime] NOT NULL,
     [Status] [tinyint] NOT NULL,
     [ProcessID] [smallint] NULL,
     [StartProcessingDateTime] [datetime] NULL,
     CONSTRAINT [PK_EnforcementRecords_ERID] PRIMARY KEY NONCLUSTERED
    (
     [EnforcementRecordID] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [DefaultIndexes]
    ) ON [TransactionData]

    GO

    CREATE NONCLUSTERED INDEX [IX_ProcessID_Status_DT_INC_ER] ON [dbo].[EnforcementRecords]
    (
     [ProcessID] ASC,
     [Status] ASC,
     [DateTime] ASC
    )
    INCLUDE ( [EnforcementRecordID]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [DefaultIndexes]
    GO

     

     

    Por otro lado hay un proceso que continuamente inserta registros en forma masiva pero siempre con fecha posterior a las que toma el SP.

     

    Gracias

    martes, 3 de enero de 2012 20:30
  • El otro proceso que iserta registros de forma masiva, como lo hace?, no usará BCP (http://msdn.microsoft.com/es-es/library/ms162802.aspx) o por bulk insert (http://msdn.microsoft.com/es-es/library/ms188365.aspx) --> este proceso pude ser la causa de tu problema

    Que concurrencia tiene la tabla, ha cierto numero de registros bloqueados la tabla no admitirá mas accesos... 


     Norman M. Pardell 

    ||Microsoft Certified IT Professional|| Database Administrator. Database Developer. SQL Server 2008


    • Editado Normannp martes, 3 de enero de 2012 20:55
    martes, 3 de enero de 2012 20:51
  • No, simplemente es un script que inserta masivamente 200 transacciones x segundo.

    Vuelvo a preguntar ... no sera un problema con lockeo de indices?

     

    Saludos y Gracias

    martes, 3 de enero de 2012 20:54
  • Por otro lado tenemos processos concurrentes que acceden a esa tabla tomando transacciones de hace mas de x cantidad de tiempo (trx insertadas hace mas de 10 minutos) para cambiarlas de estado como procesadas (SELECT + UPDATE)

     

    Usa Índices filtrados, es una característica nueva en SQL Server 2008.  Índices filtrados se utiliza para indexar una parte de las filas de una tabla que significa que se aplica el filtro de índice que mejora el rendimiento de las consultas, reducir los costos de mantenimiento del índice, y reducir los costos de almacenamiento del índice en comparación con los índices de tabla completa. http://msdn.microsoft.com/es-es/library/cc280372.aspx


     Norman M. Pardell 

    ||Microsoft Certified IT Professional|| Database Administrator. Database Developer. SQL Server 2008

    martes, 3 de enero de 2012 21:04
  • No eso no es factible. El indice debe tener ciertas caracteristicas.

    Aqui las transacciones se van insertando y los ultimos DateTime van cambiando permanentemente.

     

    Gracias y Saludos

    martes, 3 de enero de 2012 21:08
  • Si, perdona, no es un opción que te sirva.

    valora poner la opción: SET READ_COMMITTED_SNAPSHOT ON    
    La principal ventaja de éste método, es que al utilizar el nivel de aislamiento READ COMMITED (que es justo el modo de aislamiento por defecto en SQL Server), estaremos aprovechando las ventajas del versionado de filas (row versioning). Por ello, en muchos casos, simplemente será necesario activar la opción de base de datos READ_COMMITTED_SNAPSHOT (poner a ON) y disfrutar del versionado de filas (row versioning), sin necesidad de tocar el código de las transacciones.

     Se trata de una mezcla entre los modos de aislamiento READ COMMITTED y SNAPSHOT. Tiene la ventaja de utilizar el versionado de filas (row versioning), de tal modo, que las lecturas no son bloqueadas por las escrituras (y viceversa). Sin embargo, pueden producirse lecturas no repetibles (non repeatable reads) y lecturas fantasma (phantom reads). Es también importante tener en cuenta que en este modo de aislamiento, no se produce error en caso de conflictos de actualización (update conflicts).


     

     Norman M. Pardell 

    ||Microsoft Certified IT Professional|| Database Administrator. Database Developer. SQL Server 2008



    • Editado Normannp martes, 3 de enero de 2012 21:44
    martes, 3 de enero de 2012 21:16
  • Hola.

    Una pena, ese DBA nos ha dejado por el suelo a todo el gremio... Me explico.

    Tienes graves errores de diseño. Tu tabla carece de índice clustered. Y además, usas como clave primaria un campo de 32 posiciones no creciente. Te recomiendo que sustituyas el uniqueidentifier por un campo entero suficientemente grande como para albergar los registros de la tabla (seguramente te valdrá un tipo int), que esté definido como identity. Así, con el campo "EnforcementRecords" definido como entero y clave primaria (clustered), el rendimiento de tu consulta aumentará de forma exponencial.

    Pero adicionalmente, puedes optimizar también el procedimiento almacenado, cambiando el código y añadiendo indexación a medida. Para empezar, si no te es imprescindible, quita el OUTPUT. La consulta inicial tampoco es necesaria si no precisas que se actualicen determinados registros, sino cualquiera que cumplan los criterios de estar en el estado adecuado, puedes realizar el update directamente, así:

    set rowcount @Top
    
    UPDATE ER SET    
      ProcessID = @ProcessID,    
      StartProcessingDateTime = @UTCDate,    
      [Status]= @ERStatusCoordinating    
    FROM EnforcementRecords ER  
    WHERE 
      ER.ProcessID IS NULL and ER.Status = @ERStatusPendingToCoordinate
    
    set rowcount 0
    


    Si el hecho de que se modifiquen antes los registros más antiguos es un requisito, habría que hacer una subconsulta. En ese caso, podrías hacerlo así, por ejemplo:

    ;with CTE as (select (top @Top) EnforcementRecordId 
    from EnforcementRecords
    where ER.ProcessID IS NULL and ER.Status = @ERStatusPendingToCoordinate
    order by EnforcementRecordId)
    
    UPDATE ER SET    
      ProcessID = @ProcessID,    
      StartProcessingDateTime = @UTCDate,    
      [Status]= @ERStatusCoordinating    
    FROM EnforcementRecords ER inner join CTE on
    ER.EnforcementRecordId = CTE.EnforcementRecordId
    
    


    Ordeno por el ID porque al ser siempre creciente, no hace falta ordenar por la fecha. En ambos casos, se precisaría un índice que incluyera los campos Status y ProcessID:

    create nonclustered index IX_EnforcementRecords_A1 on EnforcementRecords (Status, ProcessId)
    

    Realiza estos cambios y prueba nuevamente, puede que no sea suficiente. Se puede afinar algo más si fuera necesario.


    Alberto López Grande
    SQL Server MVP
    Visita mi blog en http://qwalgrande.blogspot.es/ Sígueme en twitter en http://twitter.com/qwalgrande

    • Propuesto como respuesta Normannp martes, 3 de enero de 2012 22:17
    martes, 3 de enero de 2012 22:02
    Moderador
  • Hola,

    Lo del indice cluster es relativo.

    El IDENTIFIER es NECESARIO (Ya que tiene FK con otras tablas), por lo tanto tu solucion seria poner un IDENTITY BIGINT con CLUSTER.

    Eso esta bueno, lo que me falto aclarar es que despues de 2 dias esta tabla se va purgando en forma de fecha mas baja, con lo cual el CLUSTER obliga a la reorganizacion. Es bueno esto? Conviene el Cluster ya que obliga al reordenamiento fisico?... Nosotros lo hemos probado (sin el identity) y la performance es muy pobre debido a la aletoriadad de IDENTIFIER. 

    miércoles, 4 de enero de 2012 9:24
  • Hola.

    El empleo de un campo uniqueidentifier no es necesario, puedes realizar el cambio en todas las tablas que lo empleen, no es una operación tan compleja. Otra cosa es que ahora ya no se quiera prescindir de él, por el impacto que pudiera tener y el coste de las modificaciones y demás. Yo lo haría, por muy alto que pueda parecer el coste porque compromete la escalabilidad de la solución (vamos, que ya no funciona bien). Pero eso ya es decisión tuya y conoces mejor que nadie tu entorno como para poder decidir sobre ello.

    Lo que sí creo que no ofrece dudas, ya que es un cuestión de concepto, son los otros dos puntos que mencionas. Necesitas de un índice clustered en tus tablas (salvo contadísimas excepciones, y tu caso no es una de ellas, esto es así). Que no sea el campo UniqueIdentifier, porque como bien dices, el rendimiento iría a peor, pero crea uno, aunque sea el que te sugerí (Status, ProcessId), o este mismo incluyendo la fecha (el que ya tienes como índice nonclustered).

    Por otra parte, aunque no lo dices expresamente, por si acaso, con la frecuencia que se precise hay que reindexar o reorganizar. De otro modo el rendimiento se iría a pique. Es una cuestión de mantenimiento básico, como hacer backups, chequeos de integridad, etc. Es decir, no puedes no hacerlo. De hecho, como primer paso, reindexa tus tablas una vez cada noche por lo menos. Sólo con eso ya ganarás en rendimiento (y si no lo haces habitualmente, ganarás mucho).

    Luego ya habría que evaluar qué cambio estructural hacer. La opción que yo te recomiendo es prescindir del uniqueidentifier por completo. Si esa opción no la contemplas, podrías optar por crear un identity a tu tabla, definirlo como índice clustered, basar el código y la gestión en ese identity (en la línea de lo que te comenté en el anterior post), aunque conservando el campo actual, que quedaría como un atributo más. Sería un paso transitoria en tanto en cuanto no puedas eliminar el campo.

    Me gustaría ver el plan de ejecución en modo gráfico del proceso de update, no sé si podrías facilitárnoslo. Puede ser muy revelador.


    Alberto López Grande
    SQL Server MVP
    Visita mi blog en http://qwalgrande.blogspot.es/ Sígueme en twitter en http://twitter.com/qwalgrande

    miércoles, 4 de enero de 2012 10:09
    Moderador
  • Con permiso de Alberto, te comento:

    El hecho de que existan relaciones entre tablas no implica que necesites un campo GUID como clave primaria, ya que la única obligatoriedad es que sea único, y probablemente la forma más sencillo de conseguirlo sea con un tipo de datos entero (ya sea INT o BIGINT, dependerá de los requisitos que tengas, aunque como Alberto supongo que con un INT te sería más que suficiente) activando la propiedad IDENTITY.

    Por otro lado, no sé cómo estaréis haciendo ese purgado automático. Existen varias técnicas para hacerlo de forma eficiente: por bloques (en vez de borrar todos los registros de golpe, ir haciéndolo en conjuntos de 1000 o 10000 o lo que veais que funciona mejor) o usando el particionamiento (hay muchos ejemplos por la web, por ejemplo este mismo http://weblogs.sqlteam.com/dang/archive/2008/09/14/Automating-Sliding-Window-Maintenance.aspx)

    Y por último, no te entiendo muy bien cuando dices que el cluster obliga a la reorganización... tener un índice agrupado es una buena práctica en la gran mayoría de los casos, sólo en contadísimas ocasiones podría ser mejor tener una tabla sin él, y no creo que sea tu situación. Tener los datos ordenados (lógicamente) ayuda a SQL Server a encontrarlos más rápidamente, siempre y cuando la selección de dicho orden sea el correcto; por ejemplo, tener un índice agrupado sobre una columna UNIQUEIDENTIFIER es una de las peores cosas que puedes hacer en primer lugar por el tamaño del campo y en segundo lugar (y más importante) porque los valores serán totalmente aleatorios (a menos que exijas que sean crecientes) implicando una fragmentación del índice brutal.

    miércoles, 4 de enero de 2012 10:12
  • Hola, gracias por la aclaraciones.

    Lo que me falto aclarar es que despues de 2 dias esta tabla se va purgando en forma de fecha mas baja, con lo cual el CLUSTER obliga a la reorganizacion. Es bueno esto? Conviene el Cluster ya que obliga al reordenamiento fisico?...

    En resumen.... aclaro el proceso en si

    1- Se insertan transacciones masivamente en 3 tablas (1 es la que menciono yo)

    2- Se marcan esas transacciones como procesadas y se mandan a una 4ta tabla para que siga su ciclo (otros procesos trabajan con ella) De aqui la necesidad del OUTPUT.

    3- Luego de un determinado tiempo configurable (ej 2 dias) estas Trx se envian a un DWH y se Depuran.

     

    Aclaro algo

    Es necesario ordenar por fecha ya que no es Fecha de Creacion, sino una fecha de Transaccion que puede caer aleatoriamente con segundos de diferencia antes (EL ID no es necesariamente el mismo ORDEN).

    Por ahora estoy un poco confundido... me gustaria que me resuman claramente cual es la alternativa que pueda emplear (indices, qrys, etc) en una sola respuesta.

     

    Muchas Gracias

     

    miércoles, 4 de enero de 2012 11:02
  • Gracias! Buenisima el purgado... lo voy a empezar a leer.

    Cuando hablo de reorganizacion pienso que como los UNIQUEIDENTIFIER no son correlativos y el orden es fisico obliga a la repaginacion de la tabla (fragmentacion). Por eso trabajamos con NONCLUSTER y nos trajo mejores beneficios.

    Agradezco tu respuesta.

     

    Saludos

    miércoles, 4 de enero de 2012 11:05
  • Pero eso es por lo que te comentaba antes: tener un índice agrupado sobre un campo UNIQUEIDENTIFIER es probablemente la peor elección que puedas tener. A menos que especifiques que el orden sea siempre creciente (con la funcion NEWSEQUENTIALID, http://msdn.microsoft.com/es-es/library/ms189786.aspx), la fragmentación será tremenda y por tanto el rendimiento será pésimo (como tú mismo has podido comprobar). Pero eso no es culpa de tener un índice agrupado, sino de haberlo escogido mal. Cambia a INT o BIGINT e IDENTITY y comprobarás la diferencia.

    Por otra parte, el orden de las páginas de la tabla no es físico, sino lógico. Y en cualquier caso, el hecho de que no exista un índice agrupado en la tabla y por tanto no haga falta una reorganización no es en absoluto bueno, sino más bien al contrario: al no haber un criterio por el que ordenar los datos, estos estarán siempre desordenados, con lo que la fragmentación será también muy alta y, lo que es aún peor, no habrá forma de evitarla.

    miércoles, 4 de enero de 2012 11:40
  • Ok, lo voy a intentar.

    Es bueno que se sepa que el IDENTIFIER no es decision propia sino de datos que viene de una fuente externa como identificadores unicos de registros... es por eso que fue usada..

    El cambio es grande, espero que los consejos sean utiles para mejorar la performance...

    RECORDAR QUE ESTA SITUACION SOLO SE DA SI Y SOLO SI se insertan masivamente datos y por otro lado hay procesos que estan actualizando OTROS DATOS de la misma tabla a la vez.

    Gracias!

    miércoles, 4 de enero de 2012 16:59