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

  • 相关阅读:
    浏览器内核
    手机端开发适配问题
    关于样式的问题
    nginx和uwsgi的区别和作用
    Flask (七) 部署
    Flask (六) 项目(淘票票)
    Flask (五) RESTful API
    Flask (四) 模型进阶
    Flask (三) 数据迁移
    Flask (二) cookie 与 session 模型
  • 原文地址:https://www.cnblogs.com/fuqu/p/10320779.html
Copyright © 2011-2022 走看看