2009-03-30 21 views
5

Tengo una tabla de datos heredada en SQL Server 2005 que tiene una PK sin identidad/autoincrement y no hay poder para implementar una.Cómo evitar una condición de carrera de la base de datos al incrementar manualmente PK de la nueva fila

Como resultado, me veo forzado a crear nuevos registros en ASP.NET manualmente a través de la nueva técnica "SELECT MAX (id) + 1 FROM table" -antes de la inserción.

Obviamente, esto crea una condición de carrera en la identificación en el caso de insertos simultáneos.

¿Cuál es la mejor manera de resolver grácilmente el evento de una colisión racial? Estoy buscando ideas de código VB.NET o C# a lo largo de las líneas de detección de una colisión y luego volver a intentar la inserción fallida al obtener otro máximo (id) + 1. ¿Se puede hacer esto?

¿Pensamientos? ¿Comentarios? ¿Sabiduría?

¡Gracias!

NOTA: ¿Qué pasa si no puedo cambiar la base de datos de alguna manera?

Respuesta

4

No poder cambiar el esquema de la base de datos es duro.

Si inserta PK existente en la tabla, obtendrá SqlException con un mensaje que indica una violación de la restricción PK. Capture esta excepción y vuelva a intentar insertar algunas veces hasta que tenga éxito. Si encuentra que la tasa de colisión es demasiado alta, puede intentar max(id) + <small-random-int> en lugar de max(id) + 1. Tenga en cuenta que con este enfoque, sus identificadores tendrán espacios vacíos y el espacio de id se agotará antes.

Otro posible enfoque es emular el autoincrementing id fuera de la base de datos. Por ejemplo, cree un entero estático, Interlocked.Increment, cada vez que necesite la próxima identificación y use el valor devuelto. La parte difícil es inicializar este contador estático a un buen valor. Lo haría con Interlocked.CompareExchange: Sólo

class Autoincrement { 
    static int id = -1; 
    public static int NextId() { 
    if (id == -1) { 
     // not initialized - initialize 
     int lastId = <select max(id) from db> 
     Interlocked.CompareExchange(id, -1, lastId); 
    } 
    // get next id atomically 
    return Interlocked.Increment(id); 
    } 
} 

Obviamente los últimos trabajos si se obtienen todos los identificadores insertados a través de Autoincrement.NextId solo proceso.

+0

Justo lo que necesitaba gracias !!! –

6

Crea una tabla auxiliar con una columna de identidad. En una inserción de transacción en la tabla auxiliar, recupere el valor y úselo para insertarlo en su tabla heredada. En este punto, incluso puede eliminar la fila insertada en la tabla auxiliar, el punto es simplemente usarlo como fuente de valores incrementados.

+0

Es mucho más fácil de implementar, +1 –

+0

¡Muy inteligente, gracias! Desafortunadamente no tengo forma de cambiar la base de datos. Esta congelado. –

+0

Agréguelo como una segunda base de datos vinculada, o utilice un archivo XML local para el código para lograr lo mismo. –

1

La mejor solución es cambiar la base de datos. Es posible que no pueda cambiar la columna para que sea una columna de identidad, pero debe poder asegurarse de que haya una restricción única en la columna y agregar una nueva columna de identidad con sus PK existentes. Luego, use la nueva columna en su lugar o use un disparador para hacer que la columna anterior refleje la nueva, o ambas.

3

La clave es hacerlo en una declaración o una transacción.

¿Se puede hacer esto?

INSERT (PKcol, col2, col3, ...) 
SELECT (SELECT MAX(id) + 1 FROM table WITH (HOLDLOCK, UPDLOCK)), @val2, @val3, ... 

Sin pruebas, esto probablemente también funcionará:

INSERT (PKcol, col2, col3, ...) 
VALUES ((SELECT MAX(id) + 1 FROM table WITH (HOLDLOCK, UPDLOCK)), @val2, @val3, ...) 

Si no se puede, de otra manera es hacerlo en un disparador.

  1. El disparador es parte de la transacción INSERT
  2. Uso HOLDLOCK, UPDLOCK para el MAX. Esto mantiene el bloqueo fila hasta cometer
  3. La fila está actualizando está bloqueado durante la duración

Un segundo inserto esperará hasta que se complete la primera. El inconveniente es que está cambiando la clave principal.

Una tabla auxiliar debe formar parte de una transacción.

O cambiar el esquema como se sugiere ...

+0

Intenté una inserción anidada y me dijo que no puedo hacer inserciones anidadas. Tampoco tengo poder para cambiar la base de datos de ninguna otra manera que no sean actualizaciones/inserciones SQL. –

+0

¿Se permiten transacciones del lado del cliente en su código de cliente – gbn

2

¿Qué hay de correr todo el lote (para seleccionar ID e insertar) en una transacción serializable?

Eso debe tenerlo alrededor necesitando hacer cambios en la base de datos.

3

Nota: Todo lo que necesita es una fuente de enteros cada vez mayores. No tiene que provenir de la misma base de datos, ni siquiera de una base de datos.

Personalmente, yo usaría SQL Express porque es gratis y fácil.

Si usted tiene un único servidor web: Crear una base de datos SQL Express en el servidor web con una sola tabla [ids] con un solo campo autoincremental [NEW_ID]. Inserte un registro en esta tabla [ids], obtenga [new_id] y páselo a su capa de base de datos como PK de la tabla en cuestión.

Si tiene varios servidores web: Es un dolor de configurar, pero se puede usar el mismo truco mediante el establecimiento adecuado de semillas/incremento (es decir, incremento = 3, y = 1/2/3 semilla de tres web servidores).

1

¿El principal problema es el acceso concurrente? Quiero decir, ¿varias instancias de su aplicación (o, Dios no lo permita, otras aplicaciones fuera de su control) estarán realizando inserciones al mismo tiempo?

De lo contrario, probablemente pueda administrar las inserciones a través de un módulo central sincronizado en su aplicación, y evitar completamente las condiciones de carrera.

Si es así, bueno ... como dijo Joel, cambie la base de datos. Sé que no puedes, pero el problema es tan antiguo como las montañas, y se ha resuelto bien, a nivel de base de datos. Si quiere arreglarlo usted mismo, simplemente tendrá que hacer bucles (insertar, verificar colisiones, eliminar) una y otra vez. El problema fundamental es que no se puede realizar una transacción (no me refiero a eso en el sentido "TRANSACCIÓN" de SQL, sino en el sentido más amplio de la teoría de datos) si no cuenta con el respaldo de la base de datos.

La única idea que tengo es que si al menos usted tiene control sobre quién tiene acceso a la base de datos (por ejemplo, solo aplicaciones "autorizadas" escritas o aprobadas por usted), podría implementar un mutex de banda lateral de tipo, donde todas las aplicaciones comparten un "parlante" y se requiere la propiedad del mutex para hacer una inserción. Sin embargo, esa sería su propia bola de cera peluda, ya que tendrías que averiguar la política para los clientes muertos, dónde está alojada, problemas de configuración, etc. Y, por supuesto, un cliente "deshonesto" podría hacer inserciones sin el parlante y manguera toda la instalación.

Cuestiones relacionadas