数据库卡顿如何快速查询是哪个表被锁住了?

在数据库的日常运维与开发过程中,锁表是一个常见且关键的问题,当多个会话或事务试图同时访问同一资源(如表、行或页)时,数据库管理系统(DBMS)会使用锁机制来保证数据的一致性和完整性,不当的查询或事务设计可能导致锁长时间不被释放,进而引发阻塞、性能下降甚至应用雪崩,快速准确地诊断并定位锁表问题,是每一位数据库从业者必备的技能,本文将以主流的几种关系型数据库为例,系统性地介绍如何查看数据库是否锁表,并提供相应的分析与处理思路。

数据库卡顿如何快速查询是哪个表被锁住了?

MySQL数据库锁表诊断

MySQL中最常用的存储引擎InnoDB支持行级锁,其锁信息相对复杂,我们可以通过查询performance_schemainformation_schema库中的相关表来获取详细的锁信息。

查看当前锁等待情况

一个高效的诊断方法是查找“谁在等待谁的锁”,以下SQL语句可以连接innodb_lock_waitsinnodb_trxinnodb_locksinformation_schema.processlist,清晰地展示出阻塞关系。

SELECT
  r.trx_id waiting_trx_id,
  r.trx_mysql_thread_id waiting_thread,
  r.trx_query waiting_query,
  b.trx_id blocking_trx_id,
  b.trx_mysql_thread_id blocking_thread,
  b.trx_query blocking_query
FROM
  information_schema.innodb_lock_waits w
INNER JOIN
  information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN
  information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id
INNER JOIN
  information_schema.processlist p ON p.id = b.trx_mysql_thread_id;

执行结果解读:

  • waiting_trx_idwaiting_thread:表示正在等待锁的事务ID和其对应的线程ID。
  • waiting_query:该等待线程正在执行的SQL语句。
  • blocking_trx_idblocking_thread:表示持有锁并造成阻塞的事务ID和线程ID。
  • blocking_query:该阻塞线程最近执行的SQL(注意:可能不是当前正在持有锁的语句)。

查看所有锁信息

如果需要更全面地查看当前InnoDB中的所有锁,可以查询innodb_locks表。

SELECT * FROM information_schema.innodb_locks;

关键字段说明:

字段名 描述
lock_trx_id 持有或请求该锁的事务ID。
lock_mode 锁的模式,如X(排他锁)、S(共享锁)、AUTO_INC等。
lock_type 锁的类型,RECORD代表行级锁,TABLE代表表级锁。
lock_table 被锁定的表名。
lock_index 如果是行锁,表示锁定的索引名。
lock_space, lock_page, lock_rec 如果是行锁,这三项共同定位到被锁定的记录位置。

通过分析这些信息,可以精确定位到是哪个事务的哪条SQL锁定了哪张表的哪一行。

PostgreSQL数据库锁表诊断

PostgreSQL提供了一个功能极其强大的系统视图pg_locks,用于查看当前服务器上的锁状态。

查看锁等待关系

与MySQL类似,我们最关心的是锁的等待关系,通过将pg_lockspg_stat_activity视图关联,可以获得非常直观的诊断信息。

SELECT
  blocked_locks.pid AS blocked_pid,
  blocked_activity.usename AS blocked_user,
  blocking_locks.pid AS blocking_pid,
  blocking_activity.usename AS blocking_user,
  blocked_activity.query AS blocked_statement,
  blocking_activity.query AS current_statement_in_blocking_process
FROM
  pg_catalog.pg_locks blocked_locks
JOIN
  pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN
  pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype
  AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
  AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
  AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
  AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
  AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
  AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
  AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
  AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
  AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
  AND blocking_locks.pid != blocked_locks.pid
JOIN
  pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE
  NOT blocked_locks.GRANTED;

执行结果解读:

数据库卡顿如何快速查询是哪个表被锁住了?

  • blocked_pid:被阻塞的进程ID。
  • blocked_user:被阻塞的用户。
  • blocking_pid:造成阻塞的进程ID。
  • blocking_user:造成阻塞的用户。
  • blocked_statement:被阻塞的进程正在执行的SQL。
  • current_statement_in_blocking_process:造成阻塞的进程当前正在执行的SQL。

理解pg_locks关键字段

直接查询pg_locks也可以,但信息比较原始,其关键字段如下:

字段名 描述
pid 持有或请求该锁的后端进程ID。
locktype 锁的类型,如relation(表锁)、tuple(行锁)、transactionid(事务锁)等。
mode 锁的模式,如AccessShareLock(读锁)、RowExclusiveLock等。
granted 布尔值,为true表示锁已获得,为false表示正在等待。
relation 如果是表锁,此字段关联到pg_class表的OID,可以查出表名。

granted字段是判断是否存在等待的关键,当其值为false时,就表示该pid正在等待这个锁。

Oracle数据库锁表诊断

Oracle数据库通过动态性能视图(V$视图)来展示锁信息,诊断锁表问题通常需要关联V$LOCKV$SESSIONV$SQLAREA等视图。

查找阻塞会话与被阻塞会话

以下是一个经典的Oracle锁表诊断查询,它能清晰地展示出会话间的阻塞关系及其执行的SQL。

SELECT
  s1.username AS "Blocking User",
  s1.sid AS "Blocking SID",
  s1.serial# AS "Blocking Serial#",
  s2.username AS "Blocked User",
  s2.sid AS "Blocked SID",
  s2.serial# AS "Blocked Serial#",
  s1.sql_id AS "Blocking SQL_ID",
  s2.sql_id AS "Blocked SQL_ID",
  q1.sql_text AS "Blocking SQL"
FROM
  v$lock l1,
  v$session s1,
  v$lock l2,
  v$session s2,
  v$sql q1
WHERE
  s1.sid = l1.sid
  AND s2.sid = l2.sid
  AND l1.BLOCK = 1
  AND l2.request > 0
  AND l1.id1 = l2.id1
  AND l1.id2 = l2.id2
  AND s1.sql_id = q1.sql_id(+);

执行结果解读:

  • Blocking User/SID:持有锁并导致阻塞的用户和会话ID。
  • Blocked User/SID:等待锁而被阻塞的用户和会话ID。
  • Blocking SQL_ID/SQL:造成阻塞的会话正在执行的SQL及其文本。

理解V$LOCK关键字段

V$LOCK是核心视图,其关键字段说明如下:

字段名 描述
SID 会话标识符。
TYPE 锁的类型,如TM(DML锁,表级)、TX(事务锁,行级)。
ID1/ID2 锁标识符,对于TM锁,ID1是对象的ID;对于TX锁,ID1/ID2标识事务。
LMODE 会话持有的锁模式(0-6,数值越大锁级别越高)。
REQUEST 会话请求的锁模式(0表示没有请求,大于0表示正在等待)。
BLOCK 是否阻塞了其他会话,1表示是,0表示否。

REQUEST > 0时,表明该会话正在等待某个锁。

SQL Server数据库锁表诊断

SQL Server提供了丰富的动态管理视图(DMV)来监控锁状态,其中sys.dm_tran_locks是查看锁信息的主要入口。

查看当前锁信息和阻塞关系

通过关联sys.dm_tran_lockssys.dm_exec_sessionssys.dm_exec_requestssys.dm_exec_sql_text,可以得到完整的锁等待链路。

数据库卡顿如何快速查询是哪个表被锁住了?

SELECT
  t1.resource_type,
  t1.resource_database_id,
  t1.resource_associated_entity_id,
  t1.request_mode,
  t1.request_session_id,
  t2.blocking_session_id,
  t3.text AS 'Requesting SQL'
FROM
  sys.dm_tran_locks AS t1
INNER JOIN
  sys.dm_exec_requests AS t2 ON t1.request_session_id = t2.session_id
CROSS APPLY
  sys.dm_exec_sql_text(t2.sql_handle) AS t3
WHERE
  t1.request_status = 'GRANT' AND t2.blocking_session_id <> 0;

执行结果解读:

  • resource_type: 被锁定的资源类型(如OBJECT, PAGE, KEY)。
  • request_mode: 请求的锁模式(如X, S, IX)。
  • request_session_id: 请求锁的会话ID。
  • blocking_session_id: 阻塞当前会话的会话ID。
  • Requesting SQL: 被阻塞会话正在执行的SQL。

如果blocking_session_id大于0,就说明该会话被阻塞,要找到阻塞源,可以用这个blocking_session_idsys.dm_exec_sessions中查找其详细信息。

小编总结与处理策略

无论使用哪种数据库,诊断锁表问题的思路都是一致的:

  1. 识别症状:应用响应缓慢,数据库CPU或I/O正常但大量会话处于等待状态。
  2. 定位阻塞链:使用上述特定数据库的查询方法,找到“谁阻塞了谁”。
  3. 分析根源:查看持有锁的会话正在执行的SQL,长时间运行的事务、未提交的事务、复杂的查询或不合理的业务逻辑是根本原因。
  4. 采取措施
    • 紧急处理:在业务影响巨大的情况下,可以考虑KILL掉造成阻塞的会话(如MySQL的KILL [thread_id],Oracle的ALTER SYSTEM KILL SESSION 'sid,serial#',PostgreSQL的SELECT pg_terminate_backend(pid))。注意:此操作有风险,可能导致事务回滚和数据不一致,需谨慎评估。
    • 根治优化:与开发人员协作,优化SQL性能,缩短事务长度,调整应用逻辑,避免在事务中进行用户交互或外部调用。

相关问答FAQs

问题1:为什么我的数据库会频繁出现锁表?

答: 数据库频繁锁表通常是由以下几个原因造成的:

  • 长事务:一个事务开始后,长时间未提交(COMMIT)或回滚(ROLLBACK),它持有的锁会一直存在,阻塞其他会话。
  • 不恰当的隔离级别:例如使用了“可串行化”隔离级别,虽然保证了最强的数据一致性,但会极大地增加锁冲突的概率。
  • SQL效率低下:复杂的全表扫描、未使用索引的查询等,会锁定大量的数据行或整个表,增加锁冲突的几率。
  • 应用层逻辑问题:应用程序代码中,在事务内嵌套了耗时操作(如调用外部API、等待用户输入),导致事务被长时间挂起。
  • 并发量过高:在高并发场景下,多个会话同时争抢同一资源的概率增大,锁等待成为常态。

问题2:杀死锁表进程安全吗?有什么风险?

答: 杀死锁表进程通常被视为一种紧急的“救火”手段,而不是常规解决方案,它存在一定的风险,操作前务必谨慎评估。

  • 数据风险:被杀死的事务会立即回滚,如果该事务已经执行了大量的数据修改操作(如UPDATE, DELETE, INSERT),回滚过程可能会消耗较长时间和较多系统资源,并在回滚完成前继续持有某些锁,暂时性地加剧问题,如果事务涉及业务逻辑,部分成功的操作可能无法完全撤销,造成业务数据不一致。
  • 应用风险:对应的应用程序会立即收到数据库错误(如“会话被杀死”、“连接已中断”),如果应用没有对此类错误做妥善的重试或补偿机制,可能会导致用户操作失败、数据提交不完整等问题。
  • 业务影响:虽然解决了阻塞,但被杀死进程所代表的业务操作也失败了,可能需要人工介入处理或要求用户重新操作。

更推荐的做法是:优先分析锁产生的根本原因,通过优化SQL、调整事务、改进代码来根治问题,只有在业务影响巨大,且无法立即通过优化代码解决时,才考虑杀死进程作为临时解决方案。

【版权声明】:本站所有内容均来自网络,若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!

(0)
热舞的头像热舞
上一篇 2025-10-15 01:37
下一篇 2025-10-15 01:40

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

广告合作

QQ:14239236

在线咨询: QQ交谈

邮件:asy@cxas.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信