zoukankan      html  css  js  c++  java
  • 关于死锁

    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_TRXG;

    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_TRXG;

    *************************** 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

  • 相关阅读:
    scala之伴生对象的继承
    scala之伴生对象说明
    “Failed to install the following Android SDK packages as some licences have not been accepted” 错误
    PATH 环境变量重复问题解决
    Ubuntu 18.04 配置java环境
    JDBC的基本使用2
    DCL的基本语法(授权)
    ZJNU 1374
    ZJNU 2184
    ZJNU 1334
  • 原文地址:https://www.cnblogs.com/fuqu/p/10320779.html
Copyright © 2011-2022 走看看