2009-05-20 8 views
5

Tengo una tabla en una base de datos de SQL Server 2005 que se usa mucho. Tiene nuestra información de disponibilidad del producto a mano. Recibimos actualizaciones cada hora desde nuestro almacén y durante los últimos años hemos estado ejecutando una rutina que trunca la tabla y actualiza la información. Esto solo lleva unos segundos y no ha sido un problema, hasta ahora. Tenemos mucha más gente que usa nuestros sistemas que consultan esta información ahora y, como resultado, estamos viendo muchos tiempos de espera debido a procesos de bloqueo.¿Cuál es la mejor manera de actualizar datos en una tabla mientras está en uso sin bloquear la tabla?

... así ...

investigamos nuestras opciones y han llegado con una idea para mitigar el problema.

  1. Tendríamos dos tablas. Tabla A (activa) y tabla B (inactiva).
  2. Creamos una vista que apunta a la tabla activa (tabla A).
  3. Todas las cosas que necesitan esta información de tablas (4 objetos) ahora tendrían que pasar por la vista.
  4. La rutina por hora truncaría la tabla inactiva, la actualizaría con la información más reciente y luego actualizaría la vista para apuntar a la tabla inactiva, convirtiéndola en la activa.
  5. Esta rutina determinará qué tabla está activa y básicamente cambiará la vista entre ellos.

¿Qué pasa con esto? ¿Cambiar la vista a mitad de la consulta causará problemas? ¿Puede esto funcionar?

Gracias por su experiencia.

Información adicional

  • la rutina es un paquete SSIS que peforms muchos pasos y, finalmente trunca/actualiza la tabla en cuestión

  • Los procesos de bloqueo son otros dos procedimientos almacenados que consultan este mesa.

+0

si tiene las licencias, dos servidores con equilibrio de carga separada podría proporcionar una alternativa perfecta. Mantiene uno en vivo y actualiza el otro y luego cambia. –

Respuesta

6

¿Ha considerado usar snapshot isolation. Le permitiría comenzar una gran transacción para sus cosas de SSIS y aún leer de la tabla.

Esta solución parece mucho más limpia que cambiar las tablas.

+0

+1 en esto. Es exactamente lo que deberías estar haciendo. Idealmente, obtendrías actualizaciones que no requerían tirar todo, pero como no es así, el truco es comenzar una transacción, eliminar (no truncar) todo y luego cargar todo. En el instante en que ejecute commit, la base de datos hará que todo cambie sin interrupciones. –

+0

¿Por qué no truncar? ¿No sería más lento eliminar ya que cada eliminación se registra? –

+0

Creo que ese es el punto. mediante el uso de aislamiento de instantáneas, es la confirmación que hace que los usuarios vean los datos nuevos. truncado no está registrado y no sé cómo afectaría la transacción, pero probablemente no cómo lo desea. De hecho, apuesto a que el truncado fallaría si la tabla tuviera una transacción pendiente. – Zack

2

creo que esto va en ello el camino equivocado - la actualización de una tabla tiene para bloquearla, aunque se puede limitar a que el bloqueo por página o incluso por fila.

Me gustaría no truncar la tabla y volver a llenarla. Eso siempre va a interferir con los usuarios que intentan leerlo.

Si actualizaste la tabla en lugar de reemplazarla, podrías controlarla de otra forma: los usuarios que leen la imagen no deben bloquear la tabla y pueden salirse con lecturas optimistas.

Pruebe agregar la sugerencia con (nolock) a la lectura de la declaración SQL View. Debería poder obtener grandes volúmenes de usuarios que lean incluso con la tabla que se actualiza regularmente.

+0

Entiendo que SQl Hints, especialmente NOLOCK, son malos por muchas razones. http://tinyurl.com/qwloxh –

+1

No son tan malos como crear copias de los datos y reconstruir vistas para evitar el bloqueo. La mejor forma de establecer el bloqueo es en realidad en las propiedades de la transacción, pero eso no es posible aquí porque está utilizando truncar. También estoy en desacuerdo con los puntos señalados en ese artículo: a veces las sugerencias SQL son la mejor manera de lidiar con los bloqueos, pero debes entender lo que estás haciendo con ellos. – Keith

+0

Ah, y lo hace: http://www.codinghorror.com/blog/archives/001166.html – Keith

1

¿Por qué no utilizar transacciones para actualizar la información en lugar de una operación truncada?

Truncar no está registrado, por lo que no se puede realizar en una transacción.

Si su operación se realiza en una transacción, los usuarios existentes no se verán afectados.

Cómo se hace esto dependerá de cosas como el tamaño de la tabla y qué tan radicalmente cambian los datos. Si me das más detalles, quizás pueda asesorarte más.

+0

The Routine es un paquete SSIS y parece que todo se está ejecutando en una transacción, que es lo que causa el bloqueo. –

2

Personalmente, si siempre va a introducir un tiempo de inactividad para ejecutar un proceso por lotes contra la mesa, creo que debe gestionar la experiencia del usuario en la capa de acceso de negocios/datos. Introduzca un objeto de gestión de tabla que supervise las conexiones a esa tabla y controle el procesamiento por lotes.

Cuando los nuevos datos por lotes están listos, el objeto de administración detiene todas las nuevas consultas (¿tal vez incluso la cola?), Permite que las consultas existentes se completen, ejecuta el lote y luego vuelve a abrir la tabla para consultas. El objeto de gestión puede generar un evento (BatchProcessingEvent) que la capa de la interfaz de usuario puede interpretar para que la gente sepa que la tabla no está disponible actualmente.

Mi $ 0,02,

Nate

+0

Hemos considerado esto y realmente nos gusta esta opción, pero requiere mucho más trabajo para comenzar, y el tiempo es un lujo que lamentablemente no tenemos. Pero estoy de acuerdo en manejar esto independientemente de que el proceso por lotes del servidor sea óptimo. –

0

Hacemos esto en nuestros sistemas de alto uso y no hemos tenido ningún problema. Sin embargo, al igual que con todas las cosas de la base de datos, la única forma de estar seguro de que ayudaría sería realizar el cambio en dev y luego cargarlo. Sin saber qué más puede hacer su paquete SSIS, aún puede causar bloqueos.

1

Una posible solución sería minimizar el tiempo necesario para actualizar la tabla.

Primero crearía una tabla de etapas para descargar los datos del almacén.

Si usted tiene que hacer "inserciones, actualizaciones y eliminaciones" en la mesa final

vamos a suponer que la mesa final se parece a esto:

Table Products: 
    ProductId  int 
    QuantityOnHand Int 

y que necesita para actualizar QuantityOnHand del almacén.

En primer lugar crear una tabla de etapas como:

Table Prodcuts_WareHouse 
    ProductId  int 
    QuantityOnHand Int 

y luego crear una tabla de "acciones" de esta manera:

Table Prodcuts_Actions 
    ProductId  int 
    QuantityOnHand Int 
    Action   Char(1) 

El proceso de actualización debería ser entonces algo como esto:

1.Truncate tabla Prodcuts_WareHouse

2.Trunch table Prodcuts_Actions

3.Fill la tabla Prodcuts_WareHouse con los datos del almacén de

4.Llenar la tabla Prodcuts_Actions con esto:

Inserciones:

INSERT INTO Prodcuts_Actions (ProductId, QuantityOnHand,Action) 
SELECT  SRC.ProductId, SRC.QuantityOnHand, 'I' AS ACTION 
FROM   Prodcuts_WareHouse AS SRC LEFT OUTER JOIN 
         Products AS DEST ON SRC.ProductId = DEST.ProductId 
WHERE  (DEST.ProductId IS NULL) 

Borra

INSERT INTO Prodcuts_Actions (ProductId, QuantityOnHand,Action) 
SELECT  DEST.ProductId, DEST.QuantityOnHand, 'D' AS Action 
FROM   Prodcuts_WareHouse AS SRC RIGHT OUTER JOIN 
         Products AS DEST ON SRC.ProductId = DEST.ProductId 
WHERE  (SRC.ProductId IS NULL) 

Actualizaciones

INSERT INTO Prodcuts_Actions (ProductId, QuantityOnHand,Action) 
SELECT  SRC.ProductId, SRC.QuantityOnHand, 'U' AS Action 
FROM   Prodcuts_WareHouse AS SRC INNER JOIN 
         Products AS DEST ON SRC.ProductId = DEST.ProductId AND SRC.QuantityOnHand <> DEST.QuantityOnHand 

Hasta ahora no se ha cerrado la mesa final.

5.In una transacción de actualización de la mesa final:

BEGIN TRANS 

DELETE Products FROM Products INNER JOIN 
Prodcuts_Actions ON Products.ProductId = Prodcuts_Actions.ProductId 
WHERE  (Prodcuts_Actions.Action = 'D') 

INSERT INTO Prodcuts (ProductId, QuantityOnHand) 
SELECT ProductId, QuantityOnHand FROM Prodcuts_Actions WHERE Action ='I'; 

UPDATE Products SET QuantityOnHand = SRC.QuantityOnHand 
FROM   Products INNER JOIN 
Prodcuts_Actions AS SRC ON Products.ProductId = SRC.ProductId 
WHERE  (SRC.Action = 'U') 

COMMIT TRAN 

Con todo el proceso anterior, se minimiza la cantidad de registros a actualizar al mínimo necesario, por lo que el tiempo de la mesa final será bloqueado durante la actualización.

Incluso puede no utilizar una transacción en el paso final, por lo que entre el comando se lanzará la tabla.

1

Si tiene la versión Enterprise Edition de SQL Server a su disposición, entonces le sugiero que utilice la tecnología de creación de particiones de SQL Server.

Puede hacer que sus datos requeridos actualmente residan dentro de la partición 'Live' y la versión actualizada de los datos en la partición 'Secundaria' (que no está disponible para consultar sino para administrar datos).

Una vez que los datos se han importado en la parición 'Secundaria', puede CAMBIAR instantáneamente la partición 'EN VIVO' OUT y la partición 'Secundaria' EN, incurriendo en tiempo de inactividad cero y sin bloqueo.

Una vez que haya realizado el cambio, puede truncar los datos que ya no son necesarios sin que los adversos afecten a los usuarios de los datos recién actualizados (anteriormente la partición secundaria).

Cada vez que necesita hacer un trabajo de importación, simplemente repite/reversa el proceso.

Para más información acerca de SQL Server Partición ver:

http://msdn.microsoft.com/en-us/library/ms345146(SQL.90).aspx

O simplemente me puede pedir :-)

EDIT:

En una nota lateral, con el fin para abordar cualquier problema de bloqueo, puede usar la tecnología de control de versiones de SQL Server.

http://msdn.microsoft.com/en-us/library/ms345124(SQL.90).aspx

+0

Esta es la idea de lo que queríamos lograr con una vista, pero solo tenemos la edición estándar de SQL Server. –

2

acaba de leer está utilizando SSIS

podría utilizar el componente TableDiference de: http://www.sqlbi.eu/Home/tabid/36/ctl/Details/mid/374/ItemID/0/Default.aspx

alt text http://www.sqlbi.eu/Portals/0/Articles/Table%20Difference%20Images/DataFlowSimple.png

esta manera se puede aplicar los cambios a la tabla, uno por UNO, pero por supuesto, esto será mucho más lento y, dependiendo del tamaño de la tabla, se necesitará más RAM en el servidor, pero el problema de bloqueo se corregirá por completo.

0

Si la tabla no es muy grande, puede almacenar en caché los datos en su aplicación durante un breve período de tiempo. Puede que no elimine por completo el bloqueo, pero reduciría las posibilidades de que se consulte la tabla cuando se produzca una actualización.

0

Quizás tenga sentido hacer un análisis de los procesos que están bloqueando, ya que parecen ser la parte de su paisaje que ha cambiado. Solo se necesita una consulta mal escrita para crear los bloques que estás viendo. Salvo una consulta mal escrita, tal vez la tabla necesite uno o más índices de cobertura para acelerar esas consultas y volver a ponerlo en su camino sin tener que volver a diseñar su código que ya funciona.

Espero que esto ayude,

Bill

Cuestiones relacionadas