zoukankan      html  css  js  c++  java
  • mysql deadlock、Lock wait timeout解决和分析

    项目上线 线上遇到大量的deadlock 和wait timeout 但是看程序没什么问题 问dba也不能给出很好的解决方案!最终自己去了解mysql锁 以及看mysq锁日志 如果了解mysql锁的机制下分析就很好解决

    mysql的几种锁

    X锁(排他锁) :

      与其他X锁和S锁互斥  

    S锁(共享锁):

      与X锁互斥 当一个事物获得S锁 别的事物可以继续获得S锁 但是不能加X锁 X锁与X锁和S锁互斥

    IX(意向排他锁)

      IX是表级的 mysql引擎自动控制 在获得X锁之前 会先获得IX锁  IX只会与表级的S,X锁互斥.  当mysql对表级进行加锁(X或者S)的时候不用一行一行对数据判断是否加了X锁  直接根据是否有IX锁来进行判断,提高了效率

    IS(意向共享锁)

        IS是锁是表级的  mysql引擎自动控制 在获得S锁之前会先获得IS锁  IS锁只会与表级X锁互斥  当mysql锁对表级进行加X锁是 不用一行一行对数据判断是否加了S锁  直接判断是否存在表级的IS意向锁

    gap(锁)

        间隙锁 用于在指定索引位置区间加锁 (只会在插入是互斥)

    id age
    1 10
    2 20
    3 30
    4 40

      这个时候gap锁就有[无穷小,10],[10,20],[20,30],[30,40][40,无穷大]  

      如果在RR模式下delete table where age=20 将不能插入10~20之间  20~30到之间的值

      gap锁 只会在insert的时候互斥 (可以理解为gap在非Insert获取的都是共享锁  在Insert时获取的是排他锁)

    next-key(X锁和gap锁的组合) 

        X锁和gap锁的组合

    当前读与快照读

    快照读

    简单的查询  select * from student  where ?

    值得注意的是 在RC模式下 每次读取都是新的快照  在RR模式下 当一次快照读后 后面都会读快照 

    session1 session2
    select * from student;  
      inser into student(name) values('小明')
      update student set name='小明' where id=1;
      delete student set name='小明2' where id=2;
      commit;
    select * from student;  
    commit;  

    RC模式 session1第二次查询 将会受到session2的操作的影响 因为RC模式每次读取是读取新的快照

    RR模式 session1第二次查询不会受到session2的影响 因为后面每次读取都是读取快照(session1 update insert delete也会更新快照)

    ps:以上结果经过在mysql innodb模式 实践

    当前读

    select * student where ? lock in share mode  共享(S)锁

    select * student where ? for update( 排他(X)锁

    insert

    delete

    update

    快照读是不加锁的 当前读 都会获取相应的锁

    聚簇索引以及二级索引

    mysql加锁不是锁住某一行数据而是在表的索引上面加锁  理解聚簇索引和二级索引能够很好的帮助我们理解锁

    什么是聚簇索引?

    在innodb下  数据的存储顺序跟索引的存储存储顺序是一样  聚簇索引的页节点就指向数据,每个表都会有一个聚簇索引 默认在主键上  如果没有找到将会在表中的唯一非空的列上加上聚簇索引 如果没有mysql将自动维护一个隐式的列作为聚簇索引

    二级索引(非聚簇索引)

    二级索引的 页节点 存储的是聚簇索引  当查询一条非聚簇索引的列 会先根据索引找到聚簇索引 再根据聚簇索引查找数据

    mysql RC和RR隔离级别的加锁方式

    现在有student表有以下数据

    id name
    1 a
    2 b
    3 d
    4 f

    查看和设置默认事物隔离级别

    SELECT @@tx_isolation;

    对当前session设置隔离级别(如果不加session就是global)

    可选:READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE.

    set session transaction isolation level read uncommitted;

    RC模式

    delete student where  id=1;

    如果id列是主键

    将会在id为1的的聚簇索引上加上X锁

    如果id列为唯一索引

    将会在唯一索引上加上X锁 同时根据唯一索引找到聚簇索引 并加锁    为什么在唯一索引上面加了X锁还要在聚簇索引上加锁  因为如果这个时候另外一个事物根据聚簇索引更新数据 将感知不到锁

    比如上面表 name为聚簇索引  delete student where  id=1;   id=1的索引将加上锁  这个但是聚簇索引name没有加上 update  student id=2 where name=a 这个时候 name=a 获取锁成功能将能修改成功

    上面的解释但是会有疑惑 既然加锁都加载聚簇索引上为什么还要多此一举的在非聚簇索引列上加锁(个人理解 判断锁互斥时 直接根据条件的索引 而不用再次根据索引找到聚簇索引)

    如果id列非唯一索引

    跟唯一索引加锁机制一样

    如果id列无索引

    将在走聚簇索引 全表扫描不管是否满足条件的数据都会加上X锁  但是RC模式有优化 不满足条件的加锁之后又会释放

    RR模式

    如果id列是主键

    将会在满足条件的聚簇索引上加上X锁

    如果id列为唯一索引

    在id列上加上X锁同时在 对应的聚簇索引加上X锁

    如果id列非唯一索引

    将在id列上加上next-key锁 同时在聚簇索引加上x锁

    如果id列无索引

    将在走聚簇索引 全表扫描不管是否满足条件的数据都会加上next-key锁  跟RC模式不同的是将不会释放

      

    deadlock分析

    环境:RR隔离级别

    线上报大量的deadlock 通过命令登录mysql 通过此SHOW ENGINE INNODB STATUSG将打印最近一次死锁

    定位到代码 有这样一个操作

    1.delete from dealer_order_item where dealer_order_code='1111'

    2.insert dealer_order_item(dealer_order_code,.....) values('1111',....)

    dealer_order_code 没有索引

    通过上面的锁分析 RR模式下如果当前读没有满足条件的数据 整个表每一条数据都将会加上next-key锁如下

    session1 session2
    delete from dealer_order_item where dealer_order_code='1111'  
      delete from dealer_order_item where dealer_order_code='2222'
    insert dealer_order_item(dealer_order_code,.....) values('1111',....)  
       

    结果:session1执行insert会死锁 session2执行delete会等待

    1.session1 delete全表扫描获得所有行的gap锁和x锁(gap锁是共享的)

    2.session2执行 delete全表扫描获得gap锁 然后锁等待session释放x锁(gap锁是共享)

    3.session2执行 delete 尝试获得next-key锁,因为gap锁是共享的所以成功拿到,但是X锁session1还没有释放 所以等待

    4.session1执行insert 获取next-key锁。成功获取X锁,因为session1 insert获取gap锁是X锁。发现session2没有释放他获取的gap共享锁。形成相互等待 死锁

    情况2(已经重现)

    session1:

    delete from prm_booking_wine_coin_record_item where type=0;

    session2:

    delete from prm_booking_wine_coin_record_item where type=3;

    非insert模式下 申请的gap锁都是共享锁

    session1

    insert prm_booking_wine_coin_record_item valuse(21,2) 等待session 2释放 3的gap锁

    session2

    insert prm_booking_wine_coin_record_item valuse(22,2) 等待 session 1释放2的gap锁 

    死锁 session2释放锁  session提交成功

    waitlock分析

    表数据id为主键索引 name为非唯一索引 

     

    select * from information_schema.innodb_trx;表 

    trx_id(事物id) trx_state(事物状态) trx_started(事物开始时间) trx_requested_lock_id trx_wait_started(事物开始等待时间) trx_weight trx_mysql_thread_id(事物线程id) trx_query(具体sql) trx_operation_state(事物当前操作状态) trx_tables_in_use(事物中多少个表被使用) trx_tables_locked(事物锁了多少个表) trx_lock_structs trx_lock_memory_bytes(事物锁住的内存大小) trx_rows_locked(事物拥有多少个锁) trx_rows_modified(事物修改的行数) trx_concurrency_tickets(事物并发票数) trx_isolation_level(事物隔离级别) trx_unique_checks(是否唯一检查) trx_foreign_key_checks(是否外键检查) trx_last_foreign_key_error(最后的外键错误) trx_adaptive_hash_latched trx_adaptive_hash_timeout trx_is_read_only trx_autocommit_non_locking
    7842656 LOCKWAIT  2019-01-14 10:22:04 7842656:25:4:4   2019-01-14 10:22:04 8 3733599  update demo set name='5555' where name='3333'   updating or deleting  1136  10  0  REPEATABLE READ 1  NULL  0  0
     7842647  RUNNING 2019-01-14 10:21:37  NULL   NULL  7 3733596  NULL  NULL   0  1 1136   7  REPEATABLE READ  1 NULL   0  0  0

    select * from information_schema.innodb_lock_waits;表

    requesting_trx_id(请求锁的事物id) requested_lock_id(请求锁的锁id) blocking_trx_id(当前拥有锁的事物id) blocking_lock_id(当前拥有锁锁id)
    7842656 7842656:25:4:4 7842647 7842647:25:4:4

    select * from information_schema.innodb_locks;表

    lock_id(锁id) lock_trx_id lock_mode(锁模式) lock_type(锁类型) lock_table(被做锁的表) lock_index(被锁的索引) lock_space(被锁的表空间号) lock_page(被锁的页号) lock_rec(被锁的记录号) lock_data(被锁的数据)
    7842656:25:4:4 7842656(拥有锁的事物id) X,GAP RECORD `dms`.`demo` index_name 25 4 4 '6666', 2
    7842647:25:4:4 7842647 X RECORD `dms`.`demo` index_name 25 4 4 '6666', 2

    1. 先看 表1  事物id 7842656等待7842647释放锁 trx_rows_locked字段看命名像是锁了多少数据 不过我根据数据分析应该是获得了多少个锁 name索引加上聚簇索引id的锁+3个gap锁等于10 

    ctrl+f 搜索:7842647 看关系更佳

    2.分析表2 事物id7842656等待事物id7842647释放7842647:25:4:4这个锁

    3.分析表3就清晰很多了 事物id7842656是请求这个锁7842656:25:4:4

    4.结论 session 2 尝试修改name为5555会获得索引为5555的x锁和gap锁 但是被seesion1获得没释放 所以造成锁等待

    mysql 锁等待分析相关表

    information_schema.innodb_trx表

    包含了正在InnoDB引擎中执行的所有事务的信息,包括waiting for a lock和running的事务

    information_schema.innodb_lock_waits表

    包含了blocked的事务的锁等待的状态

    information_schema.innodb_locks表

    主要包含了InnoDB事务锁的具体情况,包括事务正在申请加的锁和事务加上的锁。

    如何避免deadlock和wait lock

    delete update 避免使用非索引字段为条件

     RR隔离级别将会走聚簇索引 全表扫描为每一行加上next-key锁  注:RC隔离级别会逐行加X锁 并释放

    可以这样理解:delete update  扫描一条就会为一条加上锁   当没有索引会全表扫描 RR隔离级别扫描一条就会为这条加上锁 并不会释放   RC隔离级别扫描一条就会为这条加上锁 如果不满足条件的加上锁之后 会自动释放

    name非索引条件  用于修改条件 虽然有满足条件数据 也会导致全表扫描并逐行加X锁  

    我们为name加上索引条件再进行测试

    alter table demo add index index_name(name);

     可以发现修改成功并不会等待

    避免批量修改删除避免无序

    1.session修改id为1的数据 session2修改id为4的数据各自拿到next-key锁

    2.session1 修改id为4的数据 等待session2释放next-key锁  session2修改id为1的数据等待session1释放next-key锁 造成死锁

    批量修改或者删除 统一排序一下 比如这里根据id 就不会出现交叉修改依赖

    避免隐式转换

    session1 name为varchar  确传入number导致隐式转换走聚簇索引全表扫描 导致整个表都锁了

    记录一个小插曲

    可以看到 name有索引也没有隐式转换 也锁了整个表。因为这个是mysql的优化机制当扫描的数据超过全表的20%~30%时 即便有二级索引也会走扫描整个聚簇索引( 个人测试针对当前读是这样 select不受影响)

    避免where条件全表扫描

    其实总结上面 可以发现 当前读是根据where条件 扫描一条就加一个锁

    避免操作不存在的数据

    修改或者编辑 最好先判断数据不存在 

    我们观察一下锁的情况

    可以发现操作不存在数据会触发gap对应索引排序的gap锁 锁无穷大 影响插入数据(应该在只会在RR模式上出现  不过在不确定数据是否存在 操作之前先判断是否存在 是个好习惯)

  • 相关阅读:
    HDU 1556 差分,前缀和
    Full permutation
    PAT B1029
    字串简介
    阵列(3)
    完形填空
    关于c的比较
    19 阵列的复制
    switch述句
    阵列变数(2)
  • 原文地址:https://www.cnblogs.com/LQBlog/p/10133758.html
Copyright © 2011-2022 走看看