Explicação detalhada da relação entre timestamp do log binário e exec_time.
Autor: Li Xichao, DBA do Jiangsu Commercial Bank, responsável pela operação, manutenção e construção de banco de dados e middleware. Bom em MySQL, Python, Oracle e adora ciclismo, pesquisa e compartilhamento de tecnologia.
Produzido pela comunidade de código aberto Aikeson, o conteúdo original não pode ser utilizado sem autorização. Entre em contato com o editor e indique a fonte para reimpressão.
Este artigo tem cerca de 2.000 palavras e espera-se que leve 8 minutos para ser lido.
Visão geral
Recentemente, quando um sistema foi testado, descobriu-se que havia um atraso na sincronização mestre-escravo, e a causa do atraso foi confirmada através do binlog. Depois de usar o comando mysqlbinlog para analisá-lo, descobri que as informações nele contidas eram "um tanto vagas, mas não compreensíveis".
Por exemplo, para o seguinte trecho de binlog:
# at 449880
#240430 18:38:49 server id 345 end_log_pos 449967 CRC32 0xb3e8a02a GTID last_committed=13 sequence_number=14 rbr_only=yes original_committed_timestamp=1714473533138376 immediate_commit_timestamp=1714473539246294 transaction_length=446792
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
# original_commit_timestamp=1714473533138376 (2024-04-30 18:38:53.138376 CST)
# immediate_commit_timestamp=1714473539246294 (2024-04-30 18:38:59.246294 CST)
/*!80001 SET @@session.original_commit_timestamp=1714473533138376*//*!*/;
/*!80014 SET @@session.original_server_version=80027*//*!*/;
/*!80014 SET @@session.immediate_server_version=80027*//*!*/;
SET @@SESSION.GTID_NEXT= 'c0ac4587-6046-11ee-9fa7-001c42c92a7b:44'/*!*/;
# at 449967
#240430 18:38:16 server id 345 end_log_pos 450039 CRC32 0x0c7cb74e Query thread_id=16 exec_time=37 error_code=0
SET TIMESTAMP=1714473496/*!*/;
BEGIN
/*!*/;
/*!*/;
# at 450039
#240430 18:38:16 server id 345 end_log_pos 450098 CRC32 0xf9a84808 Table_map: `testdb`.`tb3` mapped to number 110
# at 450098
#240430 18:38:16 server id 345 end_log_pos 458309 CRC32 0xad84e9b0 Write_rows: table id 110
...
# at 896439
#240430 18:38:46 server id 345 end_log_pos 896498 CRC32 0x5cd7cd3b Table_map: `testdb`.`tb3` mapped to number 110
# at 896498
#240430 18:38:46 server id 345 end_log_pos 896540 CRC32 0x21b77031 Write_rows: table id 110 flags: STMT_END_F
...
### INSERT INTO `testdb`.`tb3`
### SET
### @1=131060 /* INT meta=0 nullable=0 is_null=0 */
### @2='c' /* VARSTRING(80) meta=80 nullable=1 is_null=0 */
# at 896540
#240430 18:38:49 server id 345 end_log_pos 896599 CRC32 0x6d6bf911 Table_map: `testdb`.`tb3` mapped to number 110
# at 896599
#240430 18:38:49 server id 345 end_log_pos 896641 CRC32 0xccd2fbb1 Write_rows: table id 110 flags: STMT_END_F
...
### INSERT INTO `testdb`.`tb3`
### SET
### @1=131061 /* INT meta=0 nullable=0 is_null=0 */
### @2='c' /* VARSTRING(80) meta=80 nullable=1 is_null=0 */
# at 896641
#240430 18:38:49 server id 345 end_log_pos 896672 CRC32 0xadb14b9d Xid = 85
Do binlog acima, podemos saber (P1):
#240430 18:38:16 执行 begin 开启了事务 (为便于表述,将时间字段名为timestamp)
#240430 18:38:16 执行了 tb3的insert 操作
#240430 18:38:46 执行了 tb3的insert 操作
#240430 18:38:49 执行了 tb3的insert 操作
#240430 18:38:49 执行了commit操作
Além disso (P2):
original_commit_timestamp=2024-04-30 18:38:53
immediate_commit_timestamp=2024-04-30 18:38:59
exec_time=37
Em relação às informações P2, são feitas as seguintes perguntas:
- Q1: O que significam os campos em P2? Como é calculado?
- Q2: Qual é a relação entre os campos de P2 e o carimbo de data/hora visto por P1?
- Q4: Como é obtido o carimbo de data/hora em P1? Especialmente em um ambiente mestre-escravo
Para tanto, por meio de verificação de testes e análise de código-fonte, exec_time
analisa-se a origem dos tempos de eventos comuns e no log binário, e resumi-se o relacionamento entre os campos.
A análise a seguir é baseada no MySQL 8.0 e os campos podem ser diferentes em versões diferentes.
Log binário do nó mestre
1. Evento GTID
carimbo de data/hora
Para o nó principal: Se não houver instruções especiais, o Evento deve obter o carimbo de data / hora mais recente ( ) timestamp
na posição inicial de cada execução do thread e atribuí-lo quando o objeto Event for produzido .dispatch_command()
thd->start_time
thd->start_time
Log_event::common_header->when
As informações da pilha principal são as seguintes:
|-handle_connection (./sql/conn_handler/connection_handler_per_thread.cc:302)
|-do_command (./sql/sql_parse.cc:1343)
|-dispatch_command (./sql/sql_parse.cc:1922)
// 设置 thd->start_time
|-thd->set_time()
|-my_micro_time_to_timeval(start_utime, &start_time)
|-dispatch_sql_command (./sql/sql_parse.cc:5135)
|-mysql_execute_command (./sql/sql_parse.cc:3518)
|-Sql_cmd_dml::execute (./sql/sql_select.cc:579)
……
|-Table_map_log_event the_event(this, table, table->s->table_map_id,is_transactional)
……
|-Rows_log_event *const ev = new RowsEventT(this, table, table->s->table_map_id, )
……
|-Xid_log_event end_evt(thd, xid)
imediato_commit_timestamp/original_commit_timestamp
immediate_commit_timestamp
O carimbo de data/hora obtido é o horário de envio e o nó mestre original_commit_timestamp
é igual a immediate_commit_timestamp
.
|-error = trx_cache.flush(thd, &trx_bytes, wrote_xid)
|-Transaction_ctx *trn_ctx = thd->get_transaction()
|-trn_ctx->sequence_number = mysql_bin_log.m_dependency_tracker.step()
|-if (trn_ctx->last_committed == SEQ_UNINIT): trn_ctx->last_committed = trn_ctx->sequence_number - 1
|-if (!error): if ((error = mysql_bin_log.write_transaction(thd, this, &writer)))
|-int64 sequence_number, last_committed
|-m_dependency_tracker.get_dependency(thd, sequence_number, last_committed)
|-thd->get_transaction()->last_committed = SEQ_UNINIT
|-ulonglong immediate_commit_timestamp = my_micro_time()
//|-ulonglong original_commit_timestamp = thd->variables.original_commit_timestamp
|-ulonglong original_commit_timestamp = immediate_commit_timestamp
|-uint32_t trx_immediate_server_version = do_server_version_int(::server_version)
|-Gtid_log_event gtid_event(thd, cache_data->is_trx_cache(), last_committed, sequence_number,
cache_data->may_have_sbr_stmts(), original_commit_timestamp,
immediate_commit_timestamp, trx_original_server_version,
trx_immediate_server_version)
2. COMEÇAR Evento
carimbo de data/hora
Nota: Para o evento BEGIN do nó mestre, timestamp
não é o carimbo de data/hora quando BEGIN é executado, mas a primeira operação de modificação. Após concluir a modificação da primeira linha de dados na camada InnoDB, o evento Table_map é gerado e gravado. Antes de gerar o evento Table_map, se o cache binlog de toda a transação estiver vazio neste momento, a operação será obtida imediatamente thd->start_time
e o evento BEGIN real será gerado.
hora_executiva
Ao mesmo tempo, para o nó mestre, exec_time
é obtido obtendo o último timestamp - Evento BEGIN no processo de geração do Evento BEGIN timestamp
.
tempo_exec = A - B
- R: O momento em que o evento BEGIN é gerado após a execução do primeiro SQL modificado e a conclusão da operação de modificação da primeira linha (gravação/atualização/exclusão).
- B: O tempo de execução inicial do primeiro SQL modificado (thd->start_time)
A pilha interna e a sequência de execução são as seguintes:
3. Evento Table_map
4. Escreva o evento
5. Evento XID
6. Resumo do nó mestre
- Além do evento BEGIN,
timestamp
é o horário de início da primeira operação que precisa ser gravada no binlog (como: escrever/atualizar/excluir); - Para outros Eventos,
timestamp
é a hora de início em que a instrução SQL é executada; immediate_commit_timestamp/original_commit_timestamp
Esse é o carimbo de data/hora quando enviado;- tempo_exec = A - B
- R: O momento em que o evento BEGIN é gerado após a execução do primeiro SQL modificado e a conclusão da operação de modificação da primeira linha (gravação/atualização/exclusão).
- B: O tempo de execução inicial do primeiro SQL modificado (thd->start_time)
Log binário do nó escravo
1. Evento GTID
carimbo de data/hora
No nó escravo: Para Evento GTID, o MySQL não obterá o carimbo de data e hora do Evento GTID/XID do nó mestre ao analisar o evento, portanto, "herdará" o carimbo de data e hora da operação anterior da transação. Os carimbos de data/hora de todas as operações de modificação no nó escravo vêm do carimbo de data/hora quando o nó mestre executa a operação. Portanto, o horário do Evento GTID/XID do nó escravo é o carimbo de data/hora da última operação de modificação do nó mestre.
imediato_commit_timestamp/original_commit_timestamp
immediate_commit_timestamp
Obtenha o carimbo de data/hora do horário de envio do nó escravo. Obtido original_commit_timestamp
do evento GTID original_commit_timestamp
, ou seja, o nó principal envia a operação timestamp
.
As informações da pilha principal são as seguintes:
|-handle_slave_worker (./sql/rpl_replica.cc:5891)
|-slave_worker_exec_job_group (./sql/rpl_rli_pdb.cc:2549)
|-Slave_worker::slave_worker_exec_event (./sql/rpl_rli_pdb.cc:1760)
|-Xid_apply_log_event::do_apply_event_worker (./sql/log_event.cc:6179)
|-Xid_log_event::do_commit (./sql/log_event.cc:6084)
|-trans_commit (./sql/transaction.cc:246)
|-ha_commit_trans (./sql/handler.cc:1765)
|-MYSQL_BIN_LOG::commit (./sql/binlog.cc:8170)
|-MYSQL_BIN_LOG::ordered_commit (./sql/binlog.cc:8789)
|-MYSQL_BIN_LOG::process_flush_stage_queue (./sql/binlog.cc:8326)
|-MYSQL_BIN_LOG::flush_thread_caches (./sql/binlog.cc:8218)
|-binlog_cache_mngr::flush (./sql/binlog.cc:1099)
|-binlog_cache_data::flush (./sql/binlog.cc:2098)
|-MYSQL_BIN_LOG::write_transaction (./sql/binlog.cc:1586)
// 生成并写入 GTID event
|-ulonglong immediate_commit_timestamp = my_micro_time()
|-if (original_commit_timestamp == UNDEFINED_COMMIT_TIMESTAMP){...}
|-Gtid_log_event gtid_event(thd, cache_data->is_trx_cache(), last_committed, sequence_number,
cache_data->may_have_sbr_stmts(), original_commit_timestamp, immediate_commit_timestamp, trx_original_server_version,
trx_immediate_server_version)
oficial
imediato_commit_timestamp - original_commit_timestamp = A + B + C
- A = O tempo que leva para o nó mestre transferir o log binário para o nó escravo
- B = O tempo que leva para reproduzir o log binário do nó escravo
- C = tempo de atraso/interrupção de sincronização
2. COMEÇAR Evento
carimbo de data/hora
Aqui timestamp
vem o evento BEGIN do nó principal timestamp
. Quando for realmente executado, o Evento BEGIN será obtido timestamp
e atribuído a thd->start_time/thd->user_time
. Ao gerar um objeto Event a partir de um nó, continue thd->start_time
obtendo o carimbo de data/hora de .
hora_executiva
Então, o nó escravo exec_time
ainda obtém o carimbo de data/hora mais recente no processo de geração do Evento BEGIN timestamp
(observe que timestamp
o tempo de execução inicial do SQL modificado do nó mestre) é obtido.
As informações da pilha principal são as seguintes:
|-handle_slave_worker (./sql/rpl_replica.cc:5891)
|-slave_worker_exec_job_group (./sql/rpl_rli_pdb.cc:2549)
|-Slave_worker::slave_worker_exec_event (./sql/rpl_rli_pdb.cc:1760)
|-Log_event::do_apply_event_worker (./sql/log_event.cc:1083)
|-Query_log_event::do_apply_event (./sql/log_event.cc:4443)
|-Query_log_event::do_apply_event (./sql/log_event.cc:4606)
// 设置 user_time=start_time=ev.common_header->when
|-thd->set_time(&(common_header->when))
// query_arg="BEGIN"
|-thd->set_query(query_arg, q_len_arg)
...
oficial
tempo_exec = A + B + C + D
- A = nó mestre, todo o tempo da transação
- B = tempo de transmissão do log binário
- C = tempo de atraso/interrupção de sincronização (provavelmente - maior)
- D = Conclua a primeira linha de modificação de dados do nó
original_commit_timestamp - timestamp = do evento de início indica o consumo de tempo real de toda a transação no nó mestre ([primeira modificação principal] para [início de confirmação principal]).
3. Evento Table_map
4. Escreva o evento
5. Evento XID
6. Da seção do nó
- Exceto para Evento GTID/XID, os carimbos de data/hora de outros eventos vêm dos eventos do nó mestre;
- O evento GTID/XID é
timestamp
o horário de início da última operação de modificação do nó mestre; - O evento GTID
original_commit_timestamp
vem do nó mestre eimmediate_commit_timestamp
é o carimbo de data/hora mais recente; - tempo_exec = A - B
- A = O carimbo de data/hora mais recente do evento BEGIN gerado a partir do nó
- B = O nó mestre inicia o tempo para executar a primeira operação DML
Conclusão
Neste ponto, os carimbos de data e hora no binlog exec_time
foram basicamente resolvidos. Amigos interessados podem voltar ao início do artigo e ver se há respostas para Q1-Q3.
Por fim, recomenda-se que os leitores simulem vários casos para ter um entendimento mais profundo dos campos relevantes, para que possam se sentir mais confortáveis ao usar o binlog para analisar problemas de sincronização mestre-escravo.
As informações acima são apenas para comunicação. O nível do autor é limitado. Se houver alguma deficiência, sinta-se à vontade para comunicar na área de comentários.
Para mais artigos técnicos, visite: https://opensource.actionsky.com/
Sobre SQLE
SQLE é uma plataforma abrangente de gerenciamento de qualidade de SQL que cobre auditoria e gerenciamento de SQL desde o desenvolvimento até ambientes de produção. Ele oferece suporte aos principais bancos de dados de código aberto, comerciais e domésticos, fornece recursos de automação de processos para desenvolvimento, operação e manutenção, melhora a eficiência online e melhora a qualidade dos dados.
Obter SQLE
tipo | endereço |
---|---|
Repositório | https://github.com/actiontech/sqle |
documento | https://actiontech.github.io/sqle-docs/ |
lançar notícias | https://github.com/actiontech/sqle/releases |
Documentação de desenvolvimento de plug-in de auditoria de dados | https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse |