2012-07-05 6 views
6

Ho una procedura memorizzata che genera UID da una tabella "ticket", ma sotto carico sto ottenendo molti deadlock. Sto chiamando questa procedura molte volte da più connessioni simultanee ogni volta che il mio compito ha bisogno di un nuovo UID.Blocchi MySQL con stored procedure che generano 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 

ho considerato utilizzando:

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

Comunque io non ero sicuro se questo sarebbe al sicuro con connessioni simultanee in quanto non c'è blocco, a differenza della prima procedura con il SELECT FOR UPDATE.

Ecco la tabella:

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 |    | 
+-------+---------------------+------+-----+---------+----------------+ 

Ho installato per la lettura impegnata isolamento delle transazioni:

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

Ecco quello che sto tornando da 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 Sarei grato se qualcuno potesse spiegare cosa sta succedendo e come possono essere evitati.

+0

Per informazioni: il deadlock si verifica anche quando si utilizza questa semplice sequenza: 'START TRANSACTION; SELECT uid FROM uid_data FOR UPDATE; UPDATE uid_data SET uid = uid +1 [[deadlock possibile qui]]; COMMIT; '(quindi non ha nulla a che fare con la clausola' ON DUPLICATE'). Tuttavia, non si verifica un deadlock con un livello di isolamento di 'REPEATABLE READ;'. Ancora non so cosa concludere da questo punto. – RandomSeed

risposta

0

Un deadlock si verifica in questo scenario:

Operazione 1: richiede un blocco (SELECT...FOR UPDATE) e acquisisce

transazione 2: richiede un blocco (SELECT...FOR UPDATE) e deve attendere

transazione 1: cerca di inserire, colpisce un duplicato, quindi aggiornamenti (INSERT...ON DUPLICATE KEY UPDATE) => deadlock

Non sono troppo sicuro del ri presto, ho il sospetto che abbia qualcosa a che fare con lo ON DUPLICATE KEY UPDATE. Sto ancora indagando e tornerò se lo scoprirò.

[Edit] Un deadlock si verifica anche 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 

Che dire di questo:

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

Si potrebbe cadere il stub colum del tutto. L'unico inconveniente è che devi inizializzare il tuo uid_data con una riga.

+0

La procedura memorizzata revisionata gestisce la concorrenza con qualsiasi tipo di blocco? – Sencha

+0

@Sencha Sì, 'UPDATE' è atomico e blocca anche la/e riga/e fino alla fine della transazione. Tuttavia, sono ancora molto curioso del motivo del blocco morto nella sequenza originale (vedi anche i miei commenti alla tua domanda). – RandomSeed

0

È possibile provare a utilizzare

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

su un tavolo come

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

Questo è thread-safe e non bloccherà il tavolo se è MyISAM (tranne durante la dichiarazione di aggiornamento vero e proprio).

2

Fate questo:

CREATE TABLE tickets 
(
    uid serial 
) 

Quindi per ottenere il prossimo uid:

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

seriale uid è equivalente a

uid BIGINT(20) UNSIGNED NOT NULL PRIMARY KEY auto_increment 

Non si dovrebbe verificarsi alcun deadlock con questo approccio e può lanciare tutte le connessioni che vuoi.

+0

Dovrei aggiungere per chiarezza, che LAST_INSERT_ID() è limitato all'ambito - ad es. se 1000 di queste query vengono eseguite contemporaneamente, non c'è mai il rischio di ottenere il numero sbagliato per una connessione diversa. –