奇怪从库并发回放比单SQL线程效率更低?

2024-06-26  本文已影响0人  重庆八怪
image.png

言归正传,遇到一个问题,从库基于writeset并发的情况下,发现从库的worker线程CPU非常的低,导致大量的延迟,如果改为单SQL线程并发效率反而更高,CPU利用率也起来了,延迟消失,现分析如下。
对于主从并发,特别是基于writeset的并发,在主库生成last commit的时候并没有考虑到一些innodb特殊锁的存在(或者innodb的锁BUG)。因此在进行从库并发回放的时候就会出现锁堵塞的情况。遇到锁堵塞的时候就需要考虑到多线程之间唤醒的方式,这样worker线程才能继续下去,下面是多线程并发的时候唤醒的方式。

lock_rec_lock_slow
  ->RecLock::add_to_waitq
    ->thd_report_row_lock_wait
      ->Commit_order_manager::check_and_report_deadlock
        ->Commit_order_manager::report_deadlock
          ->Slave_worker::report_commit_order_deadlock

整套唤醒过程主要是借助了MDL LOCK和row lock的唤醒机制交替在使用,处于Waiting for preceding transaction to commit状态的worker如果由于行锁堵塞了其他正在执行的线程,则被堵塞的线程会通过MDL LOCK唤醒处于处于Waiting for preceding transaction to commit状态的worker,其醒来后会回滚事务,然后通过行锁的机制反过来唤醒处于行锁等待的执行woker线程。假设worker2等待worker1事务提交后才能提交,worker1的事务由于worker2的行锁堵塞不能继续那么接下来发生的如下,

                WORKER1                WORKER2
                   --------------------
                 |                      |
                 |                      |
                 |                      |
                 | ----------------->   |事务执行加row lock
              事务执行加rowlock         |
              拿不到行锁                |
                 |                    准备提交事务等待提交序列
                 |                    Waiting for preceding 
                 |                    transaction to commit    
                 |                       
                 |------------------>   |                      
                 |  通过MDL LOCK唤醒    |
                 |                      |
                 |                  唤醒后回滚事务
                 |                      |
                 | <-----------------   |
                 |   通过row lock解锁   |
                事务继续                |
                 |                     事务retry
                 |                      |
               事务完成                 |
                 |                      |
                 |                     事务完成 

如果存在大量锁堵塞的情况下会导致MTS效率大大降低,效率远低于单线程,这套流程主要是如下时间耗用

因此如果有大量的锁冲突这套机制就会浪费大量的时间在等待上,并且CPU的利率非常低,既然CPU的利用率低,那么CPU自然就没有执行机器码,不执行机器码当然就跑不动(高级语言-->汇编语言-->机器码)。
而对于模拟来讲可以使用下面的方式(注意:8026以下版本才能模拟,模拟用的大量的replace语句),

mysql> show create table test.testpri2 \G
*************************** 1. row ***************************
       Table: testpri2
Create Table: CREATE TABLE `testpri2` (
  `id` int NOT NULL AUTO_INCREMENT,
  `a` int DEFAULT NULL,
  `b` int DEFAULT NULL,
  `c` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `a` (`a`,`b`)
) ENGINE=InnoDB AUTO_INCREMENT=43255 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

init data:
for ((i=0;i<10000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "insert into test.testpri2(a,b,c) values($i,$i,$i)";do

Terminal 1:
for ((i=10000;i<20000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "replace into test.testpri2(a,b,c) values($i,$i,$i)";done

Terminal 2:
for ((i=0;i<10000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "replace into test.testpri2(a,b,c) values($i,$i,$i)";done
Terminal 3:
for ((i=0;i<10000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "replace into test.testpri2(a,b,c) values($i,$i,$i)";done
Terminal 4:
for ((i=0;i<10000;i++))  do  /newdata/mysql/mysql8023/install/bin/mysql -S'/newdata/mysql/mysql8023/tmp/mysql3329.sock'  -e "replace into test.testpri2(a,b,c) values($i,$i,$i)";done

而对于8026版本或者以上,由于innodb修复了BUG,


image.png

因此无法模拟出来。

上一篇 下一篇

猜你喜欢

热点阅读