Pregunta

Estamos viendo algunas perniciosa, pero raras, las condiciones de interbloqueo en el Desbordamiento de la Pila de SQL Server 2005 de la base de datos.

Os adjunto el analizador, establecer un seguimiento del perfil de uso de este excelente artículo sobre la solución de problemas de interbloqueos, y capturó un montón de ejemplos.Lo extraño es que los interbloqueos escribir es siempre el mismo:

UPDATE [dbo].[Posts]
SET [AnswerCount] = @p1, [LastActivityDate] = @p2, [LastActivityUserId] = @p3
WHERE [Id] = @p0

Otro de los interbloqueos declaración varía, pero por lo general es una especie de trivial, simple leer de los puestos de la tabla.Este siempre se mató en el interbloqueo.He aquí un ejemplo

SELECT
[t0].[Id], [t0].[PostTypeId], [t0].[Score], [t0].[Views], [t0].[AnswerCount], 
[t0].[AcceptedAnswerId], [t0].[IsLocked], [t0].[IsLockedEdit], [t0].[ParentId], 
[t0].[CurrentRevisionId], [t0].[FirstRevisionId], [t0].[LockedReason],
[t0].[LastActivityDate], [t0].[LastActivityUserId]
FROM [dbo].[Posts] AS [t0]
WHERE [t0].[ParentId] = @p0

Que quede claro, no estamos viendo escribir / escribir interbloqueos, pero de lectura / escritura.

Tenemos una mezcla de LINQ y consultas SQL con parámetros en el momento.Hemos añadido with (nolock) para todas las consultas SQL.Esto puede haber ayudado a algunos.También hemos tenido una sola (muy) mal escrito insignia de la consulta que me fijo de ayer, que fue tomando más de 20 segundos para que se ejecute cada hora, y se ejecuta cada minuto en la parte superior de eso.Estaba esperando este fue el origen de algunos de los problemas de bloqueo!

Por desgracia, tuve otro error de interbloqueo hace alrededor de 2 horas.Mismos síntomas exactos, en el mismo exacto culpable de escribir.

El verdaderamente extraño es que el bloqueo de escritura instrucción SQL que ves arriba es parte de un código específico de la ruta.Es sólo se ejecuta cuando una nueva respuesta se agrega a una pregunta: las actualizaciones de los padres a la pregunta con la respuesta del recuento y de la última fecha/usuario.Esto es, obviamente, no común en relación a la enorme cantidad de lecturas que estamos haciendo!Como lo que yo puedo decir, no estamos haciendo un gran número de escrituras de cualquier parte de la aplicación.

Me doy cuenta de que NOLOCK es una especie de un martillo gigante, pero la mayoría de las consultas que nos corremos de aquí, no es necesario ser precisos.Cuidará si su perfil de usuario es de unos pocos segundos de la fecha?

El uso de NOLOCK con Linq es un poco más difícil, ya que Scott Hanselman discute aquí.

Estamos coqueteando con la idea de utilizar

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

en la base de datos de contexto, de modo que todas nuestras consultas LINQ tiene este conjunto.Sin eso, tendríamos que envuelva cada LINQ llamada que hacer (bueno, la simple lectura, que es la gran mayoría de ellos) en una de las 3-4 de la línea de transacción bloque de código, que es feo.

Supongo que estoy un poco frustrado de que trivial lee en SQL 2005 punto muerto en que se escribe.Pude ver de escritura/escritura interbloqueos ser un gran problema, pero lee? No estamos ejecutando un sitio de banca aquí, no necesitamos una precisión perfecta en todo momento.

Ideas?Los pensamientos?


Estás instancias de un nuevo LINQ to SQL objeto DataContext para cada operación o son tal vez compartir el mismo contexto estático para todas tus llamadas?

Jeremy, estamos compartiendo una estática datacontext en el Controlador de base para la mayor parte:

private DBContext _db;
/// <summary>
/// Gets the DataContext to be used by a Request's controllers.
/// </summary>
public DBContext DB
{
    get
    {
        if (_db == null)
        {
            _db = new DBContext() { SessionName = GetType().Name };
            //_db.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
        }
        return _db;
    }
}

¿Te recomendamos crear un nuevo contexto para cada Controlador, o por Página, o ..más a menudo?

¿Fue útil?

Solución

De acuerdo a MSDN:

http://msdn.microsoft.com/en-us/library/ms191242.aspx

Cuando el LECTURA INSTANTÁNEA COMPROMETIDO o PERMITIR el AISLAMIENTO de INSTANTÁNEA de base de datos las opciones están SOBRE, copias lógicas (versiones) se mantiene para todos los datos las modificaciones realizadas en el la base de datos.Cada vez que se modifica una fila por una transacción específica, la instancia de Motor de Base de datos almacena una versión de los cometidos anteriormente imagen de la fila en tempdb.Cada la versión está marcado con la transacción número de secuencia de la transacción que hizo el cambio.Las versiones de filas modificadas están conectados mediante un enlace lista.El valor de fila más reciente es siempre almacenados en la base de datos actual y encadenado a la versión filas almacenadas en tempdb.

A corto ejecución de las transacciones, una versión de una fila modificada puede obtener almacena en el buffer pool sin llegar por escrito en los archivos de disco de la base de datos tempdb.Si la necesidad de la versión de la fila es de corta duración, que simplemente se caen de la grupo de búfer y no necesariamente incurrir en I/O sobrecarga.

Parece que existe una ligera reducción en el rendimiento de la sobrecarga adicional, pero puede ser insignificante.Debemos probar para asegurarse de que.

Trate de configurar esta opción y ELIMINAR todos los NOLOCKs de consultas de código a menos que sea realmente necesario.NOLOCKs o el uso de métodos globales en el contexto de base de datos de controlador para luchar contra la base de datos de niveles de aislamiento de transacción son un curita a este problema.NOLOCKS máscara cuestiones fundamentales con nuestra capa de datos y, posiblemente, llevar a la selección de datos fiable, donde automático seleccione / actualización de las versiones de fila parece ser la solución.

ALTER Database [StackOverflow.Beta] SET READ_COMMITTED_SNAPSHOT ON

Otros consejos

NOLOCK y READ UNCOMMITTED son una pendiente resbaladiza.Nunca se debe utilizar a menos que comprender por qué el estancamiento que está sucediendo en primer lugar.Sería me preocupa que usted dice, "Hemos añadido with (nolock) para todas las consultas SQL".La necesidad de añadir CON NOLOCK en todas partes es un signo seguro de que usted tiene problemas en la capa de datos.

La actualización de la declaración en sí parece un poco problemático.¿Determinar el número anterior en la transacción, o simplemente tirar de un objeto? AnswerCount = AnswerCount+1 cuando una pregunta se agrega es probablemente una mejor manera de manejar esto.Entonces usted no necesita una transacción para obtener el número correcto y usted no tiene que preocuparse acerca de la concurrencia problema que potencialmente están exponiendo a.

Una forma fácil de conseguir alrededor de este tipo de problema de interbloqueo sin un montón de trabajo y sin la habilitación de las lecturas es el uso de "Snapshot Isolation Mode" (nuevo en SQL 2005), que siempre te dará una lectura que limpia de la última sin modificar los datos.También se puede coger y volver a intentar estancada declaraciones bastante fácil si se quiere manejar con gracia.

El OP pregunta era para preguntar el motivo de este problema en la actualidad.Este post espera de respuesta que, dejando las posibles soluciones para ser trabajado por los demás.

Esta es, probablemente, un índice relacionadas con el tema.Por ejemplo, supongamos que la tabla de mensajes, que tiene un índice no agrupado X que contiene el ParentID y uno (o más) de los campo(s) que se actualiza (AnswerCount, valor lastactivitydate, LastActivityUserId).

Un interbloqueo ocurriría si se SELECCIONA cmd no comparten un bloqueo de lectura sobre el índice de X a buscar por el ParentId y, a continuación, debe hacer una compartido-bloqueo de lectura en el índice agrupado para obtener el resto de las columnas, mientras que la ACTUALIZACIÓN de cmd hace una escritura bloqueo exclusivo en el índice agrupado y la necesidad de obtener una escritura bloqueo exclusivo en el índice de X para actualizarlo.

Ahora tenemos una situación donde Un cerrado de X y está tratando de obtener Y mientras que B bloqueado y y está tratando de obtener X.

Por supuesto, necesitaremos el OP para actualizar su post con más información sobre lo que los índices están en juego para confirmar si esta es realmente la causa.

Estoy bastante incómodo sobre esta cuestión y las consiguientes respuestas.Hay un montón de "probar este polvo mágico!No se que magia polvo!"

Yo no puedo ver en cualquier lugar que usted ha anaylzed los bloqueos que se toman, y determina qué tipo de bloqueos se encuentran en un impasse.

Todo lo que he indicado es que algunos se producen bloqueos, no lo que es de los interbloqueos.

En SQL 2005 usted puede obtener más información acerca de lo que las cerraduras están siendo tomadas por el uso de:

DBCC TRACEON (1222, -1)

de modo que cuando el interbloqueo se produce tendrás un mejor diagnóstico.

Estás instancias de un nuevo LINQ to SQL objeto DataContext para cada operación o son tal vez compartir el mismo contexto estático para todas tus llamadas?Originalmente se trató de la última, y por lo que recuerdo, lo que hizo no deseados de bloqueo en la base de datos.Yo ahora crear un nuevo contexto para cada operación atómica.

Antes de quemar la casa para coger un vuelo con NOLOCK de todo, puede que desee echar un vistazo a ese gráfico de interbloqueo debería has capturado con el Analizador.

Recuerde que un interbloqueo requiere (al menos) 2 bloqueos.Conexión 1 tiene Un Bloqueo, quiere Bloqueo de B y viceversa para la Conexión 2.Esta es una situación irresoluble, y alguien tiene que dar.

Lo que ha demostrado hasta el momento es resuelto por el simple bloqueo, lo que Sql Server está contento de hacer durante todo el día.

Sospecho que usted (o LINQ) se inicie una transacción con la instrucción UPDATE en ella, y la Selección de algún otro pedazo de info antes de la mano.Pero, usted realmente necesita para retroceder en el gráfico de interbloqueo para encontrar los bloqueos a cabo por cada hilo, y luego dar marcha atrás a través del Analizador de encontrar las declaraciones que causaron esos bloqueos a ser concedido.

Espero que haya al menos 4 instrucciones para completar este rompecabezas (o una declaración que toma múltiples cerraduras - tal vez hay un desencadenador en los Puestos de la mesa?).

Cuidará si su perfil de usuario es de unos pocos segundos de la fecha?

Nada de eso - eso es perfectamente aceptable.Ajuste de la base del nivel de aislamiento de transacción es probablemente el mejor/más limpio camino a seguir.

Típico de lectura/escritura de interbloqueo proviene de la orden de índice de acceso.Leer (T1) localiza la fila en el índice y, a continuación, busca proyectado columna en el índice a B (generalmente agrupados).Escribir (T2) cambios en el índice de B (el grupo), entonces se tiene que actualizar el índice A.T1 tiene S-Lck en Una, quiere S-Lck en B, T2 tiene X-Lck en B, quiere U-Lck en A.Interbloqueo, puff.T1 es asesinado.Esto es frecuente en lugares con un alto OLTP tráfico y sólo un poco demasiado muchos de los índices :).La solución es hacer que la lectura no tiene que saltar de Una a B (es decir.columna incluida en Una, o eliminar columna a partir de las proyecciones de la lista) o T2 no tienen que pasar de B a a (no actualizar columna indizada).Por desgracia, linq no es tu amigo aquí...

@Jeff - estoy definitivamente no es un experto en esto, pero he tenido buenos resultados con instancias de un nuevo contexto en casi cada llamada.Creo que es similar a la creación de un nuevo objeto de Conexión en cada llamada con ADO.La sobrecarga no es tan malo como se podría pensar, ya que la agrupación de conexiones se utiliza de todos modos.

Yo sólo uso global auxiliar estática como este:

public static class AppData
{
    /// <summary>
    /// Gets a new database context
    /// </summary>
    public static CoreDataContext DB
    {
        get
        {
            var dataContext = new CoreDataContext
            {
                DeferredLoadingEnabled = true
            };
            return dataContext;
        }
    }
}

y luego puedo hacer algo como esto:

var db = AppData.DB;

var results = from p in db.Posts where p.ID = id select p;

Y me gustaría hacer la misma cosa para las actualizaciones.De todos modos, no tengo casi tanto tráfico como usted, pero yo era, sin duda algunos de bloqueo cuando he usado un compartida DataContext temprano con apenas un puñado de usuarios.No hay garantías, pero valdría la pena darle una oportunidad.

Actualización:Entonces de nuevo, mirando el código, sólo compartir el contexto de datos de la vida de esa instancia de controlador, que básicamente parece estar bien, a menos que de alguna manera es llegar utilizados simultáneamente por varias llamadas dentro del controlador.En un hilo sobre el tema, ScottGu dijo:

Los controladores de vivir sólo para una única solicitud - así que al final de la tramitación de una solicitud son el recolector de basura (lo que significa que el DataContext es recogida)...

Así que de todos modos, que no puede ser, pero de nuevo es probablemente vale la pena intentarlo, tal vez en combinación con algunas de las pruebas de carga.

P.¿Por qué te almacenar el AnswerCount en el Posts la tabla en el primer lugar?

Un enfoque alternativo es el de eliminar la "escritura" a la Posts tabla no almacenar el AnswerCount en la tabla, pero para calcular de forma dinámica el número de respuestas a los post como sea necesario.

Sí, esto significa que usted está ejecutando una consulta adicional:

SELECT COUNT(*) FROM Answers WHERE post_id = @id

o más normalmente (si usted está mostrando esta para la página de inicio):

SELECT p.post_id, 
     p.<additional post fields>,
     a.AnswerCount
FROM Posts p
    INNER JOIN AnswersCount_view a
    ON <join criteria>
WHERE <home page criteria>

pero esto normalmente se traduce en un INDEX SCAN y puede ser más eficientes en el uso de los recursos que el uso de READ ISOLATION.

Hay más de una manera para la piel de un gato.Prematuro de normalización de una base de datos de esquema pueden presentar problemas de escalabilidad.

Usted definitivamente quiere READ_COMMITTED_SNAPSHOT on, que no es por defecto.Que le da MVCC semántica.Es la misma cosa Oracle utiliza de forma predeterminada.Tener un MVCC de la base de datos es tan increíblemente útil, NO usando uno está loco.Esto le permite ejecutar el siguiente dentro de una transacción:

Update USUARIOS Set Nombre = 'foobar';//decidir a dormir durante un año.

mientras tanto, sin comprometer la de arriba, todo el mundo puede seguir para seleccionar de la tabla bien.Si usted no está familiarizado con MVCC, usted estará sorprendido de que alguna vez capaz de vivir sin él.En serio.

El ajuste predeterminado para la lectura no confirmada no es una buena idea.Su voluntad, sin duda, introducir incoherencias y terminar con un problema que es peor que lo que tenemos ahora.El aislamiento de instantánea puede funcionar bien, pero es un cambio drástico en la forma en que Sql Server funciona y pone un enorme carga en tempdb.

Aquí es lo que debe hacer:el uso de try-catch (en T-SQL) para detectar la condición de interbloqueo.Cuando esto sucede, vuelva a ejecutar la consulta.Este es el estándar de programación de base de datos de la práctica.

Hay buenos ejemplos de esta técnica en Pablo Nielson Sql Server 2005 De La Biblia.

Aquí está un rápido plantilla que yo uso:

-- Deadlock retry template

declare @lastError int;
declare @numErrors int;

set @numErrors = 0;

LockTimeoutRetry:

begin try;

-- The query goes here

return; -- this is the normal end of the procedure

end try begin catch
    set @lastError=@@error
    if @lastError = 1222 or @lastError = 1205 -- Lock timeout or deadlock
    begin;
        if @numErrors >= 3 -- We hit the retry limit
        begin;
            raiserror('Could not get a lock after 3 attempts', 16, 1);
            return -100;
        end;

        -- Wait and then try the transaction again
        waitfor delay '00:00:00.25';
        set @numErrors = @numErrors + 1;
        goto LockTimeoutRetry;

    end;

    -- Some other error occurred
    declare @errorMessage nvarchar(4000), @errorSeverity int
    select    @errorMessage = error_message(),
            @errorSeverity = error_severity()

    raiserror(@errorMessage, @errorSeverity, 1)

    return -100
end catch;    

Una cosa que me ha funcionado en el pasado es asegurarse de que todas mis consultas y actualizaciones de acceso a los recursos (las tablas) en el mismo orden.

Es decir, si uno consulta las actualizaciones en orden Tabla1, Tabla2 y una consulta diferente de las actualizaciones en el orden de la Tabla2, Tabla1, a continuación, puede ver interbloqueos.

No estoy seguro si es posible cambiar el orden de las actualizaciones ya que usted está utilizando LINQ.Pero es algo para mirar.

Cuidará si su perfil de usuario es de unos pocos segundos de la fecha?

Un par de segundos definitivamente sería aceptable.No le parece que sería largo, de todos modos, a menos que un gran número de personas son la presentación de las respuestas al mismo tiempo.

Estoy de acuerdo con Jeremy en este.Pregunte si usted debe crear un nuevo contexto de datos para cada controlador o por página - que tienden a crear una nueva para cada consulta independiente.

Estoy construyendo una solución en la actualidad y que se utiliza para implementar el contexto estático como lo haces, y cuando me arrojaron toneladas de solicitudes a la bestia de un servidor (millones) durante las pruebas de estrés, que también estaba recibiendo de lectura/escritura bloquea al azar.

Tan pronto como he cambiado mi estrategia de utilizar un diferente contexto de datos en LINQ nivel por consulta, y confía en que SQL server puede trabajar su conexión a la agrupación de magia, los bloqueos parecía desaparecer.

Por supuesto, yo estaba bajo alguna presión de tiempo, por lo que intentar una serie de cosas que todos alrededor de la misma hora, así que no puedo estar 100% seguro de que es lo que fija, pero tengo un alto nivel de confianza - vamos a ponerlo de esa manera.

Usted debe poner en práctica las lecturas.

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

Si no requieren absolutamente perfecta integridad transaccional con su consulta, usted debe utilizar las lecturas cuando el acceso a las tablas con un alto nivel de concurrencia.Puedo asumir que sus Puestos de la tabla sería uno de ellos.

Esto puede darle el llamado "lecturas fantasma", que es cuando la consulta actúa sobre los datos de una transacción que no ha sido cometido.

No estamos ejecutando un sitio de banca aquí, no necesitamos perfecta de precisión, cada vez

El uso de lecturas.Tienes razón en que no le darán la perfecta exactitud, pero se debe aclarar tus muertos problemas de bloqueo.

Sin eso, tendríamos que envuelva cada LINQ llamada que hacer (bueno, la simple lectura, que es la gran mayoría de ellos) en una de las 3-4 de la línea de transacción bloque de código, que es feo

En el caso de implementar lecturas sobre "la base de datos de contexto", siempre puedes envolver tus llamadas individuales utilizando un mayor nivel de aislamiento si usted necesita la integridad transaccional.

Así que ¿cuál es el problema con la implementación de un mecanismo de reintento?Siempre habrá la posibilidad de un estancamiento ocurring así que ¿por qué no tener un poco de lógica para identificar y probar de nuevo?

No, al menos, algunas de las otras opciones de introducir penalizaciones de rendimiento que se toman todo el tiempo cuando un sistema de reintentos se patada en rara vez?

También, no se olvide de algún tipo de registro cuando un reintento pasa de modo que usted no entrar en esa situación de raro convirtiéndose a menudo.

Ahora que veo Jeremy respuesta, creo yo recuerdo haber oído que la mejor práctica es utilizar un nuevo DataContext para datos de cada operación.Rob Conery escrito varios posts sobre DataContext, y él siempre noticias de ellos en lugar de utilizar un singleton.

Aquí está el patrón que hemos utilizado para el Vídeo.Mostrar (enlace a la fuente de la vista en CodePlex):

using System.Configuration;
namespace VideoShow.Data
{
  public class DataContextFactory
  {
    public static VideoShowDataContext DataContext()
    {
        return new VideoShowDataContext(ConfigurationManager.ConnectionStrings["VideoShowConnectionString"].ConnectionString);
    }
    public static VideoShowDataContext DataContext(string connectionString)
    {
        return new VideoShowDataContext(connectionString);
    }
  }
}

A continuación, en el nivel de servicio (o incluso más granular, para las actualizaciones):

private VideoShowDataContext dataContext = DataContextFactory.DataContext();

public VideoSearchResult GetVideos(int pageSize, int pageNumber, string sortType)
{
  var videos =
  from video in DataContext.Videos
  where video.StatusId == (int)VideoServices.VideoStatus.Complete
  orderby video.DatePublished descending
  select video;
  return GetSearchResult(videos, pageSize, pageNumber);
}

Yo tendría que estar de acuerdo con Greg tan largo como establecer el nivel de aislamiento read uncommitted no tiene malos efectos en otras consultas.

Me interesaría saber, Jeff, cómo la configuración en el nivel de base de datos podría afectar a una consulta como la siguiente:

Begin Tran
Insert into Table (Columns) Values (Values)
Select Max(ID) From Table
Commit Tran

Está bien conmigo si mi perfil es aún varios minutos fuera de fecha.

Son vuelva a intentar la lectura después de la falla?Ciertamente es posible cuando el disparo de un montón de lecturas aleatorias que unos pocos se golpea cuando no se puede leer.La mayoría de las aplicaciones con las que trabajo son muy pocos los que escribe en comparación con el número de lecturas y estoy seguro de que las lecturas están en ninguna parte cerca del número al que desea llegar.

Si la ejecución de la "READ UNCOMMITTED" no resuelve el problema, entonces es difícil ayudar sin saber mucho más acerca de la transformación.Puede haber alguna otra opción de optimización que ayudar a este comportamiento.A menos que algunos MSSQL gurú viene al rescate, recomiendo que presenta el problema para el proveedor.

Me gustaría seguir afinar todo;¿cómo es el subsistema de disco de realizar?¿Cuál es el promedio de longitud de cola de disco?Si e/s de la copia de seguridad, el problema real no podría ser de estas dos consultas que son los interbloqueos, podría ser otra consulta que es cuellos de botella del sistema;usted ha mencionado una consulta de tomar 20 segundos en los que se ha afinado, ¿hay otros?

Centrarse en el acortamiento del tiempo de ejecución de las consultas, apuesto a que los de interbloqueo problemas desaparecerán.

Tenía el mismo problema, y no se puede utilizar el "IsolationLevel = IsolationLevel.ReadUncommitted" en TransactionScope porque el servidor no tiene DTS habilitado (!).

Eso es lo que hice yo, con un método de extensión:

public static void SetNoLock(this MyDataContext myDS)
{
    myDS.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
}

Así que, para las mismas que uso crítico de concurrencia tablas, que permiten la "nolock" como esta:

using (MyDataContext myDS = new MyDataContext())
{
   myDS.SetNoLock();

   //  var query = from ...my dirty querys here...
}

Sugerencias son bienvenidos!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top