2011-03-16 20 views
13

Esta pregunta se refiere a las mejores prácticas para manejar muchas inserciones o actualizaciones utilizando Microsoft Entity Framework. El problema es que escribimos un programa de larga ejecución que extrae miles de registros de la base de datos y luego actualiza un solo campo en cada uno de esos registros, uno por uno. Para nuestra gran consternación, nos dimos cuenta de que cada uno de estos registros que se actualizaron se bloquearon durante el tiempo en que no se eliminó el ObjectContext. A continuación se muestra un poco de pseudocódigo (en realidad no ejecutar) para ilustrar:Cómo evitar el bloqueo de la base de datos en Entity Framework 4 Cuando se realizan muchas actualizaciones

using(ObjectContext context = new ObjectContext()) 
{ 

    var myRecords = context.CreateObjectSet<MyType>().AsQueryable(); 

    foreach(var record in myRecords) 
    { 
     record.MyField = "updated!"; 
     context.SaveChanges(); 

     //--do something really slow like call an external web service 
    } 
} 

El problema es que tenemos que hacer muchos cambios sin ninguna consideración por las transacciones. Nos sorprendimos al darnos cuenta de que llamar context.SaveChanges() en realidad crea el bloqueo en los registros y no lo libera hasta que se elimina el ObjectContext. En especial, NO queremos bloquear los registros en la base de datos ya que este es un sistema de alto tráfico y el programa podría ejecutarse durante horas.

Entonces la pregunta es: ¿cuál es la forma óptima de hacer muchas actualizaciones en Microsoft Entity Framework 4 SIN hacer todo en una transacción larga que bloquea la base de datos? Esperamos que la respuesta no sea crear un nuevo ObjectContext para cada actualización ...

+0

¿Ve bloqueos de registros o bloqueos de bloqueo a páginas o tablas? –

+1

Parece que la tabla completa está bloqueada, por lo que no puede insertar o actualizar registros. Sin embargo, puede seleccionar registros siempre que no seleccione uno de los registros que se han retirado.Sin embargo, en nuestro pseudo ejemplo anterior, retiramos todos los registros. En realidad, solo estábamos retirando un subconjunto del total de registros y solo esos registros no se podían leer. Sin embargo, toda la tabla está bloqueada desde escrituras. – jakejgordon

Respuesta

7

marco de la entidad en la parte superior del servidor SQL por defecto usos leer nivel de aislamiento de transacción confirmada y transacción se confirma al final del SaveChanges. Si sospecha que hay otro comportamiento, debe ser por el resto de su código (¿está usando TransactionScope?- no lo mostró en su código) o debe ser un error.

También su enfoque es incorrecto. Si desea guardar cada registro por separado, también debe cargar cada registro por separado. EF definitivamente es una mala elección para este tipo de aplicaciones. Incluso si usa solo el número SaveChange para actualizar todos sus registros, seguirá haciendo un solo viaje de ida y vuelta a la base de datos para cada actualización.

+1

Su segundo párrafo es similar a lo que finalmente decidimos hacer. Terminamos creando un nuevo ObjectContext, recuperando el registro, actualizándolo, guardándolo y disponiendo el ObjectContext para que cada actualización ocurriera en su pequeña transacción. Entity Framework no parece ser la mejor solución para este tipo de aplicación. –

2

Puedo estar equivocado, pero creo que no debería llamar a SaveChanges() cada vez que aplique los cambios al base de datos en ese punto. En su lugar, aplique SaveChanges() al final de sus cambios de objeto, o use un contador para hacerlo con menos frecuencia.

+0

Tiene razón; sin embargo, no libera el bloqueo en el registro. La pregunta es cómo comprometerse inmediatamente sin mantener el bloqueo durante todo el ciclo de vida del ObjectContext. – jakejgordon

4

Esos bloqueos no son creados por Entity Framework. EF solo admite concurrencia optimista, el bloqueo pesimista no es compatible con EF.

Creo que el bloqueo que experimentas es el resultado de la configuración de tu SQL Server. Tal vez, si su nivel de aislamiento de transacciones en el servidor está configurado como REPEATABLE LEER, esto podría causar bloqueos después de cada consulta. Pero no estoy seguro de qué configuración podría ser exactamente el problema. Más detalles son here.

Editar:

Otro artículo útil acerca de las transacciones y de aislamiento de transacción en EF es here. Se recomienda encarecidamente establecer siempre el nivel de aislamiento explícitamente. Cita del artículo:

Si no tomar el control de [el nivel de aislamiento ], usted no tiene idea de lo que nivel de aislamiento sus consultas va a correr. Después de todo, usted no sabe dónde la conexión que obtuvo del grupo ha sido [...] Simplemente hereda el último nivel de aislamiento usado en la conexión, por lo que no tiene idea qué tipo de bloqueos se toman (o peor: se ignoran) por sus consultas y por cuánto tiempo se mantendrán estos bloqueos . En una base de datos ocupada, esto definitivamente conducirá a errores aleatorios, tiempos muertos y bloqueos.

+1

Soy un compañero de trabajo de la persona que pregunta (FYI) y no entiendo cómo podría ser este el caso. La base de datos a la que está accediendo tiene docenas de actualizaciones que ocurren por segundo (durante AÑOS) desde fuentes que no son de Entity Framework y nunca hemos tenido este problema antes de usar Entity Framework para acceder a la base de datos ... estamos en una pérdida como este punto. – jakejgordon

+0

@jakejgordon: ¿Puede verificar la configuración actual con, por ejemplo, el estudio de administración de SQL Server ejecutando 'DBCC USEROPTIONS' en una ventana de consulta. Esto debería enumerar algunas configuraciones, una de ellas es 'nivel de aislamiento '. Por defecto, debe ser "leído comprometido". ¿Tiene quizás otra configuración aquí? – Slauma

+0

El nivel de aislamiento es "leído comprometido". Gracias por su ayuda hasta el momento, ¡realmente lo apreciamos! – jakejgordon

1

En nuestra aplicación tuvimos un escenario similar, evitemos el bloqueo tanto como sea posible ejecutando una selección masiva y luego creando muchas inserciones después de algunas operaciones de memoria.

  1. Si desea leer todo por adelantado

Solución A) Use un ámbito de transacción que incluye leer y actualizar PRO: Datos actualizados de forma segura CONS: Cerraduras causadas por lectura (repetible lecturas) y actualizar

Solución B) No utilice una transacción y actualice todos los datos PRO: Datos actualizados con seguridad, pero los datos que lectura puede haber sido cambiado en el ínterin CONS: Cerraduras causados ​​por la actualización para toda la duración (EF crea por defecto una transacción)

Solución C) Actualización en lotes en lugar de todos los datos de todos juntos (sólo utilizable si la selección no bloquea las tablas, de lo contrario obtendrá el mismo comportamiento que B PRO: bloqueos más cortos y más pequeños en las tablas actualizadas CONS: aumenta el cambio de ser afectado por obsolescencia de datos

  1. Si desea (y puede) leer en lotes

Solución D) Resolver el problema y dividir las lecturas puede facilitar la reducción del bloqueo para que pueda usar un alcance de transacción para envolver tanto la lectura como la escritura (como sol. A) PRO: Datos actualizados de forma segura CONS: Cerraduras causadas por lectura (repetible lecturas) y de actualización, los impactos varían en función del tamaño del lote y la naturaleza de la propia consulta

solución E) No utilice transacciones, por lo que sólo la actualización producirá cerraduras pequeñas (como Sol B). PRO: datos actualizados de forma segura, pero los datos que lee puede haber cambiado mientras tanto CONS: cerraduras causadas por los cambios

como @Ladislav apuntado correctamente , las inserciones múltiples son realmente ineficientes y un perfil rápido en la base de datos muestra cómo falla la magia de ORM en este caso. Si desea utilizar EF para realizar operaciones por lotes inserciones, actualización y eliminaciones, te recomiendo echar un vistazo a esto: EF Utilities

tiendo a probar cerraduras usando esta consulta, espero que pueda ayudar a entender mejor Que esta pasando.

SELECT 
    OBJECT_NAME(p.OBJECT_ID) AS TableName, 
    resource_type, 
    resource_description 
FROM 
    sys.dm_tran_locks l JOIN 
    sys.partitions p ON 
    l.resource_associated_entity_id = p.hobt_id 
Cuestiones relacionadas