美文网首页
奇怪从库并发回放比单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

因此无法模拟出来。

相关文章

  • MySQL从机Relay log消费浅析

    ​从机Relay log处理 在5.6之前,数据回放只使用了一个线程,SQL thread SQL线程做了以下一些...

  • MySQL replication 高级进阶

    延时从库 解决逻辑损坏问题 ,实际上是让SQL线程慢点回放relay-log,一般企业建议延迟3-6小时,具体看运...

  • Mysql主从复制原理

    Mysql主从复制原理 从库生成两个线程,一个I/O线程,一个SQL线程; 从库的I/O线程去请求主库...

  • 07-Java基础-多线程 & 单例

    多线程、单例 线程线程是程序执行的一条路径, 一个进程中可以包含多条线程。多线程并发执行可以提高程序的效率, 可以...

  • Redis

    NoSQL: not only sql为什么有NOSql: 互联网项目:特点: 数据库高并发读写,海量数据高效率...

  • MySQL延时从库

    延时从库,原理,在SQL线程上做手脚,不影响IO线程连接dump线程取数据。 MySQL延时从库操作 企业中一般会...

  • 美团二面:你向 Mysql 数据库插入 100w 条数据用了多久

    目录 多线程插入(单表) 多线程插入(多表) 预处理SQL 多值插入SQL 事务(N条提交一次) 多线程插入(单表...

  • MySQL主从复制过程

    一般形式: 从库启动两个线程,一个I/O线程,一个SQL线程 从库的I/O线程会请求主库的binlog日志,并将b...

  • java其他类StringBuffer与StringBuilde

    1、StringBuffer 线程安全,效率低,用于并发 StringBuilder 运行更快,效率高,无线程安全...

  • mysql快速插入海量数据的方法总结

    多线程插入(单表)多线程插入(多表)预处理SQL多值插入SQL事务(N条提交一次) 多线程插入(单表) 问:为何对...

网友评论

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

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