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
行锁和gap锁的组合
当前读与快照读
快照读
简单的查询 select * from student
值得注意的是 在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 lock in share mode(共享锁)
select * student for update(排他锁)
insert
delete
update
快照读是不加锁的 当前读 都会获取相应的锁
聚簇索引以及二级索引
mysql加锁不是锁住某一行数据而是在表的索引上面加锁 理解聚簇索引和二级索引能够很好的帮助我们理解锁
什么是聚簇索引?
在innodb下 数据的存储顺序跟索引的存储存储顺序是一样 聚簇索引的页节点就指向数据,每个表都会有一个聚簇索引 默认在主键上 如果没有找到将会在表中的唯一非空的列上加上聚簇索引 如果没有mysql将自动维护一个隐式的列作为聚簇索引

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

mysql RC和RR隔离级别的加锁方式
现在有student表有以下数据
| id | name |
| 1 | a |
| 2 | b |
| 3 | d |
| 4 | f |
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 STATUS\G将打印最近一次死锁
定位到代码 有这样一个操作
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锁
2.session2执行 delete全表扫描获得gap锁 然后锁等待session释放x锁
3.session2执行 insert 尝试获得gap锁 因为session2已经拿到gap锁但是未拿到x锁, 所以不能插入等待 因为session2也在等待session1释放x锁(所以死锁)
情况2(未验证过 生产环境遇到过2个insert死锁情况)
如果where条件都不存在 2个delete都拿到gap锁(锁无穷大)而没有行锁 然后各自执行insert相互等待gap锁 导致死锁
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 | 1 | 1 | 5 | 1136 | 10 | 3 | 0 | REPEATABLE READ | 1 | 1 | NULL | 0 | 0 | 0 | 0 |
| 7842647 | RUNNING | 2019-01-14 10:21:37 | NULL | NULL | 7 | 3733596 | NULL | NULL | 0 | 1 | 4 | 1136 | 7 | 3 | 0 | REPEATABLE READ | 1 | 1 | NULL | 0 | 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模式上出现 不过在不确定数据是否存在 操作之前先判断是否存在 是个好习惯)
mysql deadlock、Lock wait timeout解决和分析的更多相关文章
- 排查mysql innodb Lock wait timeout exceeded; try restarting transaction的问题
OMG写的时候崩溃了一次. 触发关注这个问题的事情是 我们在使用pt-online-schedule 改表的时候总是拿不到锁,并且报出mysql innodb Lock wait timeout ex ...
- mysql异常Lock wait timeout exceeded; try restarting transaction
mysql中使用update语句更新数据报错: Lock wait timeout exceeded; try restarting transaction. 这是由于你要更新的表的锁在其它线程手里. ...
- Mysql错误: Lock wait timeout exceeded 解决办法
一.临时解决办法: 执行mysql命令:show full processlist; 然后找出插入语句的系统id 执行mysql命令:kill id 或 首先,查看数据库的进程信息: show ful ...
- mysql提示 Lock wait timeout exceeded解决办法 事务锁死
查询 select concat('KILL ',id,';') from information_schema.processlist; 复制结果 新建sql脚本粘贴并执行
- mysql 异常 Lock wait timeout exceeded; try restarting transaction;expc=java.sql.SQLExcept
这种一般是等锁超时了,可以设置延长等锁时间. mysql> set innodb_lock_wait_timeout=100 Query OK, 0 rows affected (0.02 se ...
- mysql死锁,等待资源,事务锁,Lock wait timeout exceeded; try restarting transaction解决
前面已经了解了InnoDB关于在出现锁等待的时候,会根据参数innodb_lock_wait_timeout的配置,判断是否需要进行timeout的操作,本文档介绍在出现锁等待时候的查看及分析处理: ...
- mysql Lock wait timeout exceeded; try restarting transaction解决
前面已经了解了InnoDB关于在出现锁等待的时候,会根据参数innodb_lock_wait_timeout的配置,判断是否需要进行timeout的操作,本文档介绍在出现锁等待时候的查看及分析处理: ...
- Mysql错误: ERROR 1205: Lock wait timeout exceeded解决办法(MySQL锁表、事物锁表的处理方法)
Java执行一个SQL查询未提交,遇到1205错误. java.lang.Exception: ### Error updating database. Cause: java.sql.SQLExc ...
- MySQL事务锁等待超时 Lock wait timeout exceeded; try restarting transaction
工作中处理定时任务分发消息时出现的问题,在查找并解决问题的时候,将相关的问题博客收集整理,在此记录下,以便之后再遇到相同的问题,方便查阅. 问题场景 问题出现的场景: 在消息队列处理消息时,同一事务内 ...
随机推荐
- poi读取docx中的文字和图片(自己应用)
poi读取docx中的文字和图片(自己应用) package com.fry.poiDemo.dao; import java.io.File; import java.io.FileInputStr ...
- 【BZOJ 1601】 灌水
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1601 [算法] 最小生成树 [代码] #include<bits/stdc++ ...
- php获得本机ipv4地址
if (isset($_ENV["HOSTNAME"])) $MachineName = $_ENV["HOSTNAME"]; else if (isset($ ...
- codevs1222 信与信封问题
1222 信与信封问题 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description John先生晚上写了n封信,并相应地写了 ...
- # 深入理解Redis(二)——内存管理的建议与技巧
引语 随着使用Redis的深入,我们不可避免的需要深入了解优化Redis的内存,本章将重点讲解Redis的内存优化之道,同时推荐大家阅读memory-optimization一文. 想要高效的使用Re ...
- css3动画之1--animation小例子
1.首先看效果 2.代码及分析 <style type="text/css"> #div1 { margin:100px; position: absolute; te ...
- 解决Latex复制到公众号可能报“图片粘贴失败”的问题
前几天出了个版本,还发了篇“Md2All,让公众号完美显示Latex数学公式”的文章,发完后,心里还是不太爽的,因为那个版本还是遗留了一个问题:当把Latex公式转换为本地图片,再复制到公众号时,有可 ...
- NetCore下获取项目文件路径
我要获取的是doc/FPFile.xml 百度了一大堆就是找不到解决问题. 把属性更改为始终赋值, XmlDocument xdi = new XmlDocument(); xdi.Load((&qu ...
- css单双行样式
#random_box li:nth-child(odd) {//双行 background: #fff5c4; } #random_box li:nth-child(even) {//单行 back ...
- kerberos认证原理---讲的非常细致,易懂
前几天在给人解释Windows是如何通过Kerberos进行Authentication的时候,讲了半天也别把那位老兄讲明白,还差点把自己给绕进去.后来想想原因有以下两点:对于一个没有完全不了解Ker ...