2012-08-24 18 views
8

Tal escenario: una cierta cantidad de datos para insertar en una tabla, cuando alcanza un umbral que ya no se inserta, simulé este escenario, en el caso de multiproceso (eg asp.net) aparecieron problemas concurrentes.Cómo resolver el conflicto de simultaneidad en ado.net

Mi pregunta es cómo resolver el problema concurrente, no utilice el caso lock

void Main() 
{ 

    Enumerable.Range(0,20).ToList().ForEach(i=>{ 
     MockMulit(); 
    }); 

} 
//Start a certain number of threads for concurrent simulation 
void MockMulit() 
{ 
    int threadCount=100; 

    ClearData();//delete all data for test 


    var tasks=new List<Task>(threadCount); 
    Enumerable.Range(1,threadCount).ToList().ForEach(i=>{ 
    var j=i; 
    tasks.Add(Task.Factory.StartNew(()=>T3(string.Format("Thread{0}-{1}",Thread.CurrentThread.ManagedThreadId,j)))); 
    }); 
    Task.WaitAll(tasks.ToArray()); 

    CountData().Dump();//show that the result 
} 

método de un solo - concurrencia muy grave

void T1(string name) 
{ 
     using(var conn=GetOpendConn()) 
     { 
      var count=conn.Query<int>(@"select count(*) from dbo.Down").Single(); 
      if(count<20) 
      { 
       conn.Execute(@"insert into dbo.Down (UserName) values (@UserName)",new{UserName=name}); 
      } 
     } 

} 

el método dos - poner el SQL juntos puede reducir concurrente, pero todavía existe

void T2(string name) 
{ 
    using(var conn=GetOpendConn()) 
    { 
     conn.Execute(@" 
         if((select count(*) from dbo.Down)<20) 
         begin 
          --WAITFOR DELAY '00:00:00.100'; 
          insert into dbo.Down (UserName) values (@UserName) 
         end",new{UserName=name}); 
    } 
} 

método tres - con bloqueo d destruirlos del concurrente, pero yo no creo que es una mejor solución

private static readonly object countLock=new object(); 
void T3(string name) 
{ 
    lock(countLock) 
    { 
     using(var conn=GetOpendConn()) 
     { 
      var count=conn.Query<int>(@"select count(*) from dbo.Down").Single(); 
      if(count<20) 
      conn.Execute(@"insert into dbo.Down (UserName) values (@UserName)",new{UserName=name}); 
     } 
    } 
} 

otro método ayuda

//delete all data 
void ClearData() 
{ 
    using(var conn=GetOpendConn()) 
    { 
     conn.Execute(@"delete from dbo.Down"); 
    } 
} 
//get count 
int CountData() 
{ 
    using(var conn=GetOpendConn()) 
    { 
     return conn.Query<int>(@"select count(*) from dbo.Down").Single(); 
    } 
} 
//get the opened connection 
DbConnection GetOpendConn() 
{ 
    var conn=new SqlConnection(@"Data Source=.;Integrated Security=SSPI;Initial Catalog=TestDemo;"); 
    if(conn.State!=ConnectionState.Open) 
     conn.Open(); 
    return conn; 
} 
+0

¿Qué tipo de base de datos está utilizando que no puede manejar la inserción de datos? – Jake1164

+0

El escenario al que intenta proteger no es muy claro. ¿Puedes quizás reformular lo que estás tratando de hacer? –

+0

@MarcGravell En realidad, diseñé una actividad de aumento de mercancías para nuestro sitio web de comercio electrónico. Por ejemplo, solo se pueden comprar 20 productos, porque asp.net tiene múltiples hilos, ¡21 registros aparecen en nuestra base de datos! :( – JeffZhnn

Respuesta

4

Suena como si sólo se desea insertar en hacia abajo cuando hay menos de 20 filas. Si es así: hacer que una sola operación:

insert dbo.Down (UserName) 
select @UserName 
where (select count(1) from dbo.Down) < 20 

select @@rowount -- 1 if we inserted, 0 otherwise 

alternativa, si * necesidad * tendrá que utilizar una transacción, lo ideal "Serializable", de manera que se obtiene una gama llave-cerradura - tal vez incluso agregando (UPDLOCK) al recuento inicial, para garantizar que tenga un bloqueo de escritura ansioso (o bloques, en lugar de interbloqueos). Pero: la operación TSQL sola (como ya se ha ilustrado es preferible Usted podría incluso hacer que más paranoico (aunque no estoy seguro de que lo necesita):.

declare @count int 

begin tran 

insert dbo.Down (UserName) 
select @UserName 
where (select count(1) from dbo.Down (UPDLOCK)) < 20 

set @count = @@rowount 

commit tran 

select @count -- 1 if we inserted, 0 otherwise 
+0

Estaba escribiendo una prueba (https://github.com/dushouke/ConcurrencyTest), hacer que una sola operación no pueda resolver el problema. Pero con el 'UPDLOCK' puede destruir el – JeffZhnn

+0

concurrente @jefferydu no,' UPDLOCK' no "destruye el concurrente" - simplemente *** correctamente *** indica que estamos planeando cambiar estos datos en la misma transacción (en lugar de solo leerlo). ¿Intentó la segunda versión que publiqué? –

+0

sí, probé la segunda versión, no se insertaron más registros. Las capturas de pantalla del resultado http://i.stack.imgur.com/155ev.jpg – JeffZhnn

1

considerar hacer esto a la inversa y que probablemente será mucho . simple Por ejemplo:

  • crear una tabla con un índice de incremento automático Llámelo "Up" o "RequestOrdering", o lo que sea
  • Recibe solicitudes de los clientes en cualquier orden Para cada solicitud:...
    • Insertar una nueva rema en Up.
    • Obtenga la última identificación de fila insertada.
    • Si la última inserción de ID < = 20, realice una inserción real.
  • Tire a la basura su mesa cuando haya terminado con ella.

Si su base de datos admite claves primarias de varias columnas con una de ellas de auto incremento (IIRC, tablas MyISAM), puede volver a utilizar la tabla "Arriba" en varias ofertas especiales del producto.


Una implementación aún más fácil sería insertar 20 filas en "Abajo" y eliminar una por solicitud. Verifique el número de filas afectadas (debería ser 1) para ver si el usuario tuvo éxito. Esto funciona muy bien con especiales de varios productos.Por ejemplo:

delete * from Down where down_special_id = Xxx limit 1 

probablemente más fácil, y también para mantener un registro de quién "ganado", crear una fila por cada producto y tener cada actualización usuario una (y sólo una) fila. De nuevo, verifique el número de filas afectadas para ver si tuvieron éxito:

update Up 
set 
    user_name = @user_name 
where 
    user_name is null 
    and product_id = @product_id 
limit 1 
+0

Pardon la sintaxis de MySQL. Creo que SQL Server y otros como "TOP" vs. "LÍMITE". – EthanB

Cuestiones relacionadas