文章目录
正文
1. 锁基础概述
锁是Oracle数据库中用于控制并发访问数据的重要机制,确保数据的一致性和完整性。Oracle使用多种类型的锁来管理不同级别的资源访问。
1.1 锁的概念与作用
1.2 锁的工作原理
1.3 Oracle锁的分类
2. 行级锁 (Row-Level Locks)
行级锁是Oracle中最常用的锁类型,提供最高的并发性。
2.1 行级锁的基本概念
2.1.1 TX锁(事务锁)
-- 演示行级锁的基本操作
-- Session 1
BEGIN
UPDATE employees
SET salary = salary + 1000
WHERE employee_id = 100;
-- 此时employee_id=100的行被锁定
-- 不要提交,保持锁定状态
END;
/
-- 查看当前锁定信息
SELECT
s.sid,
s.serial#,
s.username,
s.program,
l.type,
l.mode_held,
l.mode_requested,
o.object_name,
l.block
FROM v$lock l
JOIN v$session s ON l.sid = s.sid
JOIN dba_objects o ON l.id1 = o.object_id
WHERE s.username IS NOT NULL
ORDER BY s.sid;
2.1.2 行级锁的工作机制
-- 创建测试表来演示锁机制
CREATE TABLE lock_demo (
id NUMBER PRIMARY KEY,
name VARCHAR2(50),
amount NUMBER,
last_updated DATE DEFAULT SYSDATE
);
-- 插入测试数据
INSERT INTO lock_demo VALUES (1, 'Record 1', 1000, SYSDATE);
INSERT INTO lock_demo VALUES (2, 'Record 2', 2000, SYSDATE);
INSERT INTO lock_demo VALUES (3, 'Record 3', 3000, SYSDATE);
COMMIT;
-- Session 1: 锁定第一行
UPDATE lock_demo SET amount = 1500 WHERE id = 1;
-- 不提交,保持锁定
-- Session 2: 可以访问其他行
SELECT * FROM lock_demo WHERE id = 2; -- 成功
UPDATE lock_demo SET amount = 2500 WHERE id = 2; -- 成功
-- Session 2: 尝试访问被锁定的行
UPDATE lock_demo SET amount = 1200 WHERE id = 1; -- 等待
2.2 行级锁的类型
2.2.1 共享行级锁 (SELECT FOR UPDATE)
-- 使用SELECT FOR UPDATE获取共享行级锁
DECLARE
CURSOR emp_cursor IS
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE department_id = 20
FOR UPDATE;
emp_rec emp_cursor%ROWTYPE;
BEGIN
DBMS_OUTPUT.PUT_LINE('=== 锁定部门20的所有员工 ===');
OPEN emp_cursor;
LOOP
FETCH emp_cursor INTO emp_rec;
EXIT WHEN emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('锁定员工: ' || emp_rec.employee_id ||
' - ' || emp_rec.first_name || ' ' || emp_rec.last_name);
-- 在这里可以进行其他业务逻辑
-- 所有相关行都被锁定,防止其他会话修改
END LOOP;
CLOSE emp_cursor;
-- 可以选择提交或回滚
COMMIT; -- 释放所有锁
END;
/
2.2.2 排他行级锁 (UPDATE/DELETE)
-- 演示排他行级锁
BEGIN
-- 更新操作自动获取排他锁
UPDATE employees
SET salary = salary * 1.1
WHERE department_id = 10;
DBMS_OUTPUT.PUT_LINE('更新了 ' || SQL%ROWCOUNT || ' 行');
-- 删除操作也获取排他锁
DELETE FROM lock_demo WHERE amount < 1000;
DBMS_OUTPUT.PUT_LINE('删除了 ' || SQL%ROWCOUNT || ' 行');
-- 提交释放锁
COMMIT;
END;
/
2.3 行级锁的高级特性
2.3.1 SELECT FOR UPDATE的选项
-- NOWAIT选项:立即返回而不等待
BEGIN
-- 尝试锁定,如果不能立即获得锁就返回错误
SELECT employee_id, salary
INTO :emp_id, :emp_salary
FROM employees
WHERE employee_id = 100
FOR UPDATE NOWAIT;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -54 THEN -- ORA-00054: resource busy
DBMS_OUTPUT.PUT_LINE('资源正忙,无法获得锁');
ELSE
DBMS_OUTPUT.PUT_LINE('其他错误: ' || SQLERRM);
END IF;
END;
/
-- WAIT选项:指定等待时间
DECLARE
v_emp_id NUMBER;
v_salary NUMBER;
BEGIN
SELECT employee_id, salary
INTO v_emp_id, v_salary
FROM employees
WHERE employee_id = 100
FOR UPDATE WAIT 5; -- 等待5秒
DBMS_OUTPUT.PUT_LINE('成功获得锁,员工ID: ' || v_emp_id);
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -30006 THEN -- ORA-30006: resource busy; acquire with WAIT timeout expired
DBMS_OUTPUT.PUT_LINE('等待超时,无法获得锁');
ELSE
DBMS_OUTPUT.PUT_LINE('错误: ' || SQLERRM);
END IF;
END;
/
2.3.2 选择性列锁定
-- 只锁定特定列
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE department_id = 30
FOR UPDATE OF salary;
-- 跳过已锁定的行
SELECT employee_id, first_name, last_name, salary
FROM employees
WHERE department_id = 40
FOR UPDATE SKIP LOCKED;
3. 表级锁 (Table-Level Locks)
表级锁控制对整个表的访问,通常在DDL操作或需要表级控制时使用。
3.1 表级锁的类型
3.1.1 表锁模式
-- 手动获取表级锁
-- 共享锁模式 (Share Mode)
LOCK TABLE employees IN SHARE MODE;
-- 排他锁模式 (Exclusive Mode)
LOCK TABLE lock_demo IN EXCLUSIVE MODE;
-- 行共享锁模式 (Row Share Mode)
LOCK TABLE employees IN ROW SHARE MODE;
-- 行排他锁模式 (Row Exclusive Mode)
LOCK TABLE employees IN ROW EXCLUSIVE MODE;
-- 共享行排他锁模式 (Share Row Exclusive Mode)
LOCK TABLE employees IN SHARE ROW EXCLUSIVE MODE;
3.1.2 锁兼容性矩阵
3.2 DDL锁
DDL操作会自动获取表级排他锁。
3.2.1 DDL锁的影响
-- DDL操作示例
-- 创建表时的锁定
CREATE TABLE ddl_lock_demo (
id NUMBER,
description VARCHAR2(100),
created_date DATE DEFAULT SYSDATE
);
-- 修改表结构时的锁定
ALTER TABLE ddl_lock_demo ADD (
status VARCHAR2(20) DEFAULT 'ACTIVE'
);
-- 创建索引时的锁定(在线DDL)
CREATE INDEX CONCURRENTLY idx_ddl_demo_status
ON ddl_lock_demo(status) ONLINE;
-- 删除表时的锁定
DROP TABLE ddl_lock_demo;
3.2.2 在线DDL操作
-- 在线添加列(不锁定表)
ALTER TABLE employees ADD (
bonus_pct NUMBER(3,2) DEFAULT 0.00
) ONLINE;
-- 在线创建索引
CREATE INDEX idx_emp_bonus
ON employees(bonus_pct)
ONLINE;
-- 在线重建索引
ALTER INDEX idx_emp_bonus REBUILD ONLINE;
4. 死锁检测与处理
死锁是两个或多个事务相互等待对方释放锁的情况。
4.1 死锁的产生
4.1.1 典型死锁场景
-- 创建死锁演示表
CREATE TABLE deadlock_demo1 (id NUMBER PRIMARY KEY, value VARCHAR2(50));
CREATE TABLE deadlock_demo2 (id NUMBER PRIMARY KEY, value VARCHAR2(50));
INSERT INTO deadlock_demo1 VALUES (1, 'Value A');
INSERT INTO deadlock_demo2 VALUES (1, 'Value B');
COMMIT;
-- Session 1:
BEGIN
UPDATE deadlock_demo1 SET value = 'Updated A' WHERE id = 1;
-- 等待几秒,然后执行下面的语句
DBMS_LOCK.SLEEP(5);
UPDATE deadlock_demo2 SET value = 'Updated B from Session 1' WHERE id = 1;
COMMIT;
END;
/
-- Session 2: (同时在另一个会话中执行)
BEGIN
UPDATE deadlock_demo2 SET value = 'Updated B' WHERE id = 1;
-- 等待几秒,然后执行下面的语句
DBMS_LOCK.SLEEP(5);
UPDATE deadlock_demo1 SET value = 'Updated A from Session 2' WHERE id = 1;
COMMIT;
END;
/
4.1.2 死锁检测机制
-- 查看死锁信息的存储过程
CREATE OR REPLACE PROCEDURE check_deadlocks
AS
BEGIN
-- 检查当前的锁等待情况
FOR rec IN (
SELECT
waiting.sid AS waiting_session,
waiting.username AS waiting_user,
waiting.machine AS waiting_machine,
holding.sid AS holding_session,
holding.username AS holding_user,
holding.machine AS holding_machine,
l.type AS lock_type,
o.object_name
FROM v$lock l1
JOIN v$lock l2 ON l1.id1 = l2.id1 AND l1.id2 = l2.id2
JOIN v$session waiting ON l1.sid = waiting.sid
JOIN v$session holding ON l2.sid = holding.sid
JOIN dba_objects o ON l1.id1 = o.object_id
WHERE l1.block = 0 AND l2.block = 1
AND l1.type = l2.type
) LOOP
DBMS_OUTPUT.PUT_LINE('等待会话: ' || rec.waiting_session ||
' 用户: ' || rec.waiting_user ||
' 被会话: ' || rec.holding_session ||
' 用户: ' || rec.holding_user || ' 阻塞');
DBMS_OUTPUT.PUT_LINE('锁类型: ' || rec.lock_type ||
' 对象: ' || rec.object_name);
DBMS_OUTPUT.PUT_LINE('---');
END LOOP;
END;
/
-- 执行死锁检查
EXEC check_deadlocks;
4.2 死锁的解决
4.2.1 Oracle自动死锁检测
Oracle会自动检测死锁并回滚其中一个事务。
-- 监控死锁事件
SELECT
name,
value
FROM v$sysstat
WHERE name LIKE '%deadlock%';
-- 查看死锁的详细信息
SELECT
to_char(timestamp, 'YYYY-MM-DD HH24:MI:SS') AS deadlock_time,
substr(message_text, 1, 100) AS message
FROM dba_hist_active_sess_history
WHERE event = 'enq: TX - row lock contention'
ORDER BY timestamp DESC;
4.2.2 预防死锁的策略
-- 策略1: 按固定顺序访问资源
CREATE OR REPLACE PROCEDURE safe_update_procedure(
p_id1 NUMBER,
p_id2 NUMBER,
p_value1 VARCHAR2,
p_value2 VARCHAR2
) AS
v_first_id NUMBER;
v_second_id NUMBER;
v_first_value VARCHAR2(50);
v_second_value VARCHAR2(50);
BEGIN
-- 确定访问顺序(按ID升序)
IF p_id1 < p_id2 THEN
v_first_id := p_id1;
v_second_id := p_id2;
v_first_value := p_value1;
v_second_value := p_value2;
ELSE
v_first_id := p_id2;
v_second_id := p_id1;
v_first_value := p_value2;
v_second_value := p_value1;
END IF;
-- 按固定顺序更新
UPDATE deadlock_demo1 SET value = v_first_value WHERE id = v_first_id;
UPDATE deadlock_demo2 SET value = v_second_value WHERE id = v_second_id;
COMMIT;
END;
/
-- 策略2: 使用超时机制
CREATE OR REPLACE PROCEDURE timeout_update_procedure AS
BEGIN
-- 设置语句超时
EXECUTE IMMEDIATE 'ALTER SESSION SET ddl_lock_timeout = 10';
UPDATE deadlock_demo1 SET value = 'Safe Update' WHERE id = 1;
UPDATE deadlock_demo2 SET value = 'Safe Update' WHERE id = 1;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
IF SQLCODE = -30006 THEN
DBMS_OUTPUT.PUT_LINE('操作超时,避免了潜在的死锁');
ELSE
RAISE;
END IF;
END;
/
5. 锁监控与管理
5.1 锁信息查询
5.1.1 当前锁状态查询
-- 查看当前所有锁信息
SELECT
s.sid,
s.serial#,
s.username,
s.status,
s.machine,
s.program,
l.type AS lock_type,
DECODE(l.type,
'TM', 'Table Lock',
'TX', 'Transaction Lock',
'UL', 'User Lock',
l.type) AS lock_description,
DECODE(l.lmode,
0, 'None',
1, 'Null',
2, 'Row Share',
3, 'Row Exclusive',
4, 'Share',
5, 'Share Row Exclusive',
6, 'Exclusive',
l.lmode) AS lock_mode,
o.owner,
o.object_name,
o.object_type
FROM v$lock l
JOIN v$session s ON l.sid = s.sid
LEFT JOIN dba_objects o ON l.id1 = o.object_id
WHERE s.username IS NOT NULL
ORDER BY s.sid, l.type;
5.1.2 锁等待情况分析
-- 创建锁等待分析视图
CREATE OR REPLACE VIEW lock_wait_analysis AS
SELECT
waiter.sid AS waiting_session,
waiter.username AS waiting_user,
waiter.status AS waiting_status,
waiter.machine AS waiting_machine,
waiter.program AS waiting_program,
holder.sid AS holding_session,
holder.username AS holding_user,
holder.status AS holding_status,
holder.machine AS holding_machine,
holder.program AS holding_program,
l1.type AS lock_type,
DECODE(l1.request,
0, 'None',
1, 'Null',
2, 'Row Share',
3, 'Row Exclusive',
4, 'Share',
5, 'Share Row Exclusive',
6, 'Exclusive',
l1.request) AS requested_mode,
DECODE(l2.lmode,
0, 'None',
1, 'Null',
2, 'Row Share',
3, 'Row Exclusive',
4, 'Share',
5, 'Share Row Exclusive',
6, 'Exclusive',
l2.lmode) AS held_mode,
o.owner,
o.object_name,
o.object_type
FROM v$lock l1
JOIN v$lock l2 ON l1.id1 = l2.id1 AND l1.id2 = l2.id2
JOIN v$session waiter ON l1.sid = waiter.sid
JOIN v$session holder ON l2.sid = holder.sid
LEFT JOIN dba_objects o ON l1.id1 = o.object_id
WHERE l1.block = 0
AND l2.block = 1
AND l1.type = l2.type;
-- 查询当前锁等待情况
SELECT * FROM lock_wait_analysis;
5.2 锁等待诊断
5.2.1 创建锁监控工具
-- 创建锁监控存储过程
CREATE OR REPLACE PROCEDURE monitor_locks(
p_show_details BOOLEAN DEFAULT TRUE
) AS
v_count NUMBER := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE('=== Oracle锁监控报告 ===');
DBMS_OUTPUT.PUT_LINE('时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
DBMS_OUTPUT.PUT_LINE('');
-- 统计锁数量
SELECT COUNT(*) INTO v_count
FROM v$lock l
JOIN v$session s ON l.sid = s.sid
WHERE s.username IS NOT NULL;
DBMS_OUTPUT.PUT_LINE('当前活跃锁数量: ' || v_count);
-- 检查锁等待
SELECT COUNT(*) INTO v_count FROM lock_wait_analysis;
IF v_count > 0 THEN
DBMS_OUTPUT.PUT_LINE('*** 警告: 发现 ' || v_count || ' 个锁等待情况 ***');
IF p_show_details THEN
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('锁等待详情:');
DBMS_OUTPUT.PUT_LINE('----------------------------------------');
FOR rec IN (SELECT * FROM lock_wait_analysis) LOOP
DBMS_OUTPUT.PUT_LINE('等待者: SID=' || rec.waiting_session ||
', 用户=' || rec.waiting_user ||
', 机器=' || rec.waiting_machine);
DBMS_OUTPUT.PUT_LINE('持有者: SID=' || rec.holding_session ||
', 用户=' || rec.holding_user ||
', 机器=' || rec.holding_machine);
DBMS_OUTPUT.PUT_LINE('锁类型: ' || rec.lock_type ||
', 对象: ' || rec.owner || '.' || rec.object_name);
DBMS_OUTPUT.PUT_LINE('请求模式: ' || rec.requested_mode ||
', 持有模式: ' || rec.held_mode);
DBMS_OUTPUT.PUT_LINE('----------------------------------------');
END LOOP;
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE('没有发现锁等待情况');
END IF;
-- 显示长时间运行的事务
SELECT COUNT(*) INTO v_count
FROM v$session s
JOIN v$transaction t ON s.saddr = t.ses_addr
WHERE (SYSDATE - t.start_date) * 24 * 60 > 30; -- 超过30分钟
IF v_count > 0 THEN
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('*** 警告: 发现 ' || v_count || ' 个长时间运行的事务 ***');
END IF;
END;
/
-- 执行锁监控
EXEC monitor_locks(TRUE);
5.2.2 自动化锁监控
-- 创建锁监控表
CREATE TABLE lock_monitor_log (
log_id NUMBER PRIMARY KEY,
log_time DATE DEFAULT SYSDATE,
lock_count NUMBER,
wait_count NUMBER,
long_tx_count NUMBER,
details CLOB
);
CREATE SEQUENCE lock_monitor_seq START WITH 1 INCREMENT BY 1;
-- 创建自动监控过程
CREATE OR REPLACE PROCEDURE auto_lock_monitor AS
v_lock_count NUMBER;
v_wait_count NUMBER;
v_long_tx_count NUMBER;
v_details CLOB;
BEGIN
-- 统计锁信息
SELECT COUNT(*) INTO v_lock_count
FROM v$lock l JOIN v$session s ON l.sid = s.sid
WHERE s.username IS NOT NULL;
SELECT COUNT(*) INTO v_wait_count FROM lock_wait_analysis;
SELECT COUNT(*) INTO v_long_tx_count
FROM v$session s JOIN v$transaction t ON s.saddr = t.ses_addr
WHERE (SYSDATE - t.start_date) * 24 * 60 > 30;
-- 如果有异常情况,记录详细信息
IF v_wait_count > 0 OR v_long_tx_count > 0 THEN
v_details := 'Lock waits detected: ' || v_wait_count || CHR(10) ||
'Long transactions: ' || v_long_tx_count;
END IF;
-- 记录监控日志
INSERT INTO lock_monitor_log (
log_id, lock_count, wait_count, long_tx_count, details
) VALUES (
lock_monitor_seq.NEXTVAL, v_lock_count, v_wait_count, v_long_tx_count, v_details
);
COMMIT;
-- 如果有严重问题,发送告警
IF v_wait_count > 5 THEN
DBMS_OUTPUT.PUT_LINE('ALERT: High number of lock waits detected!');
END IF;
END;
/
-- 设置定时任务(需要DBMS_SCHEDULER权限)
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'LOCK_MONITOR_JOB',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN auto_lock_monitor; END;',
start_date => SYSDATE,
repeat_interval => 'FREQ=MINUTELY; INTERVAL=5', -- 每5分钟执行一次
enabled => TRUE
);
END;
/
6. 锁优化策略
6.1 减少锁争用
6.1.1 优化事务设计
-- 不好的做法:长事务
CREATE OR REPLACE PROCEDURE bad_batch_update AS
BEGIN
-- 开始一个长事务
FOR rec IN (SELECT * FROM large_table) LOOP
-- 复杂的业务逻辑
UPDATE another_table SET status = 'PROCESSED'
WHERE id = rec.id;
-- 更多的处理...
DBMS_LOCK.SLEEP(1); -- 模拟耗时操作
END LOOP;
COMMIT; -- 长时间持有锁
END;
/
-- 好的做法:短事务批处理
CREATE OR REPLACE PROCEDURE good_batch_update AS
CURSOR data_cursor IS SELECT * FROM large_table;
TYPE data_array IS TABLE OF data_cursor%ROWTYPE;
v_data data_array;
v_batch_size CONSTANT NUMBER := 1000;
BEGIN
OPEN data_cursor;
LOOP
FETCH data_cursor BULK COLLECT INTO v_data LIMIT v_batch_size;
-- 处理一批数据
FORALL i IN 1..v_data.COUNT
UPDATE another_table
SET status = 'PROCESSED'
WHERE id = v_data(i).id;
COMMIT; -- 及时提交,释放锁
EXIT WHEN data_cursor%NOTFOUND;
END LOOP;
CLOSE data_cursor;
END;
/
6.1.2 使用适当的隔离级别
-- 设置事务隔离级别
-- READ COMMITTED(默认)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- SERIALIZABLE(更高的一致性,但可能增加锁争用)
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- READ ONLY(只读事务,不获取写锁)
SET TRANSACTION READ ONLY;
-- 使用只读事务进行报表查询
BEGIN
SET TRANSACTION READ ONLY;
-- 执行复杂的报表查询
SELECT d.department_name,
COUNT(e.employee_id) AS emp_count,
AVG(e.salary) AS avg_salary
FROM departments d
LEFT JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_name
ORDER BY avg_salary DESC;
COMMIT;
END;
/
6.2 锁性能调优
6.2.1 索引优化减少锁争用
-- 创建适当的索引减少锁扫描范围
CREATE INDEX idx_emp_dept_status ON employees(department_id, status);
-- 使用索引友好的查询
UPDATE employees
SET status = 'INACTIVE'
WHERE department_id = 10 AND status = 'ACTIVE';
-- 避免全表扫描的锁定
-- 不好的做法
UPDATE employees SET last_login = SYSDATE; -- 锁定所有行
-- 好的做法
UPDATE employees
SET last_login = SYSDATE
WHERE employee_id IN (
SELECT employee_id FROM recent_logins
); -- 只锁定必要的行
6.2.2 分区表减少锁争用
-- 创建分区表减少锁争用
CREATE TABLE partitioned_transactions (
transaction_id NUMBER,
transaction_date DATE,
amount NUMBER,
status VARCHAR2(20)
)
PARTITION BY RANGE (transaction_date) (
PARTITION p_2023 VALUES LESS THAN (DATE '2024-01-01'),
PARTITION p_2024 VALUES LESS THAN (DATE '2025-01-01'),
PARTITION p_2025 VALUES LESS THAN (DATE '2026-01-01')
);
-- 并行处理不同分区,减少锁争用
-- Session 1: 处理2023年数据
UPDATE partitioned_transactions
SET status = 'PROCESSED'
WHERE transaction_date >= DATE '2023-01-01'
AND transaction_date < DATE '2024-01-01'
AND status = 'PENDING';
-- Session 2: 同时处理2024年数据(不同分区,无锁争用)
UPDATE partitioned_transactions
SET status = 'PROCESSED'
WHERE transaction_date >= DATE '2024-01-01'
AND transaction_date < DATE '2025-01-01'
AND status = 'PENDING';
7. 特殊锁类型
7.1 用户定义锁
7.1.1 DBMS_LOCK包的使用
-- 使用DBMS_LOCK创建用户定义锁
CREATE OR REPLACE PROCEDURE demo_user_lock AS
v_lockhandle VARCHAR2(128);
v_lock_result NUMBER;
BEGIN
-- 分配锁句柄
DBMS_LOCK.ALLOCATE_UNIQUE('MY_CUSTOM_LOCK', v_lockhandle);
DBMS_OUTPUT.PUT_LINE('尝试获取用户锁...');
-- 请求锁(排他模式,等待10秒)
v_lock_result := DBMS_LOCK.REQUEST(
lockhandle => v_lockhandle,
lockmode => DBMS_LOCK.X_MODE, -- 排他模式
timeout => 10, -- 10秒超时
release_on_commit => TRUE -- 提交时释放
);
CASE v_lock_result
WHEN 0 THEN
DBMS_OUTPUT.PUT_LINE('成功获得锁');
-- 执行需要同步的操作
DBMS_OUTPUT.PUT_LINE('执行关键业务逻辑...');
DBMS_LOCK.SLEEP(5); -- 模拟处理时间
-- 手动释放锁
v_lock_result := DBMS_LOCK.RELEASE(v_lockhandle);
DBMS_OUTPUT.PUT_LINE('锁已释放');
WHEN 1 THEN
DBMS_OUTPUT.PUT_LINE('获取锁超时');
WHEN 2 THEN
DBMS_OUTPUT.PUT_LINE('死锁检测到');
WHEN 3 THEN
DBMS_OUTPUT.PUT_LINE('参数错误');
WHEN 4 THEN
DBMS_OUTPUT.PUT_LINE('锁已被持有');
ELSE
DBMS_OUTPUT.PUT_LINE('未知错误: ' || v_lock_result);
END CASE;
END;
/
-- 测试用户锁
EXEC demo_user_lock;
7.1.2 应用级锁控制
-- 创建应用级锁管理表
CREATE TABLE app_locks (
lock_name VARCHAR2(100) PRIMARY KEY,
session_id NUMBER,
username VARCHAR2(30),
acquired_time DATE,
purpose VARCHAR2(200)
);
-- 创建应用锁管理包
CREATE OR REPLACE PACKAGE app_lock_mgr AS
FUNCTION acquire_lock(
p_lock_name VARCHAR2,
p_purpose VARCHAR2 DEFAULT NULL,
p_timeout NUMBER DEFAULT 30
) RETURN BOOLEAN;
FUNCTION release_lock(p_lock_name VARCHAR2) RETURN BOOLEAN;
PROCEDURE show_locks;
END app_lock_mgr;
/
CREATE OR REPLACE PACKAGE BODY app_lock_mgr AS
FUNCTION acquire_lock(
p_lock_name VARCHAR2,
p_purpose VARCHAR2 DEFAULT NULL,
p_timeout NUMBER DEFAULT 30
) RETURN BOOLEAN IS
v_count NUMBER;
v_session_id NUMBER;
v_start_time DATE := SYSDATE;
BEGIN
SELECT SYS_CONTEXT('USERENV', 'SID') INTO v_session_id FROM DUAL;
LOOP
-- 尝试获取锁
BEGIN
INSERT INTO app_locks (
lock_name, session_id, username, acquired_time, purpose
) VALUES (
p_lock_name, v_session_id, USER, SYSDATE, p_purpose
);
COMMIT;
RETURN TRUE; -- 成功获取锁
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
-- 锁已被其他会话持有
IF (SYSDATE - v_start_time) * 24 * 60 * 60 > p_timeout THEN
RETURN FALSE; -- 超时
END IF;
DBMS_LOCK.SLEEP(1); -- 等待1秒后重试
END;
END LOOP;
END acquire_lock;
FUNCTION release_lock(p_lock_name VARCHAR2) RETURN BOOLEAN IS
v_session_id NUMBER;
v_count NUMBER;
BEGIN
SELECT SYS_CONTEXT('USERENV', 'SID') INTO v_session_id FROM DUAL;
DELETE FROM app_locks
WHERE lock_name = p_lock_name
AND session_id = v_session_id;
v_count := SQL%ROWCOUNT;
COMMIT;
RETURN v_count > 0;
END release_lock;
PROCEDURE show_locks IS
BEGIN
DBMS_OUTPUT.PUT_LINE('=== 应用级锁状态 ===');
FOR rec IN (
SELECT lock_name, session_id, username,
TO_CHAR(acquired_time, 'YYYY-MM-DD HH24:MI:SS') AS acquired,
purpose
FROM app_locks
ORDER BY acquired_time
) LOOP
DBMS_OUTPUT.PUT_LINE('锁名: ' || rec.lock_name ||
', 会话: ' || rec.session_id ||
', 用户: ' || rec.username ||
', 获取时间: ' || rec.acquired);
IF rec.purpose IS NOT NULL THEN
DBMS_OUTPUT.PUT_LINE(' 目的: ' || rec.purpose);
END IF;
END LOOP;
END show_locks;
END app_lock_mgr;
/
-- 使用应用级锁
DECLARE
v_result BOOLEAN;
BEGIN
-- 尝试获取锁
v_result := app_lock_mgr.acquire_lock('BATCH_PROCESS', '每日批处理作业');
IF v_result THEN
DBMS_OUTPUT.PUT_LINE('成功获取应用锁');
-- 执行批处理逻辑
DBMS_OUTPUT.PUT_LINE('执行批处理...');
DBMS_LOCK.SLEEP(3);
-- 释放锁
v_result := app_lock_mgr.release_lock('BATCH_PROCESS');
DBMS_OUTPUT.PUT_LINE('锁已释放');
ELSE
DBMS_OUTPUT.PUT_LINE('无法获取应用锁');
END IF;
END;
/
-- 查看当前锁状态
EXEC app_lock_mgr.show_locks;
8. 锁的最佳实践
8.1 设计原则
8.1.1 最小化锁持有时间
-- 原则1: 尽快提交事务
CREATE OR REPLACE PROCEDURE process_orders AS
BEGIN
FOR order_rec IN (
SELECT order_id, customer_id, total_amount
FROM orders
WHERE status = 'PENDING'
) LOOP
-- 处理单个订单
UPDATE orders
SET status = 'PROCESSING',
last_updated = SYSDATE
WHERE order_id = order_rec.order_id;
-- 立即提交,不要等待整个循环结束
COMMIT;
-- 非数据库操作(如调用外部服务)放在事务外
-- call_external_service(order_rec.order_id);
END LOOP;
END;
/
-- 原则2: 避免用户交互期间持有锁
CREATE OR REPLACE PROCEDURE safe_interactive_update(
p_emp_id NUMBER,
p_new_salary NUMBER
) AS
v_current_salary NUMBER;
v_emp_name VARCHAR2(100);
BEGIN
-- 首先获取数据(不锁定)
SELECT salary, first_name || ' ' || last_name
INTO v_current_salary, v_emp_name
FROM employees
WHERE employee_id = p_emp_id;
-- 显示信息给用户确认(这里不持有锁)
DBMS_OUTPUT.PUT_LINE('员工: ' || v_emp_name);
DBMS_OUTPUT.PUT_LINE('当前工资: ' || v_current_salary);
DBMS_OUTPUT.PUT_LINE('新工资: ' || p_new_salary);
-- 只在实际更新时才获取锁
UPDATE employees
SET salary = p_new_salary,
last_updated = SYSDATE
WHERE employee_id = p_emp_id;
COMMIT; -- 立即提交
END;
/
8.1.2 合理的锁粒度选择
-- 选择合适的锁粒度
-- 场景1: 批量更新少量记录 - 使用行级锁
UPDATE employees
SET salary = salary * 1.1
WHERE department_id = 10; -- 只影响几行
-- 场景2: 大量更新操作 - 考虑表级锁
LOCK TABLE temp_import_data IN EXCLUSIVE MODE;
-- 执行大量数据导入
INSERT /*+ APPEND */ INTO main_table SELECT * FROM temp_import_data;
COMMIT;
-- 场景3: 复杂报表查询 - 使用读锁或快照
SET TRANSACTION READ ONLY;
-- 执行复杂查询,确保数据一致性
SELECT /* 复杂报表查询 */ * FROM multiple_tables;
COMMIT;
8.2 性能优化技巧
8.2.1 减少锁升级
-- 避免锁升级的技巧
-- 1. 使用绑定变量
CREATE OR REPLACE PROCEDURE update_salaries_efficient(
p_dept_id NUMBER,
p_increase_pct NUMBER
) AS
BEGIN
-- 使用绑定变量,减少SQL解析开销
UPDATE employees
SET salary = salary * (1 + p_increase_pct / 100)
WHERE department_id = p_dept_id;
COMMIT;
END;
/
-- 2. 批量操作
CREATE OR REPLACE PROCEDURE bulk_salary_update AS
TYPE emp_id_array IS TABLE OF employees.employee_id%TYPE;
TYPE salary_array IS TABLE OF employees.salary%TYPE;
v_emp_ids emp_id_array;
v_new_salaries salary_array;
BEGIN
-- 批量获取数据
SELECT employee_id, salary * 1.1
BULK COLLECT INTO v_emp_ids, v_new_salaries
FROM employees
WHERE department_id = 20;
-- 批量更新
FORALL i IN 1..v_emp_ids.COUNT
UPDATE employees
SET salary = v_new_salaries(i)
WHERE employee_id = v_emp_ids(i);
COMMIT;
END;
/
8.2.2 并发控制优化
-- 乐观锁实现
ALTER TABLE employees ADD version_number NUMBER DEFAULT 1;
CREATE OR REPLACE PROCEDURE optimistic_update_salary(
p_emp_id NUMBER,
p_new_salary NUMBER,
p_version NUMBER
) AS
v_rows_updated NUMBER;
BEGIN
UPDATE employees
SET salary = p_new_salary,
version_number = version_number + 1,
last_updated = SYSDATE
WHERE employee_id = p_emp_id
AND version_number = p_version;
v_rows_updated := SQL%ROWCOUNT;
IF v_rows_updated = 0 THEN
RAISE_APPLICATION_ERROR(-20001,
'数据已被其他用户修改,请刷新后重试');
END IF;
COMMIT;
END;
/
-- 使用乐观锁
DECLARE
v_emp_id NUMBER := 100;
v_current_salary NUMBER;
v_current_version NUMBER;
v_new_salary NUMBER := 8000;
BEGIN
-- 获取当前数据和版本号
SELECT salary, version_number
INTO v_current_salary, v_current_version
FROM employees
WHERE employee_id = v_emp_id;
-- 执行业务逻辑...
-- 尝试更新
optimistic_update_salary(v_emp_id, v_new_salary, v_current_version);
DBMS_OUTPUT.PUT_LINE('工资更新成功');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('更新失败: ' || SQLERRM);
END;
/
8.3 监控和维护
8.3.1 定期锁健康检查
-- 创建锁健康检查报告
CREATE OR REPLACE PROCEDURE lock_health_report AS
v_total_locks NUMBER;
v_waiting_locks NUMBER;
v_long_tx NUMBER;
v_deadlocks NUMBER;
BEGIN
DBMS_OUTPUT.PUT_LINE('=== Oracle锁健康报告 ===');
DBMS_OUTPUT.PUT_LINE('报告时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
DBMS_OUTPUT.PUT_LINE('');
-- 总锁数量
SELECT COUNT(*) INTO v_total_locks
FROM v$lock l JOIN v$session s ON l.sid = s.sid
WHERE s.username IS NOT NULL;
DBMS_OUTPUT.PUT_LINE('1. 当前活跃锁数量: ' || v_total_locks);
-- 等待锁数量
SELECT COUNT(*) INTO v_waiting_locks FROM lock_wait_analysis;
DBMS_OUTPUT.PUT_LINE('2. 锁等待情况: ' || v_waiting_locks);
-- 长事务数量
SELECT COUNT(*) INTO v_long_tx
FROM v$session s JOIN v$transaction t ON s.saddr = t.ses_addr
WHERE (SYSDATE - t.start_date) * 24 * 60 > 15; -- 超过15分钟
DBMS_OUTPUT.PUT_LINE('3. 长事务数量: ' || v_long_tx || ' (>15分钟)');
-- 死锁统计
SELECT value INTO v_deadlocks
FROM v$sysstat
WHERE name = 'enqueue deadlocks';
DBMS_OUTPUT.PUT_LINE('4. 总死锁次数: ' || v_deadlocks);
-- 健康评估
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('=== 健康评估 ===');
IF v_waiting_locks = 0 AND v_long_tx = 0 THEN
DBMS_OUTPUT.PUT_LINE('状态: 良好 ✓');
ELSIF v_waiting_locks < 5 AND v_long_tx < 3 THEN
DBMS_OUTPUT.PUT_LINE('状态: 正常 ⚠');
ELSE
DBMS_OUTPUT.PUT_LINE('状态: 需要注意 ✗');
DBMS_OUTPUT.PUT_LINE('建议: 检查长事务和锁等待情况');
END IF;
END;
/
-- 执行健康检查
EXEC lock_health_report;
8.3.2 清理和维护脚本
-- 清理孤立锁的脚本
CREATE OR REPLACE PROCEDURE cleanup_orphaned_locks AS
v_count NUMBER := 0;
BEGIN
-- 查找并清理应用级锁表中的孤立锁
FOR rec IN (
SELECT al.lock_name, al.session_id
FROM app_locks al
LEFT JOIN v$session s ON al.session_id = s.sid
WHERE s.sid IS NULL -- 会话已不存在
) LOOP
DELETE FROM app_locks
WHERE lock_name = rec.lock_name
AND session_id = rec.session_id;
v_count := v_count + 1;
DBMS_OUTPUT.PUT_LINE('清理孤立锁: ' || rec.lock_name);
END LOOP;
COMMIT;
DBMS_OUTPUT.PUT_LINE('清理完成,共清理 ' || v_count || ' 个孤立锁');
END;
/
-- 定期执行清理
EXEC cleanup_orphaned_locks;
通过这个全面的Oracle锁教程,我们深入探讨了Oracle数据库中锁的各个方面,从基础概念到高级应用,从监控诊断到性能优化。理解和正确使用锁机制对于构建高性能、高并发的数据库应用至关重要。在实际应用中,应该根据具体的业务需求和并发模式来选择合适的锁策略,并建立完善的监控和维护机制。
结语
感谢您的阅读!期待您的一键三连!欢迎指正!