MySQL Metadata Locks

I metadata lock sono le protezioni che regolano la modifica concorrente alle strutture dati MySQL come le tabelle. Sono molto importanti perche' molte operazioni di DDL in MySQL sono sospensive.

Dalla versione 5.7 e' possibile controllare in modo semplice i metadata lock utilizzando la vista performance_schema.metadata_locks ed in questo breve documento vediamo come.

Nota: i lock a livello di riga e di tabella che proteggono i dati, e non le strutture dati come i metadata lock, sono un altro argomento e non sono trattati in questa paginetta.

Introduzione

I metadata lock sono le protezioni che regolano la modifica concorrente agli oggetti MySQL come tabelle, indici e partizioni. In pratica regolano l'accesso alle tabelle quando vengono effettuati comandi di DDL come le ALTER TABLE e simili.

Non e' mai stato semplice con MySQL individuare i lock attivi... come ben sa chi ha utilizzato il famigerato:
 SHOW ENGINE INNODB STATUS \G

Il comando da' una risposta molto esaustiva... ma certo di non facile lettura.
Per fortuna, versione dopo versione, sono state introdotti in MySQL strumenti per il controllo sempre piu' sofisticati ed utili.
Dalla versione 5.7 e' possibile controllare in modo semplice i metadata lock utilizzando una specifica vista. La vista e' contenuta nel performance_schema e si chiama metadata_locks.

Abilitazione

Anche se la vista metadata_locks e' sempre presente in 5.7 per default e' vuota perche' non e' abilitata la collezione dei dati.

La configurazione del performace schema si effettua su due tabelle: setup_consumers e setup_instruments. Ecco i comandi da utilizzare:

use performance_schema;

-- Non strettamente necessario: gia' abilitato per default in 5.7
UPDATE setup_consumers
   SET ENABLED = 'YES'
 WHERE NAME = 'global_instrumentation';

-- Da abilitare in 5.7, gia' abilitato per default in 8.0
UPDATE setup_instruments
   SET ENABLED = 'YES'
 WHERE NAME = 'wait/lock/metadata/sql/mdl';

A questo punto sono abilitati gli eventi che consentono di popolare la vista metadata_locks.

Metadata locks

Utilizzare la vista sui metadata locks e' facile:

SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS,
       THREAD_ID, PROCESSLIST_ID, PROCESSLIST_INFO
  FROM performance_schema.metadata_locks
  INNER JOIN performance_schema.threads ON THREAD_ID = OWNER_THREAD_ID
 WHERE PROCESSLIST_ID <> CONNECTION_ID();

Utilizzando un join con i thread sono in questo modo visibili tutti i metadata lock attivi ed i dettagli sulle relative sessioni di database.

Volendo selezionare solo i lock in blocco basta aggiungere una condizione:

SELECT OBJECT_TYPE, OBJECT_SCHEMA, OBJECT_NAME, LOCK_TYPE, LOCK_STATUS,
       THREAD_ID, PROCESSLIST_ID, PROCESSLIST_INFO
  FROM performance_schema.metadata_locks
  INNER JOIN performance_schema.threads ON THREAD_ID = OWNER_THREAD_ID
 WHERE PROCESSLIST_ID <> CONNECTION_ID()
   AND lock_status<>'GRANTED';

Analizzare i metadata lock e' effettivamente molto facile in 5.7!

Configurazione Performance schema

Riprendiamo le tabelle di configurazione del performance schema...
Prima la setup_consumers:

mysql> select * from setup_consumers;
+----------------------------------+---------+
| NAME                             | ENABLED |
+----------------------------------+---------+
| events_stages_current            | NO      |
| events_stages_history            | NO      |
| events_stages_history_long       | NO      |
| events_statements_current        | YES     |
| events_statements_history        | YES     |
| events_statements_history_long   | NO      |
| events_transactions_current      | NO      |
| events_transactions_history      | NO      |
| events_transactions_history_long | NO      |
| events_waits_current             | NO      |
| events_waits_history             | NO      |
| events_waits_history_long        | NO      |
| global_instrumentation           | YES     |
| thread_instrumentation           | YES     |
| statements_digest                | YES     |
+----------------------------------+---------+
15 rows in set (0.00 sec)

Quindi la setup_instruments:

mysql> select * from  setup_instruments;
+--------------------------------------------------------------------------------+---------+-------+
| NAME                                                                           | ENABLED | TIMED |
+--------------------------------------------------------------------------------+---------+-------+
| wait/synch/mutex/sql/TC_LOG_MMAP::LOCK_tc                                      | NO      | NO    |
| wait/synch/mutex/sql/LOCK_des_key_file                                         | NO      | NO    |
| wait/synch/mutex/sql/MYSQL_BIN_LOG::LOCK_commit                                | NO      | NO    |
| wait/synch/mutex/sql/MYSQL_BIN_LOG::LOCK_commit_queue                          | NO      | NO    |
| wait/synch/mutex/sql/MYSQL_BIN_LOG::LOCK_done                                  | NO      | NO    |
| wait/synch/mutex/sql/MYSQL_BIN_LOG::LOCK_flush_queue                           | NO      | NO    |
| wait/synch/mutex/sql/MYSQL_BIN_LOG::LOCK_index                                 | NO      | NO    |
...
| wait/synch/cond/myisam/keycache_thread_var::suspend                            | NO      | NO    |
| wait/io/file/sql/map                                                           | YES     | YES   |
| wait/io/file/sql/binlog                                                        | YES     | YES   |
| wait/io/file/sql/binlog_cache                                                  | YES     | YES   |
...
| wait/io/table/sql/handler                                                      | YES     | YES   |
| wait/lock/table/sql/handler                                                    | YES     | YES   |
| stage/sql/After create                                                         | NO      | NO    |
| stage/sql/allocating local table                                               | NO      | NO    |
...
| wait/io/socket/sql/client_connection                                           | NO      | NO    |
| idle                                                                           | YES     | YES   |
| memory/performance_schema/mutex_instances                                      | YES     | NO    |
| memory/performance_schema/rwlock_instances                                     | YES     | NO    |
...
| wait/lock/metadata/sql/mdl                                                     | YES     | NO    |
+--------------------------------------------------------------------------------+---------+-------+
1021 rows in set (0.00 sec)

Il numero di righe presenti nelle tabelle setup_consumers e setup_instruments dipendono dalle versioni di MySQL e dalla configurazione presente.
Ad esempio in setup_consumers vi sono 12 righe in 5.6 e 15 in 5.7 mentre in setup_instruments vi sono 554 rows in 5.6 ed oltre 1000 in 5.7.

Importante: anche se tecnicamente e' possibile abilitare ogni possibile controllo impostando a YES le colonne ENABLED e TIMED questo comporterebbe un significativo degrado prestazionale. Le impostazioni di default sono un buon compromesso che consente un monitoraggio dettagliato della base dati con un impatto quasi non rilevabile sulle prestazioni.
In caso di problemi e' possibile attivare ulteriori consumer ed instrument ma, una volta eseguita la diagnosi, conviene reimpostarli al valore di default.

Su MySQL e sul fork Percona il performance schema e' abilitato per default. Sul fork MariaDB ed i vari servizi MySQL Amazon RDS per default il performance schema non e' abilitato.

A mio avviso l'abilitazione del performance schema e la configurazione suggerita in questa pagina (abilitazione di wait/lock/metadata/sql/mdl) non ha un impatto significativo e deve essere mantenuta negli ambienti di produzione. In effetti nella versione MySQL 8.0 e' stata poi inserita tra i default.

Varie ed eventuali

Le differenti versioni di MySQL sono riportate in questo documento.


Titolo: MySQL Metadata Locks
Livello: Esperto (4/5)
Data: 14 Febbraio 2018
Versione: 1.0.0 - 14 Febbraio 2018 ❤️ San Valentino
Autore: mail [AT] meo.bogliolo.name