美文网首页
奇怪从库并发回放比单SQL线程效率更低?

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

作者: 重庆八怪 | 来源:发表于2024-06-26 09:14 被阅读0次
    image.png

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

    • 本worker线程遇到行锁堵塞,直接反馈,并且解锁MDL LOCK(Commit_order_manager::check_and_report_deadlock-->Commit_order_manager::report_deadlock),这一这里MDL LOCK为每个woerker线程都有一把锁,需要精准唤醒,也就是唤醒正在处于Waiting for preceding transaction to commit状态的且堵塞了本worker线程进行数据更改的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
    
    • 处于Waiting for preceding transaction to commit状态的worker唤醒后会重新判定自己是否堵塞了其他worker线程,(Commit_order_manager::wait_on_graph) ,唤醒可能是,
      A:正常的提交序列来到继续,唤醒状态为GRANTS
      B:被正在遭遇行堵塞的worker唤醒(上面的MDL LOCK解锁),唤醒后重新检测,唤醒状态为VICTIM
    • 如果是VICTIM状态唤醒的worker,那么不会进行提交,而是进行事务rollback,回滚后唤醒堵塞等待的worker线程(lock_reset_wait_and_release_thread_if_suspended ->que_thr_move_to_run_state),因为本worker线程可能行锁等待中,而其他worker的事务完成后进行retry transaction
    • retry transaction前自己有一个sleep过程。(slave_worker_exec_job_group)

    整套唤醒过程主要是借助了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效率大大降低,效率远低于单线程,这套流程主要是如下时间耗用

    • 锁等待唤醒
    • 事务回滚
    • retry 事务和之前的sleep

    因此如果有大量的锁冲突这套机制就会浪费大量的时间在等待上,并且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

    因此无法模拟出来。

    相关文章

      网友评论

          本文标题:奇怪从库并发回放比单SQL线程效率更低?

          本文链接:https://www.haomeiwen.com/subject/cctscjtx.html