2010-04-14 14 views
33

Nel nostro database di produzione, abbiamo fatto la seguente query batch pseudo-codice SQL in esecuzione ogni ora:Come migliorare INSERT INTO ... il comportamento di blocco SELEZIONE

INSERT INTO TemporaryTable 
    (SELECT FROM HighlyContentiousTableInInnoDb 
    WHERE allKindsOfComplexConditions are true) 

Ora questa domanda in sé non ha bisogno di essere veloce , ma ho notato che stava bloccando HighlyContentiousTableInInnoDb, anche se era solo leggendo da esso. Ciò che rendeva alcune altre query molto semplici richiede ~ 25 secondi (questo è il tempo richiesto da altre query).

Quindi ho scoperto che le tabelle InnoDB in questo caso sono effettivamente bloccate da un SELECT! http://www.mysqlperformanceblog.com/2006/07/12/insert-into-select-performance-with-innodb-tables/

Ma non mi piace molto la soluzione nell'articolo di selezionare in un OUTFILE, sembra un hack (i file temporanei sul filesystem sembrano schifosi). Altre idee? C'è un modo per fare una copia completa di una tabella InnoDB senza bloccarla in questo modo durante la copia. Quindi potrei semplicemente copiare lo HighlyContentiousTable in un'altra tabella e fare la query lì.

+0

Non ho chiesto qui, ma non ho trovato un modo. Sto usando un file di output per impedire i 20 minuti di blocco richiesti dalla mia query :) – therealsix

+1

Qualcuno sa se questo problema è effettivamente risolto in MySQL 5.1 come suggerisce l'articolo? – Artem

+1

No, MySQL 5.1.44 - stesso problema – clops

risposta

21

La risposta a questa domanda è molto più semplice ora: - Utilizza la replica basata sulle righe e il livello di isolamento Approvato.

Il blocco che stavi vivendo scompare. spiegazione

più lunga: http://harrison-fisk.blogspot.com/2009/02/my-favorite-new-feature-of-mysql-51.html

+6

'now' significa 5.1 – mayu

+0

Ho notato un calo di oltre il 50% nel tempo di esecuzione quando usato su una query di aggiornamento usando sub-select, bel bonus. – StrangeElement

+1

Appena aggiunto un bounty di +50 su questa domanda per una risposta più dettagliata e dettagliata di quanto sopra. – Ryan

1

Probabilmente è possibile utilizzare il comando Crea vista (vedere Create View Syntax). Per esempio,

Create View temp as SELECT FROM HighlyContentiousTableInInnoDb WHERE allKindsOfComplexConditions are true 

Dopo che si potrebbe usare la sua dichiarazione inserto con questa visione. Qualcosa di simile

INSERT INTO TemporaryTable (SELECT * FROM temp) 

Questa è solo la mia proposta.

+1

Funziona davvero? Pensavo che la Vista avrebbe fatto esattamente lo stesso lavoro ... – Artem

+0

Forse dovresti provare ... – smg

+0

se modifichi/leggi campi usando una vista, il tuo DBMS deve bloccare i campi così come tu li acceda direttamente. l'unica differenza è che non blocca l'intera riga (con tutte le colonne) ma solo le colonne utilizzate dalla vista. se le tue transazioni utilizzano colonne disgiunte, questo potrebbe davvero aiutarti. (chi diavolo ha dato -1 a questa risposta?) –

1

Disclaimer: Non ho molta esperienza con i database e non sono sicuro che questa idea sia fattibile. Per favore correggimi se non lo è.

Informazioni sulla configurazione di una tabella equivalente secondaria HighlyContentiousTableInInnoDb2 e sulla creazione di trigger AFTER INSERT nella prima tabella che mantiene aggiornata la nuova tabella con gli stessi dati. Ora dovresti essere in grado di bloccare HighlyContentiousTableInInnoDb2 e solo rallentare i trigger della tabella primaria, invece di tutte le query.

Potenziali problemi:

  • 2 x dati memorizzati
  • lavoro aggiuntivo per tutti gli inserimenti, aggiornamenti ed eliminazioni
  • potrebbe non essere transazionale suono
0

Il motivo per il blocco (readlock) è quello di proteggere la tua transazione di lettura non leggere i dati "sporchi" che una transazione parallela potrebbe attualmente scrivere. La maggior parte dei DBMS offre l'impostazione che gli utenti possono impostare e revocare leggere i blocchi di scrittura & manualmente. Questo potrebbe essere interessante per te se la lettura di dati sporchi non è un problema nel tuo caso.

Penso che non ci sia un modo sicuro per leggere da una tabella senza blocchi in un DBS con più transazioni.

Ma il seguente è un brainstorming: se lo spazio non è un problema, puoi pensare di eseguire due istanze della stessa tabella. HighlyContentiousTableInInnoDb2 per la tua costante transazione di lettura/scrittura e un HighlyContentiousTableInInnoDb2_shadow per il tuo accesso in batch. Forse puoi riempire la tabella shadow automatizzata tramite trigger/routine all'interno del tuo DBMS, che è più veloce e più intelligente di una transazione di scrittura aggiuntiva ovunque.

Un'altra idea è la domanda: tutte le transazioni devono accedere all'intera tabella? Altrimenti è possibile utilizzare le viste per bloccare solo le colonne necessarie. Se l'accesso continuo e l'accesso in batch sono disgiunti rispetto alle colonne, è possibile che non si blocchino l'un l'altro!

1

Se è possibile consentire alcune anomalie, è possibile modificare il LIVELLO DI ISOLAMENTO con il livello meno rigoroso: LEGGERE NON CORRETTO. Ma durante questo periodo a qualcuno è consentito leggere dalla tabella di destinazione. Oppure puoi bloccare manualmente la tabella di destinazione (presumo che mysql stia dando questa funzionalità?).

Oppure in alternativa è possibile utilizzare READ COMMITTED, che non deve bloccare anche la tabella di origine. Ma blocca anche le righe inserite nella tabella di destinazione fino al commit.

Vorrei scegliere il secondo.

+0

Questa è una direzione interessante. http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html La tabella di destinazione è comunque temporanea (non replicata), quindi penso che READ COMMITTED sia la strada da percorrere. Mi piacerebbe provare questo. – Artem

+1

Ora ho provato e sembra funzionare senza problemi! Io ora: IMPOSTAZIONE LIVELLO DI ISOLAMENTO LIVELLO LETTO IMPOSTATO; INSERT INTO TemporaryTable SELECT ... FROM HighlyContentiousTableInInnoDb; E questo non blocca HighlyContentiousTableInInnoDb. Non conosco alcuno svantaggio nell'usare questo a differenza del metodo SELECT INTO OUTFILE. Non riesco a replicare questo TemporaryTable, quindi penso che non dovrei avere problemi. – Artem

0

Non ho familiarità con MySQL, ma si spera che ci sia un equivalente ai livelli di isolamento della transazione Snapshot e Read committed snapshot in SQL Server. L'utilizzo di uno di questi dovrebbe risolvere il tuo problema.

+0

non ci sono istantanee in mysql –

3

Tutti coloro che utilizzano le tabelle InnoDB probabilmente ottenuto utilizzare al fatto InnoDB tabelle eseguono senza blocco legge, il che significa a meno che non si utilizzano alcuni modificatori come ad esempio BLOCCO IN MODALITA 'azione o per aggiornamento, selezionare le dichiarazioni non bloccano alcuna riga durante l'esecuzione.

Questo è generalmente corretto, tuttavia c'è un'eccezione notevole - INSERIRE nella tabella1 SELEZIONA * DA tabella2. Questa istruzione eseguirà la lettura di blocco (blocchi condivisi) per la tabella table2. Si applica anche alle tabelle simili con clausola where e join. È importante che le tabelle che vengono letti siano Innodb, anche se le scritture vengono eseguite nella tabella MyISAM.

Allora perché è stato questo fatto, essendo piuttosto male per MySQL prestazioni e concorrenza?

Il motivo è: la replica. In MySQL prima della 5.1 la replica è basata sull'istruzione, il che significa che le istruzioni replicate sul master dovrebbero avere lo stesso effetto dello slave. Se Innodb non bloccherebbe le righe nella tabella di origine, un'altra transazione potrebbe modificare la riga e il commit prima della transazione che sta eseguendo INSERT .. Istruzione SELECT. Questo farebbe sì che questa transazione venga applicata sullo slave prima dell'istruzione INSERT ... SELECT e possibilmente produca dati diversi da quelli master. Il blocco delle righe nella tabella di origine durante la lettura protegge da questo effetto mentre le altre transazioni modificano le righe prima che INSERT ... SELECT abbia la possibilità di accedervi, inoltre verrà modificato nello stesso ordine sullo slave. Se la transazione tenta di modificare la riga dopo che è stata acceduta e quindi bloccata da INSERT ... SELECT, la transazione dovrà attendere il completamento dell'istruzione per assicurarsi che venga eseguita sullo slave nell'ordine corretto. Diventa abbastanza complicato? Bene, tutto ciò che occorre sapere è che deve essere fatto prima che la replica funzioni correttamente in MySQL prima di 5.1.

In MySQL 5.1 questo e alcuni altri problemi dovrebbero essere risolti dalla replica basata su riga.Tuttavia, devo ancora sottoporlo a test di stress reali per vedere come funziona :)

Un'altra cosa da tenere in considerazione - INSERT ... SELECT esegue in realtà la lettura in modalità di blocco e quindi ignora parzialmente il controllo della versione e recupera l'ultima riga confermata . Quindi, anche se si opera in modalità REPEATABLE-READ, questa operazione verrà eseguita nella modalità READ-COMMITTED , dando potenzialmente un risultato diverso rispetto a quanto sarebbe offerto da SELECT. Questo a proposito si applica a SELECT .. LOCK IN SHARE MODE e SELECT ... FOR UPDATE pure.

Uno chiedo cosa è se non sto utilizzando la replica e il mio registro binario è disabilitato? Se non viene utilizzata la replica, è possibile abilitare l'opzione innodb_locks_unsafe_for_binlog, che allenterà i blocchi che Innodb imposta nell'esecuzione dell'istruzione, che generalmente offre una maggiore concorrenza. Tuttavia, come dice il nome, rende i lock non sicuri prima della replica e del recupero puntuale, quindi usa l'opzione innodb_locks_unsafe_for_binlog con cautela.

La nota che disabilita i registri binari non è sufficiente per attivare i blocchi rilassati. È necessario impostare innodb_locks_unsafe_for_binlog = 1 anche per . Ciò è possibile in modo che l'abilitazione diregistro binario non causi modifiche impreviste nel blocco dei problemi di comportamento e prestazioni . Puoi anche usare questa opzione con la replica a volte, se sai davvero cosa stai facendo. Vorrei raccomandare lo a meno che non sia realmente necessario, dato che potresti non sapere quali altri blocchi saranno rilassati nelle versioni future e in che modo lo influirà sulla tua replica.

+0

collegamento all'origine? https://www.percona.com/blog/2006/07/12/insert-into-select-performance-with-innodb-tables/ – nvartolomei

4

È possibile impostare il formato di binlog così:

SET GLOBAL binlog_format = 'ROW'; 

Edit my.cnf se si vuole fare se permanente:

[mysqld] 
binlog_format=ROW 

Set livello di isolamento per la sessione corrente prima di eseguire La tua richiesta:

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 
INSERT INTO t1 SELECT ....; 

Se questo non aiuta si dovrebbe provare a impostare server di livello di isolamento larga e non solo per la sessione corrente:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 

Edit my.cnf se si vuole fare se permanente:

[mysqld] 
transaction-isolation = READ-UNCOMMITTED 

È possibile modificare LEGGI-UNCOMMITTED READ -COMMITTED che è un livello di isolamento migliore.

0

Mi trovavo di fronte allo stesso problema utilizzando CREATE TEMPORARY TABLE ... SELECT ... con SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction.

In base alla query iniziale, il problema è stato risolto bloccando HighlyContentiousTableInInnoDb prima di avviare la query.

LOCK TABLES HighlyContentiousTableInInnoDb READ; 
INSERT INTO TemporaryTable 
    (SELECT FROM HighlyContentiousTableInInnoDb 
    WHERE allKindsOfComplexConditions are true) 
UNLOCK TABLES;