关于死锁

1、问题

应用经常出现如下日志

clip_image001

2、死锁定义

指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

死锁是事务系统中客观存在的事实,你的应该在设计上必须应该考虑处理死锁。一些业务系统可以从头重试事务。

2.1 关于共享锁和排它锁

Innodb存储引擎实现了如下2种标准的行级锁:
共享锁(S lock),允许事务读取一行数据。

排它锁(X lock),允许事务删除或者更新一行数据。若事务T获取了数据对象r的排它锁,则只允许T读取和修改r

锁兼容:当一个事务获取了行r的共享锁,那么另外一个事务也可以立即获取行r的共享锁,因为读取并未改变行r的数据,这种情况就是锁兼容。

锁不兼容:当一个事务获取了行r的共享锁或者排它锁,其他事务想获得行r的排它锁,则它必须等待事务释放行r上的共享锁或者排它锁释放,这种情况就是锁不兼容

二者兼容性如下表格所示:

排它锁和共享锁的兼容性X排它锁S共享锁
X排它锁冲突冲突
S共享锁冲突兼容

简单的说,只有共享锁之间是兼容的

  • 什么情况下应用排它锁?

在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。或者使用select ...for update语句

  • 什么情况下应用共享锁?

加共享锁可以使用select ... lock in share mode语句

注:select不会加任何锁

3、排它锁锁产生过程测试

测试之前,先简单说明下如下三个表的用途

innodb_trx ## 当前运行的所有事务
innodb_locks ## 当前出现的锁
innodb_lock_waits ## 锁等待的对应关系

3.1 建立测试数据

  • 创建表

create table tx1
(id int primary key ,
c1 varchar(20),
c2 varchar(30))
engine=innodb default charset = utf8;
  • 插入数据

insert into tx1 values(1,'aaaa','aaaaa2');
insert into tx1 values(2,'bbbb','bbbbb2');

3.2 模拟

  • 第一步

start transaction;
update tx1 set c1='heyf',c2='heyf' where id=1;

这时候使用如下命令,可以看到有一个事务在执行---这个表有事务不代表一定是锁表,只是代表有一个事务在执行。

SELECT * FROM information_schema.INNODB_TRX\G;

clip_image001[1]

由于没有锁等待,所以使用如下两条命令都查不出数据

SELECT * FROM information_schema.INNODB_locks;
SELECT * FROM information_schema.INNODB_lock_waits;

image

image

  • 第二步

打开另一个session,执行

update tx1 set c1='yyyyy',c2='yyyyy' where id=1;

输入命令的界面一直这样:

clip_image001[3]

然后过了一会,就显示超时

clip_image001[5]

3.3 分析

  • 在显示超时之前,看INNODB_TRX表,如下

注:(显示超时之后,这个事务就消失了,对,如果没有开启事务控制,即在前面执行start transaction命令,那么超时后就会消失)

clip_image001[7]

每个字段含义具体解释如下:

mysql> SELECT * FROM information_schema.INNODB_TRX\G;

*************************** 1. row ***************************

trx_id: 2014137 ##第2个事务

trx_state: LOCK WAIT ## 处于等待状态,事务执行的状态, 允许的值:RUNNING, LOCK WAIT, ROLLING BACK, and COMMITTING.

trx_started: 2016-11-08 16:47:48 ##事务开始时间

trx_requested_lock_id: 2014137:30721:3:2 ##请求的锁ID,事务当前等待的,如果TRX_STATE 是lock_wait;否则就是NULL. 得到信息关于lock,使用LOCK_ID和INNODB_LOCKS表关联

trx_wait_started: 2016-11-08 16:52:05 ##当事务开始等待锁的时间, 如果TRX_STATE is LOCK WAIT; 否则为空

trx_weight: 2

trx_mysql_thread_id: 118544 ##线程 ID,MySQL thread ID,得到细节关于thread, 使用这个列和NFORMATION_SCHEMA PROCESSLIST table的ID进行关联

trx_query: update tx1 set c1='yyyyy',c2='yyyyy' where id=1 ##事务执行的语句

trx_operation_state: starting index read #事务的当前操作 如果有的话 否则为NULL

trx_tables_in_use: 1 ##需要用到1个表

trx_tables_locked: 1 ##事务拥有多少个锁

trx_lock_structs: 2

trx_lock_memory_bytes: 360 #事务锁住的内存大小(B)

trx_rows_locked: 1 ##事务锁住的行数

trx_rows_modified: 0 ##事务更改的行数

trx_concurrency_tickets: 0

trx_isolation_level: REPEATABLE READ #事务隔离级别

trx_unique_checks: 1 #是否唯一性检查

trx_foreign_key_checks: 1 #是否外键检查

trx_last_foreign_key_error: NULL #最后的外键错误

trx_adaptive_hash_latched: 0

trx_adaptive_hash_timeout: 10000

trx_is_read_only: 0

trx_autocommit_non_locking: 0

  • 再看看INNODB_locks

clip_image001[9]

此表具体解释如下:

a) lock_id:锁的id以及被锁住的空间id编号、页数量、行数量

b) lock_trx_id:锁的事务id。

c) lock_mode:锁的模式。

d) lock_type:锁的类型,表锁还是行锁,行锁值是RECORD,表锁值是TABLE

e) lock_table:要加锁的表。

f) lock_index:锁的索引。

g) lock_space:innodb存储引擎表空间的id号码

h) lock_page:被锁住的页的数量,如果是表锁,则为null值。

i) lock_rec:被锁住的行的数量,如果表锁,则为null值。

j) lock_data:被锁住的行的主键值,如果表锁,则为null值。

由上面的信息,可以得出如下:

  • 这里的2014137和2014136是两个事务的ID,2014136修改了1记录未提交,2014137再修改1记录导致死锁
  • 通过lock_page、lock_rec、lock_data不为null,我们知道两个事务都是行锁
  • 通过 lock_mode值,锁的模式是x,两个事务申请的都是排它锁
  • 看相同的数据lock_space、lock_page:、lock_rec,可以得出两个事务都访问了相同的innodb数据块,通过lock_data:1,

看到锁定的数据行都是主键为1的数据记录,可见两个事务都申请了相同的资源,因此会被锁住,事务在等待

  • 再看看INNODB_lock_waits表

clip_image001[11]

此表具体解释如下:

requesting_trx_id ## 请求锁的事务

requested_lock_id: ## 请求锁的锁ID

blocking_trx_id: ## 拥有锁的事务

blocking_lock_id: ## 拥有锁的锁ID

3.4 总结

  • 支持行级锁,id为1的记录锁住的时候,不影响id为2的记录的修改

注:支持行级锁是针对有索引的表,每个表创建的时候会自动对主键生成一个索引,具体看下面行级锁的实现方式

  • 假设A事务对1进行操作未commit,B事务再操作1,这时B事务处于锁等待状态(LOCK WAIT),如果A事务提交,那么B事务也能修改成功,以B事务提交的数据为准。
  • 假设A事务对1进行操作未commit,B事务再操作1,这是B事务处于锁等待状态(LOCK WAIT),锁等待超时后,A事务提交,B事务如果未提交,且这个事务未关闭(此种情况是指B事务开启事务控制的情况下,超时后这个事务不会消失的,会一直存在),相当于开启了事务控制,但是超时了,未能commit,这个事务一直存在,可通过innodb_trx表中查看到。
  • 还有一个,关掉对应的crt窗口,相当于断掉了改对话,该事务就会在事务表中消失。


4、共享锁的产生过程测试

4.1 建立测试数据

  • 创建表

create table tx1
(id int primary key ,
c1 varchar(20),
c2 varchar(30))
engine=innodb default charset = utf8;
  • 插入数据

insert into tx1 values(1,'aaaa','aaaaa2');
insert into tx1 values(2,'bbbb','bbbbb2');

4.2 模拟

  • 第一步

start transaction;
update tx1 set c1='heyf',c2='heyf' where id=1;

还未提交之前,有这样一个事务一直在执行:

clip_image001[13]

  • 第二步

更改tx2表,条件中会查询到tx1表id为1的索引,这时候申请的是S锁,注:这时候不代表已经获得了锁,只是代表在申请,怎么查看

申请还是已获得,这就需要看innodb日志了。

update tx1 set c1='yy'  where id=(select id from tx1 where id=1)

这时候查看innodb_locks表,发现出现死锁了,这个事务申请的是S锁

clip_image001[15]

上面的是update,再尝试使用delete(如下命令),也会产生申请S锁,而导致死锁

delete from tx1  where id=(select id from tx1 where id=1);

clip_image001[17]

注意的是:我单独地执行查询“select id from tx1 where id=1”语句是不会产生S锁的。

4.3 总结

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);

对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

·共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。

·排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。

对于共享锁,有如下

  • insert语句在普通情况下是会申请排他锁,也就是X锁,但是这里出现了S锁。这是因为id字段是一个唯一索引,所以insert语句会在插入前进行一次duplicate key的检查,为了使这次检查成功,需要申请S锁防止其他事务对id字段进行修改。
  • update的时候,id字段是唯一索引,此种场景:UPDATE tbl_name SET column=value WHERE unique_key_col=key_value,产生S锁,防止其他事务对id字段进行修改
  • delete的时候,id字段是唯一索引,DELETE FROM tbl_name WHERE unique_key_col=key_value,产生S锁,防止其他事务对id字段进行修改


5、其他

5.1 关于行锁

InnoDB行锁实现方式

InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!会把所有扫描过的行都锁定!

在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。下面通过一些实际例子来加以说明。

(1)在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。

(2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。

(3)当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁

(4)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁

(5)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

5.2 关于数据库服务器出现死锁的同时,磁盘利用率达到了100%的问题

出现死锁的时候同时磁盘利用率达到了100%,那么有两种情况

1、磁盘达到了100%利用率导致的死锁,

2、死锁导致了磁盘利用率达到了100%

网上找到解释:

当事务开始时,它将缓冲区语句分配一个binlog_cache_size大小的缓冲区(我这里设置的是16777216bytes,即16MB)。 如果一个语句大于此,线程将打开一个临时文件来存储事务(默认是存放在/tmp/目录下)。 当线程结束时,临时文件会自动被删除。

上面就是因为事务里面的临时文件超过16MB了,被放到/tmp目录下了,但是这个临时文件实在太大了,导致磁盘空间不足告警了。



参考:

http://www.jb51.net/article/78511.htm

https://www.cnblogs.com/timxgb/p/9771905.html

posted @ 2019-04-05 21:35  千里之外外  阅读(577)  评论(0编辑  收藏  举报