oracle

关注公众号 jb51net

关闭
首页 > 数据库 > oracle > Oracle锁表的解决及避免

Oracle锁表的解决方法及避免锁表问题的最佳实践

作者:J.P.August

在 Oracle 数据库中,锁表或锁超时相信大家都不陌生,是一个常见的问题,尤其是在执行 DML(数据操作语言)语句时,本文将详细介绍如何解决锁表问题以及如何查找引起锁表的 SQL 语句,并提供避免锁表问题的最佳实践,需要的朋友可以参考下

背景介绍

在 Oracle 数据库中,锁表或锁超时相信大家都不陌生,是一个常见的问题,尤其是在执行 DML(数据操作语言)语句时。当一个会话对表或行进行锁定但未提交事务时,其他会话可能会因为等待锁资源而出现超时。这种情况不仅会影响数据库性能,还可能导致应用程序异常(java.sql.SQLException: Lock wait timeout exceeded)。

本文将详细介绍如何解决锁表问题以及如何查找引起锁表的 SQL 语句,并提供避免锁表问题的最佳实践。

锁表的原因

  1. 独占式封锁机制:Oracle 使用独占式封锁机制来确保数据的一致性。当一个会话对数据进行修改时,会对其加锁,直到事务提交或回滚。
  2. 长时间运行的 SQL 语句:某些 SQL 语句可能由于性能问题或其他原因而长时间运行,导致锁资源一直被占用。
  3. 高并发场景:在高并发环境下,多个会话同时访问相同的数据,可能会导致锁竞争,从而引发死锁。

解决锁表的方法

临时解决方案

SELECT L.SESSION_ID, S.SERIAL#, L.LOCKED_MODE AS "锁模式", 
       L.ORACLE_USERNAME AS "所有者", L.OS_USER_NAME AS "登录系统用户名", 
       S.MACHINE AS "系统名", S.TERMINAL AS "终端用户名", 
       O.OBJECT_NAME AS "被锁表对象名", S.LOGON_TIME AS "登录数据库时间"
  FROM V$LOCKED_OBJECT L
  INNER JOIN ALL_OBJECTS O ON O.OBJECT_ID = L.OBJECT_ID
  INNER JOIN V$SESSION S ON S.SID = L.SESSION_ID;
ALTER SYSTEM KILL SESSION 'SESSION_ID, SERIAL#';

示例

假设 session1 修改了某条数据但未提交事务,session2 查询未提交事务的那条记录时会被阻塞。

SELECT L.SESSION_ID, S.SERIAL#, L.LOCKED_MODE AS "锁模式", 
       L.ORACLE_USERNAME AS "所有者", L.OS_USER_NAME AS "登录系统用户名", 
       S.MACHINE AS "系统名", S.TERMINAL AS "终端用户名", 
       O.OBJECT_NAME AS "被锁表对象名", S.LOGON_TIME AS "登录数据库时间"
  FROM V$LOCKED_OBJECT L
  INNER JOIN ALL_OBJECTS O ON O.OBJECT_ID = L.OBJECT_ID
  INNER JOIN V$SESSION S ON S.SID = L.SESSION_ID;


 SESSION_ID	SERIAL#	锁模式	所有者	登录系统用户名	系统名	终端用户名	被锁表对象名	登录数据库时间
----------  ------- ----- ------ ------------- ----- --------- --------- ------------
29	84	3 IN	test	WORKGROUP\LA...	LAPTOP-9FDC2903	LIN_USER	2023/2/26 11:08:08
ALTER SYSTEM KILL SESSION '29, 84';

查找被锁对象

SELECT COUNT(1) FROM V$LOCKED_OBJECT;
SELECT B.OWNER, B.OBJECT_NAME, A.SESSION_ID, A.LOCKED_MODE
  FROM V$LOCKED_OBJECT A, DBA_OBJECTS B
 WHERE B.OBJECT_ID = A.OBJECT_ID;
SELECT T2.USERNAME, T2.SID, T2.SERIAL, T2.LOGON_TIME
  FROM V$LOCKED_OBJECT T1, V$SESSION T2
 WHERE T1.SESSION_ID = T2.SID
 ORDER BY T2.LOGON_TIME;
ALTER SYSTEM KILL SESSION '253, 9542';

查看当前系统中锁表情况

SELECT * FROM V$LOCKED_OBJECT;
SELECT SESS.SID, SESS.SERIAL#, LO.ORACLE_USERNAME, LO.OS_USER_NAME, AO.OBJECT_NAME, LO.LOCKED_MODE
  FROM V$LOCKED_OBJECT LO, DBA_OBJECTS AO, V$SESSION SESS, V$PROCESS P
 WHERE AO.OBJECT_ID = LO.OBJECT_ID
   AND LO.SESSION_ID = SESS.SID;

查找引起锁表的 SQL 语句

SELECT L.SESSION_ID SID, S.SERIAL#, L.LOCKED_MODE, L.ORACLE_USERNAME, S.USER#, L.OS_USER_NAME, S.MACHINE, S.TERMINAL, A.SQL_TEXT, A.ACTION
  FROM V$SQLAREA A, V$SESSION S, V$LOCKED_OBJECT L
 WHERE L.SESSION_ID = S.SID
   AND S.PREV_SQL_ADDR = A.ADDRESS
 ORDER BY SID, S.SERIAL#;
SET LINE 200;
COL TERMINAL FORMAT A10;
COL PROGRAM FORMAT A20;
COL USERNAME FORMAT A10;
COL MACHINE FORMAT A10;
COL SQL_TEXT FORMAT A40;
SELECT A.SID, A.SERIAL#, A.USERNAME, A.COMMAND, A.LOCKWAIT, A.STATUS, A.MACHINE, A.TERMINAL, A.PROGRAM, A.SECONDS_IN_WAIT, B.SQL_TEXT
  FROM V$SESSION A, V$SQL B
 WHERE B.SQL_ID = A.SQL_ID
   AND (A.BLOCKING_INSTANCE IS NOT NULL AND A.BLOCKING_SESSION IS NOT NULL);
WITH lk AS (
  SELECT BLOCKING_INSTANCE || '.' || BLOCKING_SESSION AS blocker, INST_ID || '.' || SID AS waiter
    FROM GV$SESSION
   WHERE BLOCKING_INSTANCE IS NOT NULL AND BLOCKING_SESSION IS NOT NULL
)
SELECT LPAD('  ', 2 * (LEVEL - 1)) || WAITER LOCK_TREE
  FROM (
    SELECT * FROM lk
    UNION ALL
    SELECT DISTINCT 'root', BLOCKER FROM lk
    WHERE BLOCKER NOT IN (SELECT WAITER FROM lk)
  )
CONNECT BY PRIOR WAITER = BLOCKER
START WITH BLOCKER = 'root';
WITH lk AS (
  SELECT A.BLOCKING_INSTANCE || '.' || A.BLOCKING_SESSION AS blocker,
         A.INST_ID || '.' || A.SID AS waiter,
         (SELECT B.SQL_TEXT || '  ALTER SYSTEM KILL SESSION ''' || C.SID || ', ' || C.SERIAL# || ''''
            FROM GV$SQLAREA B, GV$SESSION C
           WHERE A.BLOCKING_INSTANCE = C.INST_ID
             AND C.SID = A.BLOCKING_SESSION
             AND (C.SQL_ID = B.SQL_ID OR C.PREV_SQL_ID = B.SQL_ID)) AS kill_block_sql,
         (SELECT B.SQL_TEXT || '  ALTER SYSTEM KILL SESSION ''' || A.SID || ', ' || A.SERIAL# || ''''
            FROM GV$SQLAREA B
           WHERE A.INST_ID = B.INST_ID
             AND A.SQL_ID = B.SQL_ID) AS kill_waiter_sql
    FROM GV$SESSION A
   WHERE A.BLOCKING_INSTANCE IS NOT NULL AND A.BLOCKING_SESSION IS NOT NULL
)
SELECT LPAD('  ', 2 * (LEVEL - 1)) || WAITER || '  ' || KILL_WAITER_SQL LOCK_TREE
  FROM (
    SELECT BLOCKER, WAITER, KILL_WAITER_SQL FROM lk
    UNION ALL
    SELECT DISTINCT 'root', BLOCKER, KILL_BLOCK_SQL FROM lk
    WHERE BLOCKER NOT IN (SELECT WAITER FROM lk)
  )
CONNECT BY PRIOR WAITER = BLOCKER
START WITH BLOCKER = 'root';
COL BLOCK_MSG FOR A80
SELECT C.TERMINAL || ' (''' || A.SID || ',' || C.SERIAL# || ''') is blocking ' || B.SID BLOCK_MSG
  FROM V$LOCK A, V$LOCK B, V$SESSION C
 WHERE A.ID1 = B.ID1
   AND A.ID2 = B.ID2
   AND A.BLOCK > 0
   AND A.SID <> B.SID
   AND A.SID = C.SID;

避免锁表问题的最佳实践

1. 优化 SQL 语句

2. 使用合适的隔离级别

3. 优化索引

4. 使用分区表

5. 优化应用程序逻辑

6. 监控和调优

7. 使用数据库特性

8. 事务管理

9. 数据库配置

10. 定期维护

总结

通过上述步骤,可以有效地解决 Oracle 数据库中的锁表问题,并找到引起锁表的 SQL 语句。同时,通过实施最佳实践,可以显著减少锁表问题的发生,提高系统的并发性能和稳定性。

以上就是Oracle锁表的解决方法及避免锁表问题的最佳实践的详细内容,更多关于Oracle锁表的解决及避免的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文