2012-07-05 8 views
6

Tengo un procedimiento almacenado que genera UID de una tabla de "ticket", pero bajo carga estoy recibiendo muchos bloqueos. Estoy llamando a este procedimiento muchas veces desde múltiples conexiones simultáneas cada vez que mi tarea necesita un nuevo UID.MySQL deadlocks con procedimiento almacenado que genera el UID

BEGIN 
    DECLARE a_uid BIGINT(20) UNSIGNED; 
    START TRANSACTION; 
    SELECT uid INTO a_uid FROM uid_data FOR UPDATE; # Lock 
    INSERT INTO uid_data (stub) VALUES ('a') ON DUPLICATE KEY UPDATE uid=uid+1; 
    SELECT a_uid+1 AS `uid`; 
    COMMIT; 
END 

Yo considero usando:

BEGIN 
    REPLACE INTO uid_data (stub) VALUES ('a'); 
    SELECT LAST_INSERT_ID(); 
END 

Sin embargo, yo no estaba seguro de si eso sería seguro con conexiones simultáneas ya que no hay bloqueo, a diferencia del primer procedimiento con el SELECT FOR UPDATE.

Aquí está la tabla:

mysql> DESCRIBE uid_data; 
+-------+---------------------+------+-----+---------+----------------+ 
| Field | Type    | Null | Key | Default | Extra   | 
+-------+---------------------+------+-----+---------+----------------+ 
| uid | bigint(20) unsigned | NO | PRI | NULL | auto_increment | 
| stub | char(1)    | NO | UNI | NULL |    | 
+-------+---------------------+------+-----+---------+----------------+ 

He fijado para el aislamiento de transacción comprometida-lectura:

mysql> SHOW VARIABLES LIKE 'tx_isolation'; 
+---------------+-----------------+ 
| Variable_name | Value   | 
+---------------+-----------------+ 
| tx_isolation | READ-COMMITTED | 
+---------------+-----------------+ 

Aquí es lo que estoy de volver de SHOW ENGINE INNODB STATUS;

... 
... dozens and dozens of the following record locks... 

Record lock, heap no 1046 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 
0: len 1; hex 61; asc a;; 
1: len 8; hex 00000000000335f2; asc  5 ;; 

Record lock, heap no 1047 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 
0: len 1; hex 61; asc a;; 
1: len 8; hex 00000000000335f1; asc  5 ;; 

*** (2) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 13 page no 4 n bits 1120 index `stub` of table `my_db`.`uid_data` trx id 13AA89 lock_mode X waiting 
Record lock, heap no 583 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 
0: len 1; hex 61; asc a;; 
1: len 8; hex 00000000000334a8; asc  4 ;; 

*** WE ROLL BACK TRANSACTION (1) 

I Estaría agradecido si alguien pudiera explicar lo que está sucediendo y cómo pueden evitarse.

+0

Para información: el interbloqueo se produce incluso cuando se usa esta secuencia simple: 'START TRANSACTION; SELECCIONE uid FROM uid_data PARA ACTUALIZAR; ACTUALIZACIÓN uid_data SET uid = uid +1 [[posible punto muerto aquí]]; COMMIT; '(por lo tanto, no tiene nada que ver con la cláusula' ON DUPLICATE'). Sin embargo, no se produce un punto muerto con un nivel de aislamiento de 'REPEATABLE READ;'. Todavía no sé qué concluir a partir de este punto. – RandomSeed

Respuesta

0

produce un interbloqueo en este escenario:

Transacción 1: solicita un bloqueo (SELECT...FOR UPDATE) y adquiere

Transacción 2: solicita un bloqueo (SELECT...FOR UPDATE) y debe esperar

Transacción 1: intenta introducir, realiza un duplicado, por lo tanto, las actualizaciones (INSERT...ON DUPLICATE KEY UPDATE) => estancamiento

no estoy muy seguro acerca de la re ason, sospecho que tiene algo que ver con el ON DUPLICATE KEY UPDATE. Todavía estoy investigando y volveré si me entero.

[Editar] Un interbloqueo se produce incluso con:

BEGIN 
    START TRANSACTION; 
    SELECT uid FROM uid_data FOR UPDATE; 
    UPDATE uid_data SET uid = uid +1; -- here, a deadlock would be detected in a blocked, concurrent connection 
    COMMIT; 
END 

Qué tal esto:

BEGIN 
    START TRANSACTION;  
    UPDATE uid_data SET uid = uid +1; 
    SELECT uid FROM uid_data; 
    COMMIT; 
END 

Se podría dejar su stub Colum por completo. El único inconveniente es que debe inicializar su uid_data con una fila.

+0

¿Ese procedimiento almacenado revisado maneja la concurrencia con algún tipo de bloqueo? – Sencha

+0

@Sencha Sí, 'UPDATE' es atómico, y también bloquea la (s) fila (s) hasta el final de la transacción. Sin embargo, todavía tengo mucha curiosidad sobre la razón del bloqueo muerto en su secuencia original (vea también mis comentarios a su pregunta). – RandomSeed

0

puede intentar utilizar

UPDATE uid_data SET uid = LAST_INSERT_ID(uid+1); 
SELECT LAST_INSERT_ID(); 

en una tabla como

CREATE TABLE `uid_data` (
    `uid` BIGINT(20) UNSIGNED NOT NULL 
) 
COLLATE='utf8_general_ci' 
ENGINE=MyISAM; 

Ésta es seguro para subprocesos y no bloqueará la tabla si es MyISAM (excepto durante la instrucción de actualización real).

2

Haga lo siguiente:

CREATE TABLE tickets 
(
    uid serial 
) 

continuación para obtener el siguiente UID:

BEGIN 
    INSERT INTO tickets VALUES (NULL); 
    SELECT LAST_INSERT_ID(); 
END 

serie uid es equivalente a

uid BIGINT(20) UNSIGNED NOT NULL PRIMARY KEY auto_increment 

Usted no debe experimentar ningún bloqueo de puertas con este enfoque y puede lanzar tantas conexiones como desee.

+0

Debo añadir, para mayor claridad, que LAST_INSERT_ID() tiene un alcance restringido, p. si 1000 de estas consultas se ejecutan simultáneamente, nunca hay riesgo de obtener el número incorrecto para una conexión diferente. –

Cuestiones relacionadas