走在路途 發表於 2025-10-20 12:10:00

案例分析:MySQL 并行复制竟然比单线程慢?

<p data-tool="mdnice编辑器"><span>最近碰到一个 case,发现在特定场景下,并行复制竟然比单线程复制要慢。</span></p>
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">现象</span></h1>
<p data-tool="mdnice编辑器"><span>从某个时间点开始,从库的复制延迟持续增加,且没有下降的趋势。</span></p>
<p data-tool="mdnice编辑器"><span>数据库版本:8.0.40,事务隔离级别 RC(<span>Read Committed),并行重放线程数(replica_parallel_workers)为 8。</span></span></p>
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">分析过程</span></h1>
<p data-tool="mdnice编辑器"><span>通过<code><span>show slave status\G</span></code><span>查看,发现<code><span>Relay_Master_Log_File</span></code><span>和<code><span>Exec_Master_Log_Pos</span></code><span>都在变化,只不过变化得比较慢。</span></span></span></span></p>
<p data-tool="mdnice编辑器"><span>刚开始怀疑是主库写入量较大导致的,后来通过<code><span>mysql-binlog-time-extractor</span></code><span>(具体用法可参考:分享一个 MySQL binlog 分析小工具)分析,发现主库的写入量在刚开始出现延迟时(2025-09-01 09:30)并不大,反倒是写入量大的时间段(2025-09-01 04:57:53 - 2025-09-01 05:02:42)没有出现延迟。</span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>+--------------------+------------------------+---------------------+---------------------+-----------+------------------------------------+<span><br><span>| Log_name &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | File_size &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| Start_time &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| End_time &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| Duration &nbsp;| GTID &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<span><br><span>+--------------------+------------------------+---------------------+---------------------+-----------+------------------------------------+<span><br><span>| binary-log.005565 &nbsp;| 1302499830 (1.21 GB) &nbsp; | 2025-09-01 04:57:53 | 2025-09-01 04:58:22 | 00:00:29 &nbsp;| 1284696693-1284699126 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| binary-log.005566 &nbsp;| 1105002721 (1.03 GB) &nbsp; | 2025-09-01 04:58:22 | 2025-09-01 04:58:23 | 00:00:01 &nbsp;| 1284699127-1284699312 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| binary-log.005567 &nbsp;| 1273545902 (1.19 GB) &nbsp; | 2025-09-01 04:58:23 | 2025-09-01 05:02:33 | 00:04:10 &nbsp;| 1284699313-1284728539 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| binary-log.005568 &nbsp;| 1287820910 (1.20 GB) &nbsp; | 2025-09-01 05:02:33 | 2025-09-01 05:02:42 | 00:00:09 &nbsp;| 1284728540-1284729282 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>&nbsp; ...<span><br><span>| binary-log.005633 &nbsp;| &nbsp; 58514304 (55.80 MB) &nbsp;| 2025-09-01 09:12:53 | 2025-09-01 09:17:53 | 00:05:00 &nbsp;| 1286735216-1286786118 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| binary-log.005634 &nbsp;| &nbsp; 58955596 (56.22 MB) &nbsp;| 2025-09-01 09:17:53 | 2025-09-01 09:22:53 | 00:05:00 &nbsp;| 1286786119-1286834568 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| binary-log.005635 &nbsp;| &nbsp; 71508778 (68.20 MB) &nbsp;| 2025-09-01 09:22:53 | 2025-09-01 09:27:53 | 00:05:00 &nbsp;| 1286834569-1286880281 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| binary-log.005636 &nbsp;| &nbsp;107107179 (102.15 MB) | 2025-09-01 09:27:53 | 2025-09-01 09:32:53 | 00:05:00 &nbsp;| 1286880282-1286942223 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| binary-log.005637 &nbsp;| &nbsp;530205055 (505.64 MB) | 2025-09-01 09:32:53 | 2025-09-01 09:37:53 | 00:05:00 &nbsp;| 1286942224-1287246612 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| binary-log.005638 &nbsp;| &nbsp;546754562 (521.43 MB) | 2025-09-01 09:37:53 | 2025-09-01 09:42:53 | 00:05:00 &nbsp;| 1287246613-1287562930 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| binary-log.005639 &nbsp;| &nbsp;528677634 (504.19 MB) | 2025-09-01 09:42:53 | 2025-09-01 09:47:53 | 00:05:00 &nbsp;| 1287562931-1287868985 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>+--------------------+------------------------+---------------------+---------------------+-----------+------------------------------------+<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>查看该实例的错误日志,发现有大量的锁等待超时报错。</span></p>
<p data-tool="mdnice编辑器"><span>需要注意的是,这个实例的事务隔离级别是 RC。在该级别下,MySQL 通常只会加记录锁。此外,该实例启用了 WRITESET 并行复制,MySQL 会根据事务修改的主键或唯一索引来判断是否可并行执行。换句话说,如果两个事务在主键或唯一索引上存在冲突,它们将无法并行重放。理论上,在这种机制组合下,从库在重放过程不应发生锁等待超时。</span></p>
<p data-tool="mdnice编辑器"><span>随后使用<code><span>binlog_summary.py</span></code><span>(具体用法可参考:Binlog分析利器-binlog_summary.py)对延迟开始时段的四个 binlog 文件( binary-log.005636 ~ binary-log.005639 )进行了分析,发现这些 binlog 的操作模式十分相似:操作次数排名前两位的均为同一张表<code><span>biz_schema.tbl_product_service_mapping01</span></code><span>的&nbsp;<strong>DELETE 与 INSERT</strong><span>&nbsp;操作。</span></span></span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code># python3 binlog_summary.py -f binary-log.005636.txt -c opr --new<span><br><span>TABLE_NAME &nbsp; &nbsp; &nbsp; &nbsp; DML_TYPE &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; NUMS &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;<span><br><span>biz_schema.tbl_product_service_mapping01 INSERT &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 71271 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span><br><span>biz_schema.tbl_product_service_mapping01 DELETE &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 67434 &nbsp;<span><br><span>... &nbsp; &nbsp;<span><br></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>写了个简单的脚本测试了下,发现对于相同的唯一索引值,<code><span>INSERT</span></code><span>操作总是出现在对应的<code><span>DELETE</span></code><span>操作之后,于是写了个脚本将&nbsp;<code><span>DELETE</span></code><span>操作涉及的记录提取出来并插入到测试库中,然后将相关 binlog 当作 relay log 进行重放。</span></span></span></span></p>
<p data-tool="mdnice编辑器"><span>为了排除其它表的干扰,在重放时设置了<code><span>replicate-do-table = biz_schema.tbl_product_service_mapping01</span></code><span>,只重放这一张表。</span></span></p>
<p data-tool="mdnice编辑器"><span>下面是具体的重放步骤:</span></p>
<p data-tool="mdnice编辑器"><span>1. 初始化 relay log:</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code>CHANGE<span>&nbsp;MASTER<span>&nbsp;TO<span>&nbsp;MASTER_HOST='dummy'<span>;<span><br>STOP<span>&nbsp;SLAVE<span>;<span><br>RESET<span>&nbsp;SLAVE<span>&nbsp;ALL<span>;<span><br></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>执行上述命令后,MySQL 会在当前数据目录下生成两个文件:</span></p>
<ul class="list-paddingleft-1">
<li><code><span>instance-20250903-0701-relay-bin.000001</span></code><span>(第一个 relay log 文件)</span></li>
<li><code><span>instance-20250903-0701-relay-bin.index</span></code><span>(relay log 索引文件)</span></li>
</ul>
<p data-tool="mdnice编辑器"><span>其中,instance-20250903-0701 是主机名。</span></p>
<p data-tool="mdnice编辑器"><span>2. 替换掉 relay log:</span></p>
<p data-tool="mdnice编辑器"><span>用 binary-log.005636 替换掉 instance-20250903-0701-relay-bin.000001,并修改该文件的属主。</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code># cp binary-log.005636 /data/mysql/3306/data/instance-20250903-0701-relay-bin.000001<span><br># chown mysql.mysql /data/mysql/3306/data/instance-20250903-0701-relay-bin.000001<span><br></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>3. 启动 SQL 线程进行重放:</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code>CHANGE<span>&nbsp;MASTER<span>&nbsp;TO<span>&nbsp;RELAY_LOG_FILE='instance-20250903-0701-relay-bin.000001'<span>, RELAY_LOG_POS=1<span>, MASTER_HOST='dummy'<span>;<span><br>START<span>&nbsp;SLAVE<span>&nbsp;SQL_THREAD;<span><br></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>结果发现能成功重放,且重放过程中未出现任何报错。</span></p>
<p data-tool="mdnice编辑器"><span>测试了三次,重放时间分别为 362.74s、352.69s、361.75s,平均耗时 359.06 秒。</span></p>
<p data-tool="mdnice编辑器"><span>每次重放过程中,错误日志中都出现了多次锁等待超时错误。</span></p>
<pre data-tool="mdnice编辑器"><span class="wx_imgbc_placeholder" data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg"><code><span>2025-09-21T07:53:45.257279-00:00 260 Slave SQL for channel '': Worker 5 failed executing transaction '9206ff59-2d95-4a02-88cf-04d97adfdd65:1286917678' at master log , end_log_pos 63251784; Could not&nbsp;execute<span>&nbsp;Write_rows&nbsp;event<span>&nbsp;on<span>&nbsp;table<span>&nbsp;biz_schema.tbl_product_service_mapping01;&nbsp;Lock<span>&nbsp;wait<span>&nbsp;timeout<span>&nbsp;exceeded; try restarting transaction, Error_code: 1205;&nbsp;handler<span>&nbsp;error<span>&nbsp;HA_ERR_LOCK_WAIT_TIMEOUT; the event's master log FIRST, end_log_pos 63251784, Error_code: MY-001205<span><br></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>很显然,锁等待超时是并行重放导致的。</span></p>
<p data-tool="mdnice编辑器"><span>如果是单线程重放,就能规避这个问题,于是将<code><span>replica_parallel_workers</span></code><span>设置为 1,重新执行相同的测试,三次重放时间分别为 82.39s、83.40s、83.43s,平均仅 83.07 秒。</span></span></p>
<p data-tool="mdnice编辑器"><span>想不到,单线程重放竟然比多线程快了四倍多。</span></p>
<p data-tool="mdnice编辑器"><span>接下来,重点分析下锁等待超时问题。</span></p>
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">为什么会出现锁等待?</span></h1>
<p data-tool="mdnice编辑器"><span>以下是出现锁等待时,<code><span>sys.innodb_lock_waits</span></code><span>的输出:</span></span></p>
<pre data-tool="mdnice编辑器"><span class="wx_imgbc_placeholder" data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg"><code><span>mysql&gt;&nbsp;select<span>&nbsp;*&nbsp;from<span>&nbsp;sys.innodb_lock_waits\G<span><br><span>***************************&nbsp;1.row<span>&nbsp;***************************<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; wait_started:&nbsp;2025-10-1213<span>:11<span>:29<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; wait_age:&nbsp;08<span>:00<span>:33<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;wait_age_secs:&nbsp;28833<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; locked_table:&nbsp;`biz_schema`<span>.`tbl_product_service_mapping01`<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;locked_table_schema: biz_schema<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;locked_table_name: tbl_product_service_mapping01<span><br><span>&nbsp; &nbsp; &nbsp; locked_table_partition:&nbsp;NULL<span><br><span>&nbsp; &nbsp;locked_table_subpartition:&nbsp;NULL<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; locked_index: tbl_product_service_pk<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;locked_type:&nbsp;RECORD<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; waiting_trx_id:&nbsp;5221288<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_trx_started:&nbsp;2025-10-1213<span>:11<span>:22<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_trx_age:&nbsp;08<span>:00<span>:40<span><br><span>&nbsp; &nbsp; &nbsp;waiting_trx_rows_locked:&nbsp;35<span><br><span>&nbsp; &nbsp;waiting_trx_rows_modified:&nbsp;34<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_pid:&nbsp;10<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_query:&nbsp;INSERT&nbsp;IGNORE&nbsp;INTO<span>&nbsp;tbl_product ... (10512475<span>,&nbsp;1073743289<span>) &nbsp; &nbsp; &nbsp;,&nbsp;<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_lock_id:&nbsp;140256432120808<span>:10011<span>:67<span>:240<span>:263<span>:140256317861168<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_lock_mode: X,GAP,INSERT_INTENTION<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;blocking_trx_id:&nbsp;5221291<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_pid:&nbsp;14<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_query:&nbsp;INSERT&nbsp;IGNORE&nbsp;INTO<span>&nbsp;tbl_product ... &nbsp; &nbsp; &nbsp; (10512476<span>,&nbsp;1073743289<span>) &nbsp;<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_lock_id:&nbsp;140256432125848<span>:9282<span>:67<span>:240<span>:263<span>:140256317891856<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_lock_mode: S,GAP<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; blocking_trx_started:&nbsp;2025-10-1213<span>:11<span>:22<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_trx_age:&nbsp;08<span>:00<span>:40<span><br><span>&nbsp; &nbsp; blocking_trx_rows_locked:&nbsp;35<span><br><span>&nbsp; blocking_trx_rows_modified:&nbsp;34<span><br><span>&nbsp; &nbsp; &nbsp;sql_kill_blocking_query:&nbsp;KILL&nbsp;QUERY&nbsp;14<span><br><span>sql_kill_blocking_connection:&nbsp;KILL&nbsp;14<span><br><span>***************************&nbsp;2.row<span>&nbsp;***************************<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; wait_started:&nbsp;2025-10-1213<span>:11<span>:29<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; wait_age:&nbsp;08<span>:00<span>:33<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;wait_age_secs:&nbsp;28833<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; locked_table:&nbsp;`biz_schema`<span>.`tbl_product_service_mapping01`<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;locked_table_schema: biz_schema<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;locked_table_name: tbl_product_service_mapping01<span><br><span>&nbsp; &nbsp; &nbsp; locked_table_partition:&nbsp;NULL<span><br><span>&nbsp; &nbsp;locked_table_subpartition:&nbsp;NULL<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; locked_index: tbl_product_service_pk<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;locked_type:&nbsp;RECORD<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; waiting_trx_id:&nbsp;5221291<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_trx_started:&nbsp;2025-10-1213<span>:11<span>:22<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_trx_age:&nbsp;08<span>:00<span>:40<span><br><span>&nbsp; &nbsp; &nbsp;waiting_trx_rows_locked:&nbsp;35<span><br><span>&nbsp; &nbsp;waiting_trx_rows_modified:&nbsp;34<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_pid:&nbsp;14<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_query:&nbsp;INSERT&nbsp;IGNORE&nbsp;INTO<span>&nbsp;tbl_product ... &nbsp; &nbsp; &nbsp; (10512476<span>,&nbsp;1073743289<span>) &nbsp;<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_lock_id:&nbsp;140256432125848<span>:9282<span>:67<span>:240<span>:260<span>:140256317892560<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;waiting_lock_mode: X,GAP,INSERT_INTENTION<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;blocking_trx_id:&nbsp;5221289<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_pid:&nbsp;12<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_query:&nbsp;NULL<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_lock_id:&nbsp;140256432123832<span>:9816<span>:67<span>:240<span>:260<span>:140256317879376<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_lock_mode: S,GAP<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; blocking_trx_started:&nbsp;2025-10-1213<span>:11<span>:22<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; blocking_trx_age:&nbsp;08<span>:00<span>:40<span><br><span>&nbsp; &nbsp; blocking_trx_rows_locked:&nbsp;34<span><br><span>&nbsp; blocking_trx_rows_modified:&nbsp;33<span><br><span>&nbsp; &nbsp; &nbsp;sql_kill_blocking_query:&nbsp;KILL&nbsp;QUERY&nbsp;12<span><br><span>sql_kill_blocking_connection:&nbsp;KILL&nbsp;12<span><br>2rowsinset<span>&nbsp;(0.01<span>&nbsp;sec)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>可以看出:</span></p>
<ul class="list-paddingleft-1">
<li><span>PID 10 的 INSERT 操作被 PID 14 持有的 S,GAP 锁阻塞。</span></li>
<li><span>PID 14的 INSERT 操作又被 PID 12 持有的 S,GAP 锁阻塞。</span></li>
</ul>
<p data-tool="mdnice编辑器"><span>使用<code><span>performance_schema.data_locks</span></code><span>可以获取更详细的锁信息,包括被锁定的数据行:</span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code>SELECT<span><br><span>&nbsp; w.REQUESTING_ENGINE_TRANSACTION_ID&nbsp;AS<span>&nbsp;waiting_trx_id,<span><br><span>&nbsp; w.BLOCKING_ENGINE_TRANSACTION_ID&nbsp;AS<span>&nbsp;blocking_trx_id,<span><br><span>&nbsp; l1.LOCK_MODE&nbsp;AS<span>&nbsp;waiting_lock_mode,<span><br><span>&nbsp; l1.LOCK_DATA&nbsp;AS<span>&nbsp;waiting_lock_data,<span><br><span>&nbsp; l2.LOCK_MODE&nbsp;AS<span>&nbsp;blocking_lock_mode,<span><br><span>&nbsp; l2.LOCK_DATA&nbsp;AS<span>&nbsp;blocking_lock_data<span><br>FROM<span>&nbsp;performance_schema.data_lock_waits&nbsp;AS<span>&nbsp;w<span><br>JOIN<span>&nbsp;performance_schema.data_locks&nbsp;AS<span>&nbsp;l1<span><br>ON<span>&nbsp;w.REQUESTING_ENGINE_LOCK_ID = l1.ENGINE_LOCK_ID<span><br>JOIN<span>&nbsp;performance_schema.data_locks&nbsp;AS<span>&nbsp;l2<span><br>ON<span>&nbsp;w.BLOCKING_ENGINE_LOCK_ID = l2.ENGINE_LOCK_ID;<span><br><span>+----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+<span><br><span>| waiting_trx_id | blocking_trx_id | waiting_lock_mode &nbsp; &nbsp; &nbsp;| waiting_lock_data &nbsp; &nbsp; &nbsp; &nbsp;| blocking_lock_mode | blocking_lock_data &nbsp; &nbsp; &nbsp; |<span><br><span>+----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+<span><br><span>| &nbsp; &nbsp; &nbsp; &nbsp;5221288 | &nbsp; &nbsp; &nbsp; &nbsp; 5221291 | X,GAP,INSERT_INTENTION | 10512476, 1, 18158557178 | S,GAP &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| 10512476, 1, 18158557178 |<span><br><span>| &nbsp; &nbsp; &nbsp; &nbsp;5221291 | &nbsp; &nbsp; &nbsp; &nbsp; 5221289 | X,GAP,INSERT_INTENTION | 10512477, 1, 18158557146 | S,GAP &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| 10512477, 1, 18158557146 |<span><br><span>+----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+<span><br><span>2 rows in&nbsp;set<span>&nbsp;(0.00<span>&nbsp;sec)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>接下来从<code><span>show processlist</span></code><span>的输出中看看 PID 10、14、12 这三个线程的状态。</span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>mysql&gt;&nbsp;show<span>&nbsp;processlist<span>;<span><br><span>+----+-----------------+-----------+-----------+---------+---------+---------------------------------------------+------------------------------------------------------------------------------------------------------+<span><br><span>| Id | User &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| Host &nbsp; &nbsp; &nbsp;| db &nbsp; &nbsp; &nbsp; &nbsp;| Command | Time &nbsp; &nbsp;| State &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | Info &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<span><br><span>+----+-----------------+-----------+-----------+---------+---------+---------------------------------------------+------------------------------------------------------------------------------------------------------+<span><br><span>| &nbsp;5 | event_scheduler | localhost | NULL &nbsp; &nbsp; &nbsp;| Daemon &nbsp;| &nbsp; &nbsp;1813 | Waiting on empty queue &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| NULL &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<span><br><span>| &nbsp;8 | root &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| localhost | biz_schema | Query &nbsp; | &nbsp; &nbsp; &nbsp; 0 | init &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp;show&nbsp;processlist<span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>| &nbsp;9<span>&nbsp;|&nbsp;system&nbsp;user<span>&nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; |&nbsp;Query<span>&nbsp; &nbsp;| &nbsp; &nbsp;1588<span>&nbsp;| Waiting&nbsp;for<span>&nbsp;dependent&nbsp;transaction&nbsp;to&nbsp;commit<span>&nbsp;|&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>|&nbsp;10<span>&nbsp;|&nbsp;system&nbsp;user<span>&nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | biz_schema |&nbsp;Query<span>&nbsp; &nbsp;|&nbsp;3585949<span>&nbsp;| Applying batch&nbsp;of&nbsp;row<span>&nbsp;changes (write) &nbsp; &nbsp; &nbsp; |&nbsp;INSERT&nbsp;IGNORE&nbsp;INTO<span>&nbsp;tbl_product_service_mapping01<span><br><span>&nbsp; &nbsp; &nbsp; (<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; c1,<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; c2 |<span><br><span>|&nbsp;11<span>&nbsp;|&nbsp;system&nbsp;user<span>&nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; |&nbsp;Query<span>&nbsp; &nbsp;|&nbsp;3585949<span>&nbsp;| Waiting&nbsp;for&nbsp;preceding&nbsp;transaction&nbsp;to&nbsp;commit<span>&nbsp;|&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>|&nbsp;12<span>&nbsp;|&nbsp;system&nbsp;user<span>&nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; |&nbsp;Query<span>&nbsp; &nbsp;|&nbsp;3585949<span>&nbsp;| Waiting&nbsp;for&nbsp;preceding&nbsp;transaction&nbsp;to&nbsp;commit<span>&nbsp;|&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>|&nbsp;13<span>&nbsp;|&nbsp;system&nbsp;user<span>&nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; |&nbsp;Query<span>&nbsp; &nbsp;|&nbsp;3585949<span>&nbsp;| Waiting&nbsp;for&nbsp;preceding&nbsp;transaction&nbsp;to&nbsp;commit<span>&nbsp;|&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>|&nbsp;14<span>&nbsp;|&nbsp;system&nbsp;user<span>&nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; | biz_schema |&nbsp;Query<span>&nbsp; &nbsp;|&nbsp;3585949<span>&nbsp;| Applying batch&nbsp;of&nbsp;row<span>&nbsp;changes (write) &nbsp; &nbsp; &nbsp; |&nbsp;INSERT&nbsp;IGNORE&nbsp;INTO<span>&nbsp;tbl_product_service_mapping01<span><br><span>&nbsp; &nbsp; &nbsp; (<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; c1,<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; c2 |<span><br><span>|&nbsp;15<span>&nbsp;|&nbsp;system&nbsp;user<span>&nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; |&nbsp;Query<span>&nbsp; &nbsp;|&nbsp;3585949<span>&nbsp;| Waiting&nbsp;for&nbsp;preceding&nbsp;transaction&nbsp;to&nbsp;commit<span>&nbsp;|&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>|&nbsp;16<span>&nbsp;|&nbsp;system&nbsp;user<span>&nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; |&nbsp;Query<span>&nbsp; &nbsp;|&nbsp;3585949<span>&nbsp;| Waiting&nbsp;for<span>&nbsp;an&nbsp;event&nbsp;from<span>&nbsp;Coordinator &nbsp; &nbsp; &nbsp; |&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>|&nbsp;17<span>&nbsp;|&nbsp;system&nbsp;user<span>&nbsp; &nbsp; &nbsp;| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; |&nbsp;Query<span>&nbsp; &nbsp;|&nbsp;3585949<span>&nbsp;| Waiting&nbsp;for<span>&nbsp;an&nbsp;event&nbsp;from<span>&nbsp;Coordinator &nbsp; &nbsp; &nbsp; |&nbsp;NULL<span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>+----+-----------------+-----------+-----------+---------+---------+---------------------------------------------+------------------------------------------------------------------------------------------------------+<span><br>11rowsinset<span>,&nbsp;1warning<span>&nbsp;(0.00<span>&nbsp;sec)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>PID 12 的执行用户是<code><span>system user</span></code><span>,说明它是并行重放的工作线程,其状态为<code><span>Waiting for preceding transaction to commit</span></code><span>,表示该线程正在等待它前面的事务提交完成。</span></span></span></p>
<p data-tool="mdnice编辑器"><span>而 PID 10 和 PID 14 的状态均为<code><span>Applying batch of row changes (write)</span></code><span>。从字面上看,似乎是在执行批量写入操作,但实际上,这两个线程正在等待锁。</span></span></p>
<p data-tool="mdnice编辑器"><span>如果执行的是<code><span>SHOW FULL PROCESSLIST</span></code><span>,<code><span>Info</span></code><span>列的<code><span>INSERT IGNORE</span></code><span>操作中还可以看到具体要插入的唯一索引值。借助这些唯一索引值,可以在 binlog 中精确定位对应的执行位置,便于分析事务执行顺序和锁等待情况。</span></span></span></span></p>
<p data-tool="mdnice编辑器"><span>不过,对于 PID 12,由于<code><span>Info</span></code><span>列为<code><span>NULL</span></code><span>,无法直接看到具体的 <span>DML 操作,因此难以定位其执行内容。</span></span></span></span></p>
<p data-tool="mdnice编辑器"><span>为了解决这个问题,我在<code><span>Slave_worker::slave_worker_exec_event</span></code><span>函数中,在调用<code><span>ev-&gt;do_apply_event_worker(this)</span></code><span>的前后分别添加了日志打印。这样,就能清楚地看到每个工作线程正在执行的 event 的 binlog 位置点信息。</span></span></span></p>
<pre data-tool="mdnice编辑器"><span class="wx_imgbc_placeholder" data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg"><code><span>int<span>&nbsp;<span>Slave_worker::slave_worker_exec_event<span>(Log_event *ev)<span>&nbsp;<span>{<span><br><span>&nbsp; ...<span><br><span>&nbsp; ulong thread_id = thd-&gt;thread_id();<span><br><span>&nbsp; ulong log_pos =&nbsp;static_cast<span>&lt;ulong&gt;(ev-&gt;common_header-&gt;log_pos);<span><br>std<span>::string<span>&nbsp;msg =&nbsp;"Executing event: worker_thread_id="<span>&nbsp;+<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std<span>::to_string(thread_id) +<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;", master_log_pos="<span>&nbsp;+<span><br><span>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;std<span>::to_string(log_pos);<span><br><span>&nbsp; LogErr(INFORMATION_LEVEL, ER_CONDITIONAL_DEBUG, msg.c_str());<span><br><span>&nbsp; ret = ev-&gt;do_apply_event_worker(this<span>);<span><br><span>&nbsp; msg =&nbsp;"Done executing event: worker_thread_id="<span>&nbsp;+<span><br><span>&nbsp; &nbsp; &nbsp;&nbsp;std<span>::to_string(thread_id) +<span><br><span>&nbsp; &nbsp; &nbsp;&nbsp;", master_log_pos="<span>&nbsp;+<span><br><span>&nbsp; &nbsp; &nbsp;&nbsp;std<span>::to_string(log_pos);<span><br><span>&nbsp; LogErr(INFORMATION_LEVEL, ER_CONDITIONAL_DEBUG, msg.c_str());<span><br>return<span>&nbsp;ret;<span><br><span>}<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>下面是锁等待发生时,PID 10、12、14 正在执行的 binlog event 位置点信息:</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code># PID 10<span><br><span>grep 'worker_thread_id=10' /data/mysql/3306/data/mysqld.err | tail -1<span><br><span>2025-10-12T13:11:22.639120-00:00 10 Executing event: worker_thread_id=10, master_log_pos=63245428<span><br><span><br># PID 12<span><br><span>grep 'worker_thread_id=12' /data/mysql/3306/data/mysqld.err | tail -1<span><br><span>2025-10-12T13:11:22.725638-00:00 12 Executing event: worker_thread_id=12, master_log_pos=63248672<span><br><span><br># PID 14<span><br><span>grep 'worker_thread_id=14' /data/mysql/3306/data/mysqld.err | tail -1<span><br><span>2025-10-12T13:11:22.646870-00:00 14 Executing event: worker_thread_id=14, master_log_pos=63251784<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>可以看到,PID 10 对应的事务在 binlog 中的位置早于 PID 12。</span></p>
<p data-tool="mdnice编辑器"><span>当参数<code><span>replica_preserve_commit_order</span></code><span>设置为 ON 时,从库必须严格按照主库的提交顺序依次提交事务,因此 PID 12 必须等待 PID 10 提交完成才能继续执行。</span></span></p>
<p data-tool="mdnice编辑器"><span>结合锁依赖关系,就形成了一个循环等待的局面:</span></p>
<ul class="list-paddingleft-1">
<li><span>PID 10 等待 PID 14 持有的 S,GAP 锁;</span></li>
<li><span>PID 14 等待 PID 12 持有的 S,GAP 锁;</span></li>
<li><span>PID 12 因提交顺序限制,必须等待 PID 10 提交事务。</span></li>
</ul>
<p data-tool="mdnice编辑器"><span>最终,这种环路导致三个线程相互阻塞,直到锁等待超时,MySQL 才会重新执行这些事务。</span></p>
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">模拟从库的重放操作</span></h1>
<p data-tool="mdnice编辑器"><span>根据获取到的 PID 10、12、14 对应的 event 位置点信息,我们可以还原出锁等待发生时这三个线程正在执行的具体操作:</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>worker_thread_id=10 master_log_pos=63245428:&nbsp;insert<span>&nbsp;c1 =&nbsp;10512475<span>&nbsp;的所有记录,如(10512475<span>,1<span>),(10512475<span>,20<span>)...<span><br><span>worker_thread_id=12<span>&nbsp;master_log_pos=63248672<span>:&nbsp;insert<span>&nbsp;c1 =&nbsp;10512477<span>&nbsp;的所有记录,如(10512477<span>,1<span>),(10512477<span>,20<span>)...<span><br><span>worker_thread_id=14<span>&nbsp;master_log_pos=63251784<span>:&nbsp;insert<span>&nbsp;c1 =&nbsp;10512476<span>&nbsp;的所有记录,如(10512476<span>,1<span>),(10512476<span>,20<span>)...<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>这里的记录值对应的是表的<strong>联合唯一索引</strong><span>,其中<code><span>c1</span></code><span>是联合索引的第一列。</span></span></span></p>
<p data-tool="mdnice编辑器"><span>值得注意的是,在这些<code><span>INSERT</span></code><span>操作之前,binlog 中还存在针对相同<code><span>c1</span></code><span>值的<code><span>DELETE</span></code><span>操作:</span></span></span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code>delete<span>&nbsp;c1 =&nbsp;10512475<span>&nbsp;的所有记录,如(10512475<span>,1<span>),(10512475<span>,20<span>)...<span><br>delete<span>&nbsp;c1 =&nbsp;10512477<span>&nbsp;的所有记录,如(10512477<span>,1<span>),(10512477<span>,20<span>)...<span><br>delete<span>&nbsp;c1 =&nbsp;10512476<span>&nbsp;的所有记录,如(10512476<span>,1<span>),(10512476<span>,20<span>)...<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>也就是说,业务实际上是通过&nbsp;<strong>DELETE + INSERT</strong><span>&nbsp;的方式实现数据更新。</span></span></p>
<p data-tool="mdnice编辑器"><span>为了进一步分析锁等待问题,我打印了重放过程中每个<code><span>INSERT</span></code><span>操作的具体内容。</span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>2025-10-12T13:11:22.639267-00:00 10 Inserted row: (18158557112, 10512475, 1, ...)<span><br><span>2025-10-12T13:11:22.640729-00:00 10 Inserted row: (18158557113, 10512475, 20, ...)<span><br><span>2025-10-12T13:11:22.642004-00:00 10 Inserted row: (18158557114, 10512475, 26, ...)<span><br><span>2025-10-12T13:11:22.643344-00:00 10 Inserted row: (18158557115, 10512475, 123, ...)<span><br><span>2025-10-12T13:11:22.644262-00:00 12 Inserted row: (18158557146, 10512477, 1, ...)<span><br><span>2025-10-12T13:11:22.644663-00:00 10 Inserted row: (18158557116, 10512475, 131, ...)<span><br><span>2025-10-12T13:11:22.646250-00:00 12 Inserted row: (18158557147, 10512477, 20, ...)<span><br><span>2025-10-12T13:11:22.647020-00:00 14 Inserted row: (18158557178, 10512476, 1, ...)<span><br><span>2025-10-12T13:11:22.647192-00:00 10 Inserted row: (18158557117, 10512475, 133, ...)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>其中,第一个值是自增主键,后两个值是唯一索引列。</span></p>
<p data-tool="mdnice编辑器"><span>从输出可以看到,这三个事务的插入操作是交叉执行的。</span></p>
<h3 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">模拟从库重放过程</span></h3>
<p data-tool="mdnice编辑器"><span>下面通过一个实验来模拟从库的重放操作。</span></p>
<p data-tool="mdnice编辑器"><span>首先,在会话 1 中创建测试表并插入数据。</span></p>
<pre data-tool="mdnice编辑器"><span class="wx_imgbc_placeholder" data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg"><code><span>session1&gt;&nbsp;create<span>&nbsp;table<span>&nbsp;test.t1(id&nbsp;bigint<span>&nbsp;auto_increment primary&nbsp;key<span>,c1&nbsp;int<span>,c2&nbsp;int<span>,unique&nbsp;key<span>(c1,c2));<span><br><span>Query OK, 0 rows affected (0.06 sec)<span><br><span><br><span>session1&gt;&nbsp;insert&nbsp;into<span>&nbsp;test.t1(c1,c2)&nbsp;values<span>(10512475<span>,&nbsp;1<span>),(10512475<span>,&nbsp;2<span>),(10512476<span>,&nbsp;1<span>),(10512476<span>,&nbsp;2<span>),(10512477<span>,&nbsp;1<span>),(10512477<span>,&nbsp;2<span>);<span><br><span>Query OK, 6 rows affected (0.04 sec)<span><br><span>Records: 6 &nbsp;Duplicates: 0 &nbsp;Warnings: 0<span><br><span><br><span>session1&gt;&nbsp;select<span>&nbsp;*&nbsp;from<span>&nbsp;test.t1;<span><br><span>+----+----------+------+<span><br><span>| id | c1 &nbsp; &nbsp; &nbsp; | c2 &nbsp; |<span><br><span>+----+----------+------+<span><br><span>| &nbsp;1 | 10512475 | &nbsp; &nbsp;1 |<span><br><span>| &nbsp;2 | 10512475 | &nbsp; &nbsp;2 |<span><br><span>| &nbsp;3 | 10512476 | &nbsp; &nbsp;1 |<span><br><span>| &nbsp;4 | 10512476 | &nbsp; &nbsp;2 |<span><br><span>| &nbsp;5 | 10512477 | &nbsp; &nbsp;1 |<span><br><span>| &nbsp;6 | 10512477 | &nbsp; &nbsp;2 |<span><br><span>+----+----------+------+<span><br><span>6 rows in&nbsp;set<span>&nbsp;(0.00<span>&nbsp;sec)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>其次,在会话 2 中针对另外一张表执行<code><span>FLUSH TABLES FOR EXPORT</span></code><span>操作,至于为什么要执行这个操作,后续加锁分析部分会解释。</span></span></p>
<pre data-tool="mdnice编辑器"><span class="wx_imgbc_placeholder" data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg"><code><span>session2&gt;&nbsp;flush<span>&nbsp;tables<span>&nbsp;test.t2&nbsp;for<span>&nbsp;export<span>;<span><br><span>Query OK, 0 rows affected (0.01 sec)<span><br></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>接着,在会话 1 中删除表中数据。</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>session1&gt;&nbsp;delete<span>&nbsp;from<span>&nbsp;test.t1;<span><br><span>Query OK, 6 rows affected (0.02 sec)<span><br></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>接着分别创建三个新的会话,执行如下操作:</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>session3&gt;&nbsp;begin<span>;<span><br><span>Query OK, 0 rows affected (0.00 sec)<span><br><span><br><span>session3&gt;&nbsp;insert&nbsp;into<span>&nbsp;test.t1(c1,c2,id<span>)&nbsp;values<span>(10512475<span>,1<span>,100<span>);<span><br><span>Query OK, 1 row affected (0.01 sec)<span><br><span><br><span>session4&gt;&nbsp;begin<span>;<span><br><span>Query OK, 0 rows affected (0.00 sec)<span><br><span><br><span>session4&gt;&nbsp;insert&nbsp;into<span>&nbsp;test.t1(c1,c2,id<span>)&nbsp;values<span>(10512476<span>,1<span>,18158557178<span>);<span><br><span>Query OK, 1 row affected (0.00 sec)<span><br><span><br><span>session5&gt;&nbsp;begin<span>;<span><br><span>Query OK, 0 rows affected (0.00 sec)<span><br><span><br><span>session5&gt;&nbsp;insert&nbsp;into<span>&nbsp;test.t1(c1,c2,id<span>)&nbsp;values<span>(10512477<span>,1<span>,18158557146<span>);<span><br><span>Query OK, 1 row affected (0.01 sec)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>继续在会话 3 和 会话 4 中插入数据。</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>session3&gt;&nbsp;set<span>&nbsp;session<span>&nbsp;innodb_lock_wait_timeout=5000<span>;<span><br><span>Query OK, 0 rows affected (0.00 sec)<span><br><span><br><span>session3&gt;&nbsp;insert<span>&nbsp;into<span>&nbsp;test.t1(c1,c2)&nbsp;values<span>(10512475<span>,&nbsp;2<span>);<span><br>-- 阻塞中...<span><br><span><br><span>session4&gt;&nbsp;set<span>&nbsp;session<span>&nbsp;innodb_lock_wait_timeout=5000<span>;<span><br><span>Query OK, 0 rows affected (0.00 sec)<span><br><span><br><span>session4&gt;&nbsp;insert<span>&nbsp;into<span>&nbsp;test.t1(c1,c2)&nbsp;values<span>(10512476<span>,&nbsp;2<span>);<span><br>-- 阻塞中...<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>接着在会话 2 中执行<code><span>UNLOCK TABLES</span></code><span>操作释放表锁。</span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>session2&gt;&nbsp;unlock<span>&nbsp;tables<span>;<span><br><span>Query OK, 0 rows affected (0.00 sec)<span><br></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>在会话 5 中查看锁等待信息。</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>+----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+<span><br><span>| waiting_trx_id | blocking_trx_id | waiting_lock_mode &nbsp; &nbsp; &nbsp;| waiting_lock_data &nbsp; &nbsp; &nbsp; &nbsp;| blocking_lock_mode | blocking_lock_data &nbsp; &nbsp; &nbsp; |<span><br><span>+----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+<span><br><span>| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;23228 | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 23229 | X,GAP,INSERT_INTENTION | 10512477, 1, 18158557146 | S,GAP &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| 10512477, 1, 18158557146 |<span><br><span>| &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;23225 | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 23228 | X,GAP,INSERT_INTENTION | 10512476, 1, 18158557178 | S,GAP &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| 10512476, 1, 18158557178 |<span><br><span>+----------------+-----------------+------------------------+--------------------------+--------------------+--------------------------+<span><br><span>2 rows in&nbsp;set<span>&nbsp;(0.01<span>&nbsp;sec)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<p data-tool="mdnice编辑器"><span>可以看到,该结果与重放过程中出现锁等待时的输出完全一致。</span></p>
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">加锁分析</span></h1>
<p data-tool="mdnice编辑器"><span>接下来,我们重点分析一下:为什么在 RC(Read Committed)事务隔离级别下会产生 GAP 锁?</span></p>
<p data-tool="mdnice编辑器"><span>毕竟,在大多数人的印象中,RC 隔离级别下只会存在记录锁,而不会出现间隙锁。</span></p>
<p data-tool="mdnice编辑器"><span>事实上,这与&nbsp;<strong>INSERT 操作之前执行的 DELETE 操作</strong><span>&nbsp;有直接关系。</span></span></p>
<p data-tool="mdnice编辑器"><span>在前面的例子中,我们在 binlog 中发现,在执行 INSERT 操作之前,存在针对相同记录的 DELETE 操作。</span></p>
<p data-tool="mdnice编辑器"><span>在 MySQL 中,DELETE 操作并不会立即物理删除数据,而是将记录标记为“已删除”(delete-marked),等待后台的 purge 线程异步清理。这意味着在逻辑删除之后,这些记录仍然可能暂时保留在索引页中。</span></p>
<p data-tool="mdnice编辑器"><span>当随后执行 INSERT 操作时,如果待插入的记录在唯一索引上与某条“已标记删除但尚未清除”的记录键值相同,MySQL 会执行如下加锁行为:</span></p>
<ul class="list-paddingleft-1">
<li><span>对该索引项加上 S 锁;</span></li>
<li><span>同时,对该索引项的间隙(即该记录与下一条记录之间的范围)加上 S,GAP 锁。</span></li>
</ul>
<p data-tool="mdnice编辑器"><span>下面我们通过一个简化的实验来验证这一点。</span></p>
<h3 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">实验验证</span></h3>
<p data-tool="mdnice编辑器"><span>在前面的重放示例中,我们执行了<code><span>FLUSH TABLES FOR EXPORT</span></code><span>操作。</span></span></p>
<p data-tool="mdnice编辑器"><span>执行这个操作的目的,是为了暂停 purge 线程,从而保留 delete-marked 记录,便于重现这种锁行为。</span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code># 会话 1:创建测试表并插入数据<span><br><span>session1&gt;&nbsp;create&nbsp;table<span>&nbsp;test.t1(id&nbsp;bigint<span>&nbsp;auto_increment primary&nbsp;key<span>,c1&nbsp;int<span>,c2&nbsp;int<span>,unique&nbsp;key<span>(c1,c2));<span><br><span>Query OK, 0 rows affected (0.07 sec)<span><br><span><br><span>session1&gt;&nbsp;insert&nbsp;into<span>&nbsp;test.t1(c1,c2)&nbsp;values<span>(10512476<span>,&nbsp;1<span>),(10512476<span>,&nbsp;2<span>);<span><br><span>Query OK, 2 rows affected (0.04 sec)<span><br><span>Records: 2 &nbsp;Duplicates: 0 &nbsp;Warnings: 0<span><br><span><br><span>session1&gt;&nbsp;select<span>&nbsp;*&nbsp;from<span>&nbsp;test.t1;<span><br><span>+----+----------+------+<span><br><span>| id | c1 &nbsp; &nbsp; &nbsp; | c2 &nbsp; |<span><br><span>+----+----------+------+<span><br><span>| &nbsp;1 | 10512476 | &nbsp; &nbsp;1 |<span><br><span>| &nbsp;2 | 10512476 | &nbsp; &nbsp;2 |<span><br><span>+----+----------+------+<span><br><span>2 rows in&nbsp;set<span>&nbsp;(0.00<span>&nbsp;sec)<span><br><span><br># 会话 2:暂停 purge 线程<span><br><span>session2&gt;&nbsp;flush&nbsp;tables<span>&nbsp;test.t2&nbsp;for&nbsp;export<span>;<span><br><span>Query OK, 0 rows affected (0.03 sec)<span><br><span><br># 会话 1:删除数据<span><br><span>session1&gt;&nbsp;delete&nbsp;from<span>&nbsp;test.t1;<span><br><span>Query OK, 2 rows affected (0.01 sec)<span><br><span><br># 会话 3:开启事务并插入数据<span><br><span>session3&gt;&nbsp;begin<span>;<span><br><span>Query OK, 0 rows affected (0.00 sec)<span><br><span><br><span>session3&gt;&nbsp;insert&nbsp;into<span>&nbsp;test.t1(c1,c2,id<span>)&nbsp;values<span>(10512476<span>,1<span>,18158557178<span>);<span><br><span>Query OK, 1 row affected (0.01 sec)<span><br><span><br># 查看锁信息<span><br><span>session3&gt;&nbsp;select<span>&nbsp;object_schema, object_name, index_name, lock_type, lock_mode, lock_status, lock_data&nbsp;from<span>&nbsp; performance_schema.data_locks;<span><br><span>+---------------+-------------+------------+-----------+-----------+-------------+--------------------------+<span><br><span>| object_schema | object_name | index_name | lock_type | lock_mode | lock_status | lock_data &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>+---------------+-------------+------------+-----------+-----------+-------------+--------------------------+<span><br><span>| test &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| t1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| NULL &nbsp; &nbsp; &nbsp; | TABLE &nbsp; &nbsp; | IX &nbsp; &nbsp; &nbsp; &nbsp;| GRANTED &nbsp; &nbsp; | NULL &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<span><br><span>| test &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| t1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| c1 &nbsp; &nbsp; &nbsp; &nbsp; | RECORD &nbsp; &nbsp;| S &nbsp; &nbsp; &nbsp; &nbsp; | GRANTED &nbsp; &nbsp; | 10512476, 1, 1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<span><br><span>| test &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| t1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| c1 &nbsp; &nbsp; &nbsp; &nbsp; | RECORD &nbsp; &nbsp;| S,GAP &nbsp; &nbsp; | GRANTED &nbsp; &nbsp; | 10512476, 2, 2 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<span><br><span>| test &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| t1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| c1 &nbsp; &nbsp; &nbsp; &nbsp; | RECORD &nbsp; &nbsp;| S,GAP &nbsp; &nbsp; | GRANTED &nbsp; &nbsp; | 10512476, 1, 18158557178 |<span><br><span>+---------------+-------------+------------+-----------+-----------+-------------+--------------------------+<span><br><span>4 rows in&nbsp;set<span>&nbsp;(0.00<span>&nbsp;sec)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<h3 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">锁行为解释</span></h3>
<p data-tool="mdnice编辑器"><span>在插入&nbsp;<code><span>(10512476, 1, 18158557178)</span></code><span>&nbsp;这条记录时:</span></span></p>
<ul class="list-paddingleft-1">
<li>
<p><span>由于&nbsp;<code><span>(10512476, 1)</span></code><span>&nbsp;仍存在于索引中(虽然被标记删除),MySQL 会对该记录加上 S 锁;</span></span></p>
</li>
<li>
<p><span>同时,对<code><span>(10512476, 1)</span></code><span>&nbsp;的下一条记录&nbsp;<code><span>(10512476, 2)</span></code><span>&nbsp;加上 S,GAP 锁,防止该间隙范围内被其他事务插入新记录;</span></span></span></p>
</li>
<li>
<p><span>此外,插入的新记录&nbsp;<code><span>(10512476, 1, 18158557178)</span></code><span>&nbsp;还会继承下一条记录&nbsp;<code><span>(10512476, 2)</span></code><span>&nbsp;的 GAP 锁。</span></span></span></p>
</li>
</ul>
<p data-tool="mdnice编辑器"><span>其中:</span></p>
<ul class="list-paddingleft-1">
<li><span>前两种锁的加锁逻辑是在<code><span>row_ins_scan_sec_index_for_duplicate()</span></code><span>中实现的;</span></span></li>
<li><span>锁继承的逻辑是在<code><span>lock_rec_add_to_queue()</span></code><span>中实现的。</span></span></li>
</ul>
<p data-tool="mdnice编辑器"><span>当执行<code><span>UNLOCK TABLES</span></code><span>后,purge 线程恢复运行,会清理掉之前的 delete-marked 记录,对应的锁也会被释放。但可以看到,新插入记录自身的 GAP 锁仍然保留:</span></span></p>
<pre data-tool="mdnice编辑器"><span data-cacheurl="" data-remoteid="" data-lazy-bgimg="https://mmbiz.qpic.cn/mmbiz_svg/BO1qQiajiacVlibSwibjXiaMnyugHDAJficjcBMzia3uz8EVxq3lQI4iaNZibkopGmumFpnRofyNhqm0IJDZ1DVn4GzXYnZRAsGqFuEKib/640?wx_fmt=svg&amp;from=appmsg" data-fail="0"><code><span>session2&gt;&nbsp;unlock<span>&nbsp;tables<span>;<span><br><span>Query OK, 0 rows affected (0.05 sec)<span><br><span><br><span>session3&gt;&nbsp;select<span>&nbsp;object_schema, object_name, index_name, lock_type, lock_mode, lock_status, lock_data&nbsp;from<span>&nbsp; performance_schema.data_locks;<span><br><span>+---------------+-------------+------------+-----------+-----------+-------------+--------------------------+<span><br><span>| object_schema | object_name | index_name | lock_type | lock_mode | lock_status | lock_data &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|<span><br><span>+---------------+-------------+------------+-----------+-----------+-------------+--------------------------+<span><br><span>| test &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| t1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| NULL &nbsp; &nbsp; &nbsp; | TABLE &nbsp; &nbsp; | IX &nbsp; &nbsp; &nbsp; &nbsp;| GRANTED &nbsp; &nbsp; | NULL &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |<span><br><span>| test &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| t1 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;| c1 &nbsp; &nbsp; &nbsp; &nbsp; | RECORD &nbsp; &nbsp;| S,GAP &nbsp; &nbsp; | GRANTED &nbsp; &nbsp; | 10512476, 1, 18158557178 |<span><br><span>+---------------+-------------+------------+-----------+-----------+-------------+--------------------------+<span><br><span>2 rows in&nbsp;set<span>&nbsp;(0.01<span>&nbsp;sec)<span><br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></span></pre>
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">优化方案</span></h1>
<p data-tool="mdnice编辑器"><span>针对上面分析的锁等待案例,优化主要可以从<strong>应用侧</strong><span>和<strong>数据库侧</strong><span>两方面入手。</span></span></span></p>
<h3 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">1. 应用侧优化</span></h3>
<p data-tool="mdnice编辑器"><span>应用层面的改进主要集中在索引设计与更新逻辑上:</span></p>
<ul class="list-paddingleft-1">
<li><span>将原本的唯一索引改为普通二级索引。</span></li>
<li><span>将自增主键去掉,直接用原来的唯一索引列作为主键。与普通唯一索引不同,主键在插入时,即使遇到已经删除的记录,也不会额外加 S,GAP 锁。</span></li>
<li><span>优化更新逻辑。尽量避免通过<code><span>DELETE + INSERT</span></code><span>的方式更新数据,可以考虑使用<code><span>UPDATE</span></code><span>或者其他业务逻辑调整,以减少对间隙锁的触发。</span></span></span></li>
</ul>
<h3 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">2. 数据库侧优化</span></h3>
<p data-tool="mdnice编辑器"><span>将<code><span>replica_preserve_commit_order</span></code><span>设置为 OFF,允许从库在遇到事务等待环路时,独立提交事务,而无需等待其他事务完成。</span></span></p>
<p data-tool="mdnice编辑器"><span>不过需要注意的是,如果使用的 Group Replication,会要求该参数必须为 ON。</span></p>
<p data-tool="mdnice编辑器"><span>下表展示了不同方案下的从库重放性能对比:</span></p>
<table>
<thead>
<tr><th><span>方案</span></th><th><span>三次平均执行时间(秒)</span></th></tr>
</thead>
<tbody>
<tr>
<td><span>唯一索引 + replica_parallel_workers = 8</span></td>
<td><span>359.06</span></td>
</tr>
<tr>
<td><span>唯一索引 + replica_parallel_workers = 1</span></td>
<td><span>83.07</span></td>
</tr>
<tr>
<td><span>普通索引 + replica_parallel_workers = 8</span></td>
<td><span>33.50</span></td>
</tr>
<tr>
<td><span>唯一索引 + replica_parallel_workers = 8 + replica_preserve_commit_order = OFF</span></td>
<td><span>21.11</span></td>
</tr>
</tbody>
</table>
<h1 data-tool="mdnice编辑器"><span style="color: rgba(0, 128, 0, 1)">参考资料</span></h1>
<ol class="list-paddingleft-1">
<li><span>https://help.aliyun.com/zh/polardb/polardb-for-mysql/resolve-the-unique-key-check-problem-in-mysql</span></li>
<li><span>http://mysql.taobao.org/monthly/2015/06/02/</span></li>
<li><span>https://zhuanlan.zhihu.com/p/28797400192</span></li>
<li><span>https://dev.mysql.com/doc/refman/8.4/en/innodb-transaction-isolation-levels.html</span></li>
</ol><br><br>
来源:https://www.cnblogs.com/ivictor/p/19152369
頁: [1]
查看完整版本: 案例分析:MySQL 并行复制竟然比单线程慢?