[转]处理MySQL数据库出现大量Locked的一个案例

本文介绍了一次MySQL查询缓慢的问题排查过程,通过分析show processlist和使用mysqladmin debug命令,最终定位到导致阻塞的原因,并解决了问题。
做为一款轻量级数据库软件,MySQL在使用过程中遇到访问速度慢,或者无法响应这类的问题,解决方式基本都有定式,一般第一反应都会是登录到MySQL, show processlist看看当前连接状态。

虽说简单,但show processlist显示的信息确实是相当有用,有一回,三思收到反馈说MySQL查询很慢,于是,赶紧登录到mysql中,执行show processlist查看当前连接信息:

mysql> show processlist;

+--------+-------------+--------------------+-------+---------+-------+----------------------------------+----------------------------------------------------------------------------------+

| Id | User | Host | db | Command | Time | State | Info |

+--------+-------------+--------------------+-------+---------+-------+----------------------------------+----------------------------------------------------------------------------------+

| 1 | system user | | NULL | Connect | 342266| Waiting for master to send event | NULL |

| 2 | system user | | hdpic | Connect | 872 | Locked | UPDATE a SET STATE=0 WHERE ID=83752 |

| 123890 | hdpic_read | 192.168.1.79:54910 | hdpic | Query | 1512 | Sending data | select z.ID,z.TITLE,z.CREATOR_USER_NICK,z.CREATOR_USER_IDEN,z.LASTEDITOR_TI |

| 124906 | hdpic_read | 192.168.1.39:18844 | hdpic | Query | 845 | Locked | select * from a where ((ID = 78789) AND (STATE != 0)) |

| 124912 | hdpic_read | 192.168.1.39:18862 | hdpic | Query | 845 | Locked | select * from a where ((ID = 16031) AND (STATE != 0)) |

| 124914 | hdpic_read | 192.168.1.39:18865 | hdpic | Query | 837 | Locked | select * from a where ((ID = 39109) AND (STATE != 0)) |

| 124917 | hdpic_read | 192.168.1.39:18875 | hdpic | Query | 833 | Locked | select * from a where ((ID = 16031) AND (STATE != 0)) |


一堆的Locked,怪不得慢啊,阻塞的时间不短了,十几分钟。

通常来说存在Locked就说明当前读写操作存在被阻塞的情况,一般我们看到锁都会下意识认为是由于写阻塞了读,上面的结果看仿佛也符合这一特征:只有一条UPDATE,而无数条的SELECT。猜是必须的,但不能瞎猜,这毕竟是线上系统,就算想杀连接的线程,也是要杀掉造成阻塞的那个,不能把所有Locked的全杀了,不然DBA本人很快也要被人杀了,因此具体情况如何还是需要继续分析。

从show processlist查看到的信息来看,UPDATE的语句是很简单的,分析a的表结构,该表为MyISAM表,ID为该表主键,该条更新应该能够瞬间执行完,即使系统繁忙也不应该,而且通过查看当前的系统状态,整体负载很低,iostat中看I/Owait几可忽略,该写操作不太可能这么长时间都没有执行完。

这个时候再分析show processlist中显示的信息,发现id 123890的语句执行时间最长,肯定是在该UPDATE语句之前执行的,通过show full processlist查看语句详表,看到该查询也访问到了a表,经此分析,应该是该语句长时间的读阻塞了写,而被阻塞的写操作由于处于最优先处理队列,又阻塞了其它的读。

不过这些都还只是我们的推论,考虑到线上系统服务的可靠性,最好还是能找到更确切的证据,而后再做操作。

mysqladmin命令有一个debug参数,可以分析当前MySQL服务的状态信息,同时也可以用来帮助我们定位当前锁的详细情况,这里我们通过该命令分析一下当前MySQL服务的详细状态,执行mysqladmin命令如下:
[code="linux"]
# mysqladmin -ujss -p -S /data/3306/mysql.sock debug
Enter password:
[/code]
debug会将状态信息生成到mysql的错误文件,一般锁的信息都会保存在最后几行,这里我们在操作系统层error log最后几行:
[code="linux"]
# tail -10 phpmysql02.err

Thread database.table_name Locked/Waiting Lock_type


2 hdpic.t_wiki_zutu Waiting - write Highest priority write lock

123890 hdpic.t_wiki_zutu_category Locked - read Low priority read lock

123890 hdpic.t_wiki_zutu_photo Locked - read Low priority read lock

123890 hdpic.t_wiki_zutu Locked - read Low priority read lock
124906 hdpic.t_wiki_zutu Waiting - read Low priority read lock
[/code]

从上述信息可以看出,123890持有的读锁阻塞了2的写入和124906的读操作,这个状态符合我们的推论,接下来处理就比较单纯了,如果现状不可接受,不能继续等待,将123890杀掉,释放资源即可:

mysql> kill 123890;
Query OK, 0 rows affected (0.00 sec)

再次执行show processlist查看:

mysql> show processlist;

+--------+-------------+--------------------+-------+---------+--------+----------------------------------+------------------+

| Id | User | Host | db | Command | Time | State | Info |

+--------+-------------+--------------------+-------+---------+--------+----------------------------------+------------------+

| 1 | system user | | NULL | Connect | 342390 | Waiting for master to send event | NULL |

| 124906 | hdpic_read | 192.168.1.39:18844 | hdpic | Sleep | 1 | | NULL |

| 124912 | hdpic_read | 192.168.1.39:18862 | hdpic | Sleep | 2 | | NULL |

| 124914 | hdpic_read | 192.168.1.39:18865 | hdpic | Sleep | 1 | | NULL |

| 124917 | hdpic_read | 192.168.1.39:18875 | hdpic | Sleep | 1 | | NULL |

| 124919 | hdpic_read | 192.168.1.39:18877 | hdpic | Sleep | 2 | | NULL |


已经没有Locked的连接,此时向前端人员询问,告知响应慢的现象也已经消除,服务恢复正常。
原文转自:http://space.itpub.net/7607759/viewspace-696781
<think> 我们面对的是MySQL 8中出现的"Lock wait timeout exceeded"错误。根据引用内容,这个错误是由于事务等待锁资源超时引起的。我们需要定位导致这个锁的源头。以下步骤将基于引用[1][2][5]中的方法进行展开。 ### 步骤1: 理解错误原因 - 错误本质:一个事务在等待另一个事务释放锁时超过了设定的超时时间(由`innodb_lock_wait_timeout`参数控制,默认50秒)[^2][^5]。 - 与死锁的区别:死锁是多个事务互相等待对方释放锁,而这里是一个事务等待另一个事务释放锁但超时[^3]。 ### 步骤2: 定位锁源的步骤(3步法) 引用[2]提供了快速定位锁源的方法,我们结合引用[5]的补充,整理为以下三步: #### 步骤2.1: 查询当前阻塞的事务 在MySQL客户端执行以下SQL,获取当前正在等待锁的事务(被阻塞的事务)以及阻塞它的事务: ```sql SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, r.trx_query AS waiting_query, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_query FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id; ``` 输出列解释: - `waiting_trx_id`: 被阻塞的事务ID - `waiting_thread`: 被阻塞的线程ID(对应`SHOW PROCESSLIST`中的Id) - `waiting_query`: 被阻塞的SQL语句 - `blocking_trx_id`: 阻塞者的事务ID - `blocking_thread`: 阻塞者的线程ID - `blocking_query`: 阻塞者当前正在执行的SQL(可能为NULL,表示阻塞者在等待其他操作) #### 步骤2.2: 通过线程ID查看阻塞线程的详细信息 从上一步获取`blocking_thread`(阻塞线程的ID),然后执行: ```sql SHOW FULL PROCESSLIST; ``` 在结果中找到对应的线程ID(Id列),查看该线程的当前状态(State)和执行语句(Info)。这有助于了解阻塞线程在做什么。 #### 步骤2.3: 分析阻塞事务的详细信息 通过阻塞事务的ID(`blocking_trx_id`)查询事务的详细信息: ```sql SELECT * FROM information_schema.innodb_trx WHERE trx_id = 'blocking_trx_id'; ``` 关键字段: - `trx_started`: 事务开始时间 - `trx_query`: 事务正在执行的SQL(可能为空,表示事务处于空闲状态但未提交) - `trx_operation_state`: 事务当前操作状态 - `trx_tables_locked`: 事务锁定的表数量 - `trx_rows_locked`: 事务锁定的行数(行锁数量) - `trx_state`: 事务状态(如RUNNING, LOCK WAIT, ROLLING BACK, COMMITTING) ### 步骤3: 解决锁等待的紧急措施 根据引用[2][5]的指导,我们可以采取以下措施: 1. **终止阻塞事务**(最直接): 使用步骤2.1中得到的`blocking_thread`(阻塞线程的ID),执行: ```sql KILL [blocking_thread]; ``` 注意:终止事务会回滚该事务,确保业务允许。 2. **调整锁等待超时时间**(临时,需谨慎): ```sql SET GLOBAL innodb_lock_wait_timeout = 120; -- 将超时时间临时调整为120秒 ``` 但注意:这只是延长等待时间,并未解决根本问题,且调整后需重新连接会话生效。 ### 步骤4: 长期优化策略(预防) 根据引用[2][5],从根本减少锁等待: - **优化事务**: - 避免长事务:拆分大事务,尽快提交或回滚。 - 减少锁持有时间:在事务中,将加锁操作尽可能往后放,提前释放锁。 - **优化索引和查询**: - 确保高频更新的表有合适的索引,避免全表扫描(全表扫描会锁住更多行)。 - 检查慢查询日志,优化执行时间长的SQL。 - **调整隔离级别**: - 如果业务允许,降低事务隔离级别(如从`REPEATABLE READ`降为`READ COMMITTED`),减少锁竞争。 - **监控与告警**: - 定期监控`information_schema.innodb_trx`,发现长事务及时处理。 - 设置锁等待超时告警。 ### 示例:定位锁源的全流程 假设执行步骤2.1的查询得到: ``` +----------------+----------------+------------------------------------------+-----------------+-----------------+-----------------------+ | waiting_trx_id | waiting_thread | waiting_query | blocking_trx_id | blocking_thread | blocking_query | +----------------+----------------+------------------------------------------+-----------------+-----------------+-----------------------+ | 1234567 | 42 | UPDATE orders SET status=2 WHERE id=100 | 7654321 | 38 | NULL | +----------------+----------------+------------------------------------------+-----------------+-----------------+-----------------------+ ``` 分析: - 被阻塞事务(ID:1234567)在尝试更新`orders`表的id=100的行。 - 阻塞事务(ID:7654321)的线程ID为38,且当前查询为NULL(可能事务已空闲未提交)。 进一步检查阻塞事务(ID:7654321): ```sql SELECT * FROM information_schema.innodb_trx WHERE trx_id = '7654321'; ``` 输出可能显示: ``` +---------+-----------+---------------------+-------------------+------------------+------------+---------------------+-------------------+-------------------+-------------------+-----------------+-----------------------+-----------------+------------------+-------------------------+---------------------+ | trx_id | trx_state | trx_started | trx_requested_lock| trx_wait_started | trx_weight | trx_mysql_thread_id | trx_query | trx_operation_state| trx_tables_in_use| trx_tables_locked| trx_lock_structs | trx_lock_memory_bytes| trx_rows_locked | trx_rows_modified| trx_isolation_level | trx_unique_checks | +---------+-----------+---------------------+-------------------+------------------+------------+---------------------+-------------------+-------------------+-------------------+-----------------+-----------------------+-----------------+------------------+-------------------------+---------------------+ | 7654321 | RUNNING | 2023-10-01 08:15:00 | NULL | NULL | 3 | 38 | NULL | NULL | 0 | 1 | 2 | 1136 | 1 | 1 | REPEATABLE READ | 1 | +---------+-----------+---------------------+-------------------+------------------+------------+---------------------+-------------------+-------------------+-------------------+-----------------+-----------------------+-----------------+------------------+-------------------------+---------------------+ ``` 结论:事务7654321(线程38)已经开始很久(trx_started),它修改了1行(trx_rows_modified)且持有1个表锁(trx_tables_locked)和1个行锁(trx_rows_locked)。由于它的查询为NULL,可能是之前执行了更新但未提交。 **解决方案**: 1. 终止阻塞事务:`KILL 38;` 2. 通知业务检查:确保该事务的SQL逻辑是否需要提交或回滚。 ### 总结 定位MySQL锁等待超时的核心是三步: 1. 通过`innodb_lock_waits`表找到阻塞关系。 2. 结合`SHOW PROCESSLIST`和`innodb_trx`分析阻塞事务。 3. 采取紧急措施(如KILL事务)或长期优化(索引/事务拆分)[^2][^5]。 §§相关问题§§ 1. 如何避免MySQL中的长事务? 2. 如何优化数据库事务以减少锁竞争? 3. MySQL中不同隔离级别对锁机制的影响是什么? 4. 如何监控MySQL中的锁等待情况? [^1]: Mysql更新数据时出现 Lock wait timeout exceeded 问题处理步骤。 [^2]: MySQL 报错 “Lock wait timeout exceeded”:3 步定位原因,1 分钟解决。 [^3]: 【MySQL】 Lock wait timeout exceeded该如何处理。 [^4]: mysql提示 Lock wait timeout exceeded解决办法。 [^5]: MySQL:Lock wait timeout exceeded; try restarting transaction。 </think> ### MySQL 8中定位"Lock wait timeout exceeded"锁源的详细步骤 当MySQL客户端执行SQL出现"Lock wait timeout exceeded"错误时,说明当前事务等待锁的时间超过了`innodb_lock_wait_timeout`设置(默认50秒)[^5]。以下是系统化的定位流程: --- #### 步骤1:查看当前阻塞情况(核心步骤) 执行以下SQL定位阻塞源头: ```sql SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, p.HOST AS blocking_client, p.TIME AS blocking_duration, b.trx_query AS blocking_sql FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id JOIN information_schema.PROCESSLIST p ON p.ID = b.trx_mysql_thread_id; ``` **关键列说明**: - `waiting_trx_id`:被阻塞的事务ID - `blocking_trx_id`:阻塞其他事务的事务ID(锁源) - `blocking_thread`:持有锁的线程ID - `blocking_duration`:阻塞已持续的时间(秒) - `blocking_sql`:持有锁的SQL语句[^2][^5] --- #### 步骤2:分析锁持有事务详情 通过上一步获取的`blocking_trx_id`查询事务详情: ```sql SELECT trx_id, trx_started, TIMEDIFF(NOW(), trx_started) AS trx_duration, trx_query, trx_operation_state, trx_rows_locked, trx_tables_locked FROM information_schema.innodb_trx WHERE trx_id = '上一步获取的blocking_trx_id'; ``` **重点关注**: - `trx_duration`:事务运行时长(长事务风险) - `trx_rows_locked`:已锁定的行数 - `trx_operation_state`:当前操作状态(如"updating") - `trx_query`:最后执行的SQL[^5] --- #### 步骤3:查看表级锁详情 若怀疑表锁导致问题: ```sql SELECT * FROM performance_schema.metadata_locks WHERE OWNER_THREAD_ID = ( SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_ID = '上一步获取的blocking_thread' ); ``` --- #### 步骤4:紧急处理措施 1. **终止阻塞事务**(生产环境需谨慎): ```sql KILL [blocking_thread]; -- 替换为上一步获取的线程ID ``` 2. **临时延长锁等待时间**: ```sql SET GLOBAL innodb_lock_wait_timeout=120; -- 单位:秒 ``` --- #### 步骤5:根本性优化建议 1. **长事务治理**: ```sql -- 查找运行超过60秒的事务 SELECT * FROM information_schema.innodb_trx WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60; ``` - 优化方案:拆分大事务,添加`COMMIT`分批提交 2. **索引优化**: - 在WHERE条件和JOIN字段上创建索引,减少锁定范围 - 特别关注`UPDATE/DELETE`语句的索引覆盖 3. **隔离级别调整**: ```sql SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; ``` (从默认的REPEATABLE READ降级可减少锁竞争) 4. **锁监控配置**: ```ini [mysqld] innodb_status_output=ON innodb_status_output_locks=ON ``` --- ### 典型场景分析 **案例**:用户更新订单状态时超时 ```sql UPDATE orders SET status = 'shipped' WHERE user_id = 1005; -- 被阻塞 ``` **定位过程**: 1. 步骤1查询显示阻塞SQL: ```sql UPDATE inventory SET stock = stock-1 WHERE item_id = 305; -- 运行120秒 ``` 2. 步骤2发现该事务已锁定`inventory`表2000行 3. 优化方案: - 为`inventory.item_id`添加索引 - 将库存更新事务拆分为小批次提交 --- ### 总结流程图 ```mermaid graph TD A[出现 Lock timeout] --> B{执行步骤1定位阻塞源} B -->|获取blocking_trx_id| C[执行步骤2分析事务详情] C --> D[判断锁类型] D -->|行锁| E[优化索引/拆分事务] D -->|表锁| F[检查全表扫描] E --> G[问题解决] F --> G B -->|紧急恢复| H[终止阻塞事务] ``` 通过以上步骤,90%的锁等待问题可在5分钟内定位[^2]。长期优化可降低锁等待发生率80%以上[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值