转载本文请联系MySQL技术公众号。前言:在MySQL运维过程中,锁等待和死锁问题是让DBA和开发同学非常头疼的问题。此类问题的发生会导致业务回滚、卡顿等故障,尤其是对于业务繁忙的系统,出现死锁问题后影响会更加严重。在这篇文章中,我们一起来了解什么是锁等待和死锁,以及如何分析和处理此类问题?1.理解解锁等待和死锁。锁等待或死锁的原因是访问数据库需要加锁。那么你可能会问,为什么需要加锁呢?原因是为了保证并发更新场景下数据的正确性,保证数据库事务的隔离性。想象一个场景。如果想去图书馆借一本书《高性能MySQL》,为了防止有人提前借书,可以提前预约(锁定)。如何添加此锁?封锁图书馆(数据库级锁)锁定所有数据库相关的书籍(表级锁)只锁定MySQL相关的书籍(页级锁)只锁定《高性能MySQL》这本书(行级锁)粒度越细锁,并发级别越高,实现越复杂。锁等待也可以称为事务等待。后面执行的事务等待前面处理的事务释放锁,但是如果等待时间超过MySQL锁等待时间,就会触发这个异常。等待超时后报错“Lockwaittimeoutexceeded...”。出现死锁是因为两个事务互相等待对方释放同一个资源的锁,导致死循环。死锁发生后会立即报错“Deadlockfoundwhentryingtogetlock...”。2、现象复现及处理下面以MySQL5.7.23版本(隔离级别为RR)为例重现上述两种异常现象。mysql>showcreatetabletest_tb\G*********************************1.row*****************************表:test_tbCreateTable:创建表`test_tb`(`id`int(11)NOTNULLAUTO_INCREMENT,`col1`varchar(50)NOTNULLDEFAULT'',`col2`int(11)NOTNULLDEFAULT'1',`col3`varchar(20)NOTNULLDEFAULT'',PRIMARYKEY(`id`),KEY`idx_col1`(`col1`))ENGINE=InnoDBAUTO_INCREMENT=4DEFAULTCHARSET=utf81rowinset(0.00sec)mysql>select*fromtest_tb;+----+------+------+------+|id|col1|col2|col3|+----+------+------+------+|1|fdg|1|abc||2|a|2|fg||3|ghrv|2|rhdv|+----+------+------+-----+3rowsinset(0.00sec)#Transaction1首先执行mysql>begin;QueryOK,0rowsaffected(0.00sec)mysql>select*fromtest_tbwherecol1='a'forupdate;+----+------+------+------+|id|col1|col2|col3|+----+------+------+------+|2|a|2|fg|+----+------+------+------+1rowinset(0.00sec)#Transaction2然后执行mysql>begin;QueryOK,0rowsaffected(0.01sec)mysql>updatetest_tbsetcol2=1wherecol1='a';ERROR1205(HY000):Lockwaittimeoutexceeded;tryrestartingtransactionhasthe以上异常原因是事务2在等待事务1的行锁,但是事务1还没有提交,等待超时报错。InnoDB行锁等待超时时间由innodb_lock_wait_timeout参数控制。该参数默认值为50,单位为秒,即默认情况下,事务2会等待50s。如果还是没有获取到行锁,就会报等待超时异常,回滚这条语句。对于5.7版本,当有锁等待时,我们可以查看information_schema中的几个系统表来查询事务状态。innodb_trx当前运行的所有事务。innodb_locks当前存在的锁。innodb_lock_waits锁等待的对应关系#当发生锁等待时,查看innodb_trx表可以看到所有的事务#trx_state值为LOCKWAIT,表示事务处于等待状态mysql>select*frominformation_schema.innodb_trx\G*****************************1.行***************************trx_id:38511trx_state:LOCKWAITtrx_started:2021-03-2417:20:43trx_requested_lock_id:38511:156:4:2trx_wait_started:2021-03-2417:20:43trx_weight:2trx_mysql_thread_id:1668447trx_query:updatetest_tbsetcol2=1wherecol1='a'trx_operation_state:startingindexreadtrx_tables_in_use:1trx_tables_locked:1trx_lock_structs:2trx_lock_memory_bytes:1136trx_rows_locked:1trx_rows_modified:0trx_concurrency_tickets:0trx_isolation_level:REPEATABLEREADtrx_unique_checks:1trx_foreign_key_checks:1trx_last_foreign_key_error:NULLtrx_adaptive_hash_latched:0trx_adaptive_hash_timeout:0trx_is_read_only:0trx_autocommit_non_locking:0***************************2.row******************************trx_id:38510trx_state:RUNNINGtrx_started:2021-03-2417:18:54trx_requested_lock_id:NULLtrx_wait_started:NULLtrx_weight:4trx_mysql_thread_id:1667530trx_query:NULLtrx_operation_state:NULLtrx_tables_in_use:0trx_tables_locked:1trx_lock_structs:4trx_lock_memory_bytes:1136trx_rows_locked:3trx_rows_modified:0trx_concurrency_tickets:0trx_isolation_level:REPEATABLEREADtrx_unique_checks:1trx_foreign_key_checks:1trx_last_foreign_key_error:NULLtrx_adaptive_hash_latched:0trx_adaptive_hash_timeout:0trx_is_read_only:0trx_autocommit_non_locking:02rowsinset(0.00sec)#innodb_trx字段值含义trx_id:事务IDtrx_state:事务状态,有以下几种状态:RUNNING、LOCKWAIT、ROLLINGBACK和COMMITTING。trx_started:交易开始时间。trx_requested_lock_id:当前等待锁的事务ID,可以和INNODB_LOCKS表JOIN得到更详细的信息。trx_wait_started:事务开始等待的时间。trx_weight:交易的权重。trx_mysql_thread_id:事务线程ID,可与PROCESSLIST表JOIN。trx_query:事务正在执行的SQL语句。trx_operation_state:交易当前的运行状态。trx_tables_in_use:当前事务执行的SQL中使用的表数。trx_tables_locked:当前执行SQL的行锁数。trx_lock_structs:事务持有的锁的数量。trx_isolation_level:当前事务的隔离级别。#sys.innodb_lock_waits视图也可以看到事务等待状态,给出SQLmysql>select*fromsys.innodb_lock_waits\G******************************1.row******************************wait_started:2021-03-2417:20:43wait_age:00:00:22wait_age_secs:22locked_table:`testdb`.`test_tb`locked_index:idx_col1locked_type:RECORDwaiting_trx_id:38511waiting_trx_started:2021-03-2417:20:43waiting_trx_age:00:00:22waiting_trx_rows_locked:1waiting_trx_rows_modified:0waiting_pid:1668447waiting_query:updatetest_tbsetcol2=1wherecol1='a'waiting_lock_id:38511:156:4:2waiting_lock_mode:Xblocking_trx_id:38510blocking_pid:1667530blocking_query:NULLblocking_lock_id:38510:156:4:2blocking_lock_mode:Xblocking_trx_started:2021-03-2417:18:54blocking_trx_age:00:02:11blocking_trx_rows_locked:3blocking_trx_rows_modified:0sql_kill_blocking_query:KILLQUERY1667530sql_kill_blocking_connection:KILL1667530sys.innodb_lock_waits视图整合事务等待状态,同时给出kill杀死阻塞源的语句。但是是否杀掉链接还是需要综合考虑。死锁与锁等待略有不同,我们也简单重现一下死锁现象。#开启两个事务#事务一执行mysql>updatetest_tbsetcol2=1wherecol1='a';QueryOK,1rowaffected(0.00sec)Rowsmatched:1Changed:1Warnings:0#事务二执行mysql>updatetest_tbsetcol2=1whereid=3;QueryOK,1rowaffected(0.00sec)Rowsmatched:1Changed:1Warnings:0#回到事务,执行回车后这条语句处于锁等待状态mysql>updatetest_tbsetcol1='abcd'whereid=3;QueryOK,1rowaffected(5.71sec)Rowsmatched:1Changed:1Warnings:0#回到事务2,重新执行。此时两者互相等待造成死锁mysql>updatetest_tbsetcol3='gddx'wherecol1='a';ERROR1213(40001):Deadlockfoundwhentryingtogetlock;tryrestartingtransaction会在死锁发生后选择一个事务回滚。查找死锁原因可以执行showengineinnodbstatus查看死锁日志,根据死锁日志和业务逻辑进一步定位死锁原因。在实际应用中,我们应该尽量避免死锁的发生。可以从以下几个方面入手:事务尽量小,不要把复杂的逻辑放到一个事务中。当涉及到多行记录时,约定不同的事务以相同的顺序访问。在业务中要及时提交或回滚事务,以减少出现死锁的概率。表必须有适当的索引。尝试将隔离级别更改为RC。总结:本文简单介绍锁等待和死锁产生的原因。事实上,实际业务中的死锁很难分析,需要一定的经验积累。本文仅供初学者参考,希望您能对死锁有一个初步印象。
