mysql 8.0 一条insert语句的具体执行流程分析(三)

本文详细解析MySQL 8.0.22版本中插入一条记录的具体流程,包括调用的接口顺序及各接口的功能说明,有助于理解MySQL内部机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码版本:mysql 8.0.22

编程语言:c++ && c++11 && c++14 && c++17

上一篇文章:mysql 8.0 一条insert语句的具体执行流程分析(二)_一缕阳光的博客-优快云博客

主要介绍了存储引擎部分,这个章节主要介绍insert一条语句时有二级索引时需要调用哪些接口,调用接口的顺序如下:

下面的接口基本上看源码就能理解,不再重述。

下面以聚集索引和二级索引为例,聚集索引调用接口如下:

| > row_ins_clust_index_entry
| | > row_ins_clust_index_entry_low
| | | > btr_cur_optimistic_insert
| | | | > btr_cur_ins_lock_and_undo
| | | | | > trx_undo_report_row_operation
| | | | | | > trx_undo_page_report_insert
| | | | | | | > trx_undo_page_set_next_prev_and_add
| | | | | | | | > trx_undof_page_add_undo_rec_log

| | | | > page_cur_tuple_insert
| | | | | > rec_convert_dtuple_to_rec
            > rec_convert_dtuple_to_rec_new
            > rec_convert_dtuple_to_rec_old
              > rec_convert_dtuple_to_rec_comp
            
| | | | | > page_cur_insert_rec_low
            > page_cur_insert_rec_write_log

(12)、row_ins_clust_index_entry : 插入entry到一个聚集索引,会调用13中的接口
(13)、row_ins_clust_index_entry_low : 尝试将entry插入到聚集索引中,忽略外键约束。
(14)、btr_cur_optimistic_insert : 插入一个page到一个索引tree
(15)、btr_cur_ins_lock_and_undo : 检查lock和写undo log
(16)、page_cur_tuple_insert : 将一条record追加到当前page,需要与17中的函数完成
(17)、page_cur_insert_rec_low : 将一条record追加到当前page,以非压缩方式写入

二级索引调用接口如下:

| > row_ins_sec_index_entry
| | > row_ins_sec_index_entry_low
| | | > btr_cur_search_to_nth_level
        > ibuf_should_try
| | | > row_ins_scan_sec_index_for_duplicate

(18)、row_ins_sec_index_entry : 在二级索引中插入entry
(19)、row_ins_sec_index_entry_low : 尝试将entry插入二级索引。如果找到一个字段完全相同的记录,则必须将另一个记录标记为已删除,然后再插入到二级索引中
(20)、btr_cur_search_to_nth_level : 在索引tree中给定的level上找到当前位置
(21)、row_ins_scan_sec_index_for_duplicate : 扫描给定索引项处的唯一非聚集索引,以确定该项的键值是否发生唯一性冲突。对可能重复的记录设置共享锁

最后,所有断点都被执行之后,使用bt命令将堆栈信息打印出来,由于断点太多了,打印了主要的一些函数,如下:

(gdb) bt
#0  btr_cur_optimistic_insert (flags=0, cursor=0x7fffe83d4ae0, offsets=0x7fffe83d4a78, 
    heap=0x7fffe83d4a70, entry=0x7fff300cb370, rec=0x7fffe83d4a80, big_rec=0x7fffe83d4a68, 
    thr=0x7fff3029c6f8, mtr=0x7fffe83d50a0)
    at /storage/innobase/btr/btr0cur.cc:2833
#1  0x000055555a4c7571 in row_ins_clust_index_entry_low (flags=0, mode=2, index=0x7fff3028a640, 
    n_uniq=1, entry=0x7fff300cb370, thr=0x7fff3029c6f8, dup_chk_only=false)
    at storage/innobase/row/row0ins.cc:2518
#2  0x000055555a4c945c in row_ins_clust_index_entry (index=0x7fff3028a640, entry=0x7fff300cb370, 
    thr=0x7fff3029c6f8, dup_chk_only=false)
    at /storage/innobase/row/row0ins.cc:3097
#3  0x000055555a4c9c14 in row_ins_index_entry (index=0x7fff3028a640, entry=0x7fff300cb370, 
    multi_val_pos=@0x7fff3029c3f8: 0, thr=0x7fff3029c6f8)
    at /storage/innobase/row/row0ins.cc:3289
#4  0x000055555a4ca26f in row_ins_index_entry_step (node=0x7fff3029c338, thr=0x7fff3029c6f8)
    at /storage/innobase/row/row0ins.cc:3425
#5  0x000055555a4ca616 in row_ins (node=0x7fff3029c338, thr=0x7fff3029c6f8)
    at /storage/innobase/row/row0ins.cc:3544
#6  0x000055555a4caa92 in row_ins_step (thr=0x7fff3029c6f8)
    at /storage/innobase/row/row0ins.cc:3668
#7  0x000055555a4ea804 in row_insert_for_mysql_using_ins_graph (mysql_rec=0x7fff302892c8 "\376\005", 
    prebuilt=0x7fff3029b830) at /storage/innobase/row/row0mysql.cc:1581
#8  0x000055555a4eae9a in row_insert_for_mysql (mysql_rec=0x7fff302892c8 "\376\005", 
    prebuilt=0x7fff3029b830) at /storage/innobase/row/row0mysql.cc:1711
#9  0x000055555a2a9ad9 in ha_innobase::write_row (this=0x7fff30287bf8, record=0x7fff302892c8 "\376\005")
    at /storage/innobase/handler/ha_innodb.cc:8629
#10 0x0000555558d4ace2 in handler::ha_write_row (this=0x7fff30287bf8, buf=0x7fff302892c8 "\376\005")
    at /sql/handler.cc:7831
#11 0x00005555590a14a7 in write_record (thd=0x7fff30001040, table=0x7fff30287270, info=0x7fffe83d64f0, 
    update=0x7fffe83d6570) at /sql/sql_insert.cc:2148
#12 0x000055555909cb7e in Sql_cmd_insert_values::execute_inner (this=0x7fff302becc8, thd=0x7fff30001040)
    at /sql/sql_insert.cc:633
#13 0x00005555589ff8fc in Sql_cmd_dml::execute (this=0x7fff302becc8, thd=0x7fff30001040)
    at /sql/sql_select.cc:612
#14 0x000055555897bd6e in mysql_execute_command (thd=0x7fff30001040, first_level=true)
    at /sql/sql_parse.cc:3420
#15 0x00005555589810b9 in dispatch_sql_command (thd=0x7fff30001040, parser_state=0x7fffe83d7b90)
    at /sql/sql_parse.cc:4988
#16 0x0000555558977226 in dispatch_command (thd=0x7fff30001040, com_data=0x7fffe83d8b80, 
    command=COM_QUERY) at /sql/sql_parse.cc:1836
#17 0x00005555589756df in do_command (thd=0x7fff30001040)
    at /sql/sql_parse.cc:1320
#18 0x0000555558b6ee3d in handle_connection (arg=0x55556006bbe0)
    at /sql/conn_handler/connection_handler_per_thread.cc:301
#19 0x000055555aa337b6 in pfs_spawn_thread (arg=0x55555fee7ae0)
    at /storage/perfschema/pfs.cc:2900
--Type <RET> for more, q to quit, c to continue without paging--
#20 0x00007ffff7f95609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#21 0x00007ffff76e0293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

到此为止,在MySQL中insert一条record的基本过程已经梳理完毕,流程还是比较清晰的,有数据库初级经验的基本都能理清楚。

接下来几篇文章主要讲述MySQL中redo.log中的mtr。

<think>嗯,用户想了解MySQL 8.0.25中使用INSERT INTO SELECT语句时涉及的next-key锁行为以及解决方法。我需要先回忆一下MySQL的锁机制,尤其是InnoDB的next-key锁。 首先,next-key锁是InnoDB在可重复读隔离级别下用来防止幻读的一种锁机制。它结合了记录锁和间隙锁,锁定一个范围。当执行INSERT INTO SELECT时,如果源表有数据被扫描,可能会对扫描到的范围加next-key锁,特别是当使用唯一索引或非唯一索引的时候。 然后,用户提到的版本是8.0.25,需要确认这个版本是否有相关的变化。根据引用[2],MySQL 8.0.22开始,DML操作读取grant表时不加读锁,但这里讨论的是普通的表操作。可能需要查看MySQL官方文档关于锁的部分。 接下来,INSERT INTO SELECT在复制数据时,如果目标表有触发器或外键约束,或者源表有索引扫描,可能会导致更长时间的锁定,从而引发性能问题甚至死锁。比如,当从大表中SELECT数据插入到另一个表时,如果源表的索引范围被锁定,其他事务可能被阻塞。 解决方法可能包括调整事务隔离级别,比如使用读已提交来减少间隙锁的使用,或者优化索引,减少扫描范围。另外,分批处理数据可能有助于减少锁的持有时间。引用[3]提到MySQL 8.0提高了可靠性,可能在锁管理方面有所优化,但具体8.0.25需要确认。 用户可能遇到的情况是,在执行INSERT INTO SELECT时,源表被长时间锁定,影响其他事务。需要解释这种情况下next-key锁的行为,并提供具体的解决方案,比如使用主键顺序扫描、降低隔离级别、拆分事务等。同时,结合用户提供的引用,可能引用相关版本文档来支持解答。</think>### MySQL 8.0.25中`INSERT INTO SELECT`的Next-Key锁行为分析 #### 1. Next-Key锁的基本机制 在InnoDB存储引擎中,`INSERT INTO SELECT`语句在**可重复读(REPEATABLE READ)**隔离级别下,若涉及索引范围扫描,可能触发Next-Key锁。Next-Key锁是记录锁(Record Lock)和间隙锁(Gap Lock)的组合,用于防止幻读(Phantom Read)[^2]。 例如: ```sql INSERT INTO target_table SELECT * FROM source_table WHERE indexed_column BETWEEN 100 AND 200; ``` 如果`source_table`的`indexed_column`是非唯一索引,InnoDB会对该范围内所有记录及间隙加锁,阻止其他事务插入符合此范围的新数据。 #### 2. 具体行为表现 - **主键/唯一索引扫描**:若`SELECT`条件使用主键或唯一索引精确匹配(如`WHERE id = 5`),仅加记录锁。 - **非唯一索引扫描**:若使用非唯一索引或全表扫描,会对扫描范围加Next-Key锁。 - **无索引扫描**:全表扫描时,可能对表中所有间隙加锁,导致严重锁竞争[^3]。 #### 3. 典型问题场景 ```sql -- 事务1(长时间运行) INSERT INTO orders_archive SELECT * FROM orders WHERE create_time >= '2023-01-01'; -- 事务2(被阻塞) INSERT INTO orders (create_time) VALUES ('2023-01-02'); ``` 若`orders.create_time`有非唯一索引,事务1会锁定`2023-01-01`之后的间隙,导致事务2被阻塞。 #### 4. 优化解决方案 ##### 方法1:调整隔离级别 将事务隔离级别改为**读已提交(READ COMMITTED)**,该级别下InnoDB不使用间隙锁: ```sql SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; BEGIN; INSERT INTO target_table SELECT ...; COMMIT; ``` 注意:需评估幻读风险是否可接受。 ##### 方法2:索引优化 为`SELECT`条件添加高效索引,减少锁定范围: ```sql ALTER TABLE source_table ADD INDEX idx_column(column); ``` ##### 方法3:分批处理 使用主键范围分批操作,减少单次锁持有时间: ```sql INSERT INTO target_table SELECT * FROM source_table WHERE id BETWEEN 1 AND 1000; INSERT INTO target_table SELECT * FROM source_table WHERE id BETWEEN 1001 AND 2000; ``` ##### 方法4:使用`LIMIT`与循环 通过应用层控制分批逻辑: ```python last_id = 0 batch_size = 1000 while True: cursor.execute(f""" INSERT INTO target_table SELECT * FROM source_table WHERE id > {last_id} ORDER BY id LIMIT {batch_size} """) last_id = get_last_insert_id() if affected_rows < batch_size: break ``` #### 5. 监控锁状态 通过以下命令实时监控锁行为: ```sql -- 查看当前锁信息 SELECT * FROM performance_schema.data_locks; -- 查看阻塞关系 SELECT * FROM sys.innodb_lock_waits; ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值