【Oracle】锁
个人主页:Guiat
归属专栏:Oracle
文章目录
- 1. 锁基础概述
- 1.1 锁的概念与作用
- 1.2 锁的工作原理
- 1.3 Oracle锁的分类
- 2. 行级锁 (Row-Level Locks)
- 2.1 行级锁的基本概念
- 2.1.1 TX锁(事务锁)
- 2.1.2 行级锁的工作机制
- 2.2 行级锁的类型
- 2.2.1 共享行级锁 (SELECT FOR UPDATE)
- 2.2.2 排他行级锁 (UPDATE/DELETE)
- 2.3 行级锁的高级特性
- 2.3.1 SELECT FOR UPDATE的选项
- 2.3.2 选择性列锁定
- 3. 表级锁 (Table-Level Locks)
- 3.1 表级锁的类型
- 3.1.1 表锁模式
- 3.1.2 锁兼容性矩阵
- 3.2 DDL锁
- 3.2.1 DDL锁的影响
- 3.2.2 在线DDL操作
- 4. 死锁检测与处理
- 4.1 死锁的产生
- 4.1.1 典型死锁场景
- 4.1.2 死锁检测机制
- 4.2 死锁的解决
- 4.2.1 Oracle自动死锁检测
- 4.2.2 预防死锁的策略
- 5. 锁监控与管理
- 5.1 锁信息查询
- 5.1.1 当前锁状态查询
- 5.1.2 锁等待情况分析
- 5.2 锁等待诊断
- 5.2.1 创建锁监控工具
- 5.2.2 自动化锁监控
- 6. 锁优化策略
- 6.1 减少锁争用
- 6.1.1 优化事务设计
- 6.1.2 使用适当的隔离级别
- 6.2 锁性能调优
- 6.2.1 索引优化减少锁争用
- 6.2.2 分区表减少锁争用
- 7. 特殊锁类型
- 7.1 用户定义锁
- 7.1.1 DBMS_LOCK包的使用
- 7.1.2 应用级锁控制
- 8. 锁的最佳实践
- 8.1 设计原则
- 8.1.1 最小化锁持有时间
- 8.1.2 合理的锁粒度选择
- 8.2 性能优化技巧
- 8.2.1 减少锁升级
- 8.2.2 并发控制优化
- 8.3 监控和维护
- 8.3.1 定期锁健康检查
- 8.3.2 清理和维护脚本
正文
1. 锁基础概述
锁是Oracle数据库中用于控制并发访问数据的重要机制,确保数据的一致性和完整性。Oracle使用多种类型的锁来管理不同级别的资源访问。
1.1 锁的概念与作用
1.2 锁的工作原理
1.3 Oracle锁的分类
2. 行级锁 (Row-Level Locks)
行级锁是Oracle中最常用的锁类型,提供最高的并发性。
2.1 行级锁的基本概念
2.1.1 TX锁(事务锁)
-- 演示行级锁的基本操作
-- Session 1
BEGINUPDATE 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获取共享行级锁
DECLARECURSOR emp_cursor ISSELECT employee_id, first_name, last_name, salaryFROM employeesWHERE department_id = 20FOR UPDATE;emp_rec emp_cursor%ROWTYPE;BEGINDBMS_OUTPUT.PUT_LINE('=== 锁定部门20的所有员工 ===');OPEN emp_cursor;LOOPFETCH 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, salaryINTO :emp_id, :emp_salaryFROM employeesWHERE employee_id = 100FOR UPDATE NOWAIT;EXCEPTIONWHEN OTHERS THENIF SQLCODE = -54 THEN -- ORA-00054: resource busyDBMS_OUTPUT.PUT_LINE('资源正忙,无法获得锁');ELSEDBMS_OUTPUT.PUT_LINE('其他错误: ' || SQLERRM);END IF;
END;
/-- WAIT选项:指定等待时间
DECLAREv_emp_id NUMBER;v_salary NUMBER;
BEGINSELECT employee_id, salaryINTO v_emp_id, v_salaryFROM employeesWHERE employee_id = 100FOR UPDATE WAIT 5; -- 等待5秒DBMS_OUTPUT.PUT_LINE('成功获得锁,员工ID: ' || v_emp_id);EXCEPTIONWHEN OTHERS THENIF SQLCODE = -30006 THEN -- ORA-30006: resource busy; acquire with WAIT timeout expiredDBMS_OUTPUT.PUT_LINE('等待超时,无法获得锁');ELSEDBMS_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:
BEGINUPDATE 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: (同时在另一个会话中执行)
BEGINUPDATE 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_nameFROM v$lock l1JOIN v$lock l2 ON l1.id1 = l2.id1 AND l1.id2 = l2.id2JOIN v$session waiting ON l1.sid = waiting.sidJOIN v$session holding ON l2.sid = holding.sidJOIN dba_objects o ON l1.id1 = o.object_idWHERE l1.block = 0 AND l2.block = 1AND l1.type = l2.type) LOOPDBMS_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
) ASv_first_id NUMBER;v_second_id NUMBER;v_first_value VARCHAR2(50);v_second_value VARCHAR2(50);
BEGIN-- 确定访问顺序(按ID升序)IF p_id1 < p_id2 THENv_first_id := p_id1;v_second_id := p_id2;v_first_value := p_value1;v_second_value := p_value2;ELSEv_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;EXCEPTIONWHEN OTHERS THENROLLBACK;IF SQLCODE = -30006 THENDBMS_OUTPUT.PUT_LINE('操作超时,避免了潜在的死锁');ELSERAISE;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 = 1AND 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
) ASv_count NUMBER := 0;
BEGINDBMS_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_countFROM v$lock lJOIN v$session s ON l.sid = s.sidWHERE s.username IS NOT NULL;DBMS_OUTPUT.PUT_LINE('当前活跃锁数量: ' || v_count);-- 检查锁等待SELECT COUNT(*) INTO v_count FROM lock_wait_analysis;IF v_count > 0 THENDBMS_OUTPUT.PUT_LINE('*** 警告: 发现 ' || v_count || ' 个锁等待情况 ***');IF p_show_details THENDBMS_OUTPUT.PUT_LINE('');DBMS_OUTPUT.PUT_LINE('锁等待详情:');DBMS_OUTPUT.PUT_LINE('----------------------------------------');FOR rec IN (SELECT * FROM lock_wait_analysis) LOOPDBMS_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;ELSEDBMS_OUTPUT.PUT_LINE('没有发现锁等待情况');END IF;-- 显示长时间运行的事务SELECT COUNT(*) INTO v_countFROM v$session sJOIN v$transaction t ON s.saddr = t.ses_addrWHERE (SYSDATE - t.start_date) * 24 * 60 > 30; -- 超过30分钟IF v_count > 0 THENDBMS_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 ASv_lock_count NUMBER;v_wait_count NUMBER;v_long_tx_count NUMBER;v_details CLOB;
BEGIN-- 统计锁信息SELECT COUNT(*) INTO v_lock_countFROM v$lock l JOIN v$session s ON l.sid = s.sidWHERE s.username IS NOT NULL;SELECT COUNT(*) INTO v_wait_count FROM lock_wait_analysis;SELECT COUNT(*) INTO v_long_tx_countFROM v$session s JOIN v$transaction t ON s.saddr = t.ses_addrWHERE (SYSDATE - t.start_date) * 24 * 60 > 30;-- 如果有异常情况,记录详细信息IF v_wait_count > 0 OR v_long_tx_count > 0 THENv_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 THENDBMS_OUTPUT.PUT_LINE('ALERT: High number of lock waits detected!');END IF;END;
/-- 设置定时任务(需要DBMS_SCHEDULER权限)
BEGINDBMS_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 ASCURSOR 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;
BEGINOPEN data_cursor;LOOPFETCH data_cursor BULK COLLECT INTO v_data LIMIT v_batch_size;-- 处理一批数据FORALL i IN 1..v_data.COUNTUPDATE 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;-- 使用只读事务进行报表查询
BEGINSET TRANSACTION READ ONLY;-- 执行复杂的报表查询SELECT d.department_name,COUNT(e.employee_id) AS emp_count,AVG(e.salary) AS avg_salaryFROM departments dLEFT JOIN employees e ON d.department_id = e.department_idGROUP BY d.department_nameORDER 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 ASv_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_resultWHEN 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 THENDBMS_OUTPUT.PUT_LINE('获取锁超时');WHEN 2 THENDBMS_OUTPUT.PUT_LINE('死锁检测到');WHEN 3 THENDBMS_OUTPUT.PUT_LINE('参数错误');WHEN 4 THENDBMS_OUTPUT.PUT_LINE('锁已被持有');ELSEDBMS_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 ASFUNCTION 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 ASFUNCTION acquire_lock(p_lock_name VARCHAR2,p_purpose VARCHAR2 DEFAULT NULL,p_timeout NUMBER DEFAULT 30) RETURN BOOLEAN ISv_count NUMBER;v_session_id NUMBER;v_start_time DATE := SYSDATE;BEGINSELECT SYS_CONTEXT('USERENV', 'SID') INTO v_session_id FROM DUAL;LOOP-- 尝试获取锁BEGININSERT 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; -- 成功获取锁EXCEPTIONWHEN DUP_VAL_ON_INDEX THEN-- 锁已被其他会话持有IF (SYSDATE - v_start_time) * 24 * 60 * 60 > p_timeout THENRETURN FALSE; -- 超时END IF;DBMS_LOCK.SLEEP(1); -- 等待1秒后重试END;END LOOP;END acquire_lock;FUNCTION release_lock(p_lock_name VARCHAR2) RETURN BOOLEAN ISv_session_id NUMBER;v_count NUMBER;BEGINSELECT 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 ISBEGINDBMS_OUTPUT.PUT_LINE('=== 应用级锁状态 ===');FOR rec IN (SELECT lock_name, session_id, username, TO_CHAR(acquired_time, 'YYYY-MM-DD HH24:MI:SS') AS acquired,purposeFROM app_locksORDER BY acquired_time) LOOPDBMS_OUTPUT.PUT_LINE('锁名: ' || rec.lock_name ||', 会话: ' || rec.session_id ||', 用户: ' || rec.username ||', 获取时间: ' || rec.acquired);IF rec.purpose IS NOT NULL THENDBMS_OUTPUT.PUT_LINE(' 目的: ' || rec.purpose);END IF;END LOOP;END show_locks;END app_lock_mgr;
/-- 使用应用级锁
DECLAREv_result BOOLEAN;
BEGIN-- 尝试获取锁v_result := app_lock_mgr.acquire_lock('BATCH_PROCESS', '每日批处理作业');IF v_result THENDBMS_OUTPUT.PUT_LINE('成功获取应用锁');-- 执行批处理逻辑DBMS_OUTPUT.PUT_LINE('执行批处理...');DBMS_LOCK.SLEEP(3);-- 释放锁v_result := app_lock_mgr.release_lock('BATCH_PROCESS');DBMS_OUTPUT.PUT_LINE('锁已释放');ELSEDBMS_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
BEGINFOR 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
) ASv_current_salary NUMBER;v_emp_name VARCHAR2(100);
BEGIN-- 首先获取数据(不锁定)SELECT salary, first_name || ' ' || last_nameINTO v_current_salary, v_emp_nameFROM employeesWHERE 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 = SYSDATEWHERE 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 ASTYPE 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.1BULK COLLECT INTO v_emp_ids, v_new_salariesFROM employeesWHERE department_id = 20;-- 批量更新FORALL i IN 1..v_emp_ids.COUNTUPDATE 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
) ASv_rows_updated NUMBER;
BEGINUPDATE employees SET salary = p_new_salary,version_number = version_number + 1,last_updated = SYSDATEWHERE employee_id = p_emp_id AND version_number = p_version;v_rows_updated := SQL%ROWCOUNT;IF v_rows_updated = 0 THENRAISE_APPLICATION_ERROR(-20001, '数据已被其他用户修改,请刷新后重试');END IF;COMMIT;
END;
/-- 使用乐观锁
DECLAREv_emp_id NUMBER := 100;v_current_salary NUMBER;v_current_version NUMBER;v_new_salary NUMBER := 8000;
BEGIN-- 获取当前数据和版本号SELECT salary, version_numberINTO v_current_salary, v_current_versionFROM employeesWHERE employee_id = v_emp_id;-- 执行业务逻辑...-- 尝试更新optimistic_update_salary(v_emp_id, v_new_salary, v_current_version);DBMS_OUTPUT.PUT_LINE('工资更新成功');EXCEPTIONWHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('更新失败: ' || SQLERRM);
END;
/
8.3 监控和维护
8.3.1 定期锁健康检查
-- 创建锁健康检查报告
CREATE OR REPLACE PROCEDURE lock_health_report ASv_total_locks NUMBER;v_waiting_locks NUMBER;v_long_tx NUMBER;v_deadlocks NUMBER;
BEGINDBMS_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_locksFROM v$lock l JOIN v$session s ON l.sid = s.sidWHERE 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_txFROM v$session s JOIN v$transaction t ON s.saddr = t.ses_addrWHERE (SYSDATE - t.start_date) * 24 * 60 > 15; -- 超过15分钟DBMS_OUTPUT.PUT_LINE('3. 长事务数量: ' || v_long_tx || ' (>15分钟)');-- 死锁统计SELECT value INTO v_deadlocksFROM v$sysstatWHERE 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 THENDBMS_OUTPUT.PUT_LINE('状态: 良好 ✓');ELSIF v_waiting_locks < 5 AND v_long_tx < 3 THENDBMS_OUTPUT.PUT_LINE('状态: 正常 ⚠');ELSEDBMS_OUTPUT.PUT_LINE('状态: 需要注意 ✗');DBMS_OUTPUT.PUT_LINE('建议: 检查长事务和锁等待情况');END IF;END;
/-- 执行健康检查
EXEC lock_health_report;
8.3.2 清理和维护脚本
-- 清理孤立锁的脚本
CREATE OR REPLACE PROCEDURE cleanup_orphaned_locks ASv_count NUMBER := 0;
BEGIN-- 查找并清理应用级锁表中的孤立锁FOR rec IN (SELECT al.lock_name, al.session_idFROM app_locks alLEFT JOIN v$session s ON al.session_id = s.sidWHERE s.sid IS NULL -- 会话已不存在) LOOPDELETE 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数据库中锁的各个方面,从基础概念到高级应用,从监控诊断到性能优化。理解和正确使用锁机制对于构建高性能、高并发的数据库应用至关重要。在实际应用中,应该根据具体的业务需求和并发模式来选择合适的锁策略,并建立完善的监控和维护机制。
结语
感谢您的阅读!期待您的一键三连!欢迎指正!
相关文章:

【Oracle】锁
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 锁基础概述1.1 锁的概念与作用1.2 锁的工作原理1.3 Oracle锁的分类 2. 行级锁 (Row-Level Locks)2.1 行级锁的基本概念2.1.1 TX锁(事务锁)2.1.2 行级锁的工作机制 2.2 行级锁的类型2.…...
共识算法Raft系列(1)——什么是Raft?
Raft 算法是一种分布式一致性算法,由 Diego Ongaro 和 John Ousterhout 在 2014 年提出,旨在解决 Paxos 算法复杂且难以理解的问题。Raft 设计目标是易于理解和实现,同时提供强一致性(CAP 中的 CP 系统),广…...

JS逆向爬虫教程与实战技巧
想要一个关于爬虫JS逆向的详细教程。这是一个很专业的技术需求,最近有个可能是正在学习爬虫技术的开发者或者数据分析师,遇到了需要破解JavaScript加密的反爬机制的问题,想让我出一期实战教程,话不多说,开干。 以下是我…...
Neovim - LSP 底层原理,难点配置(二)
Neovim LSP 的工作原理 基本概念 LSP(Language Server Protocol)可以理解成是一个"语言助手"。每种编程语言都有自己的"语言助手"(比如 TypeScript 的 tsserver),这些助手能告诉你: 哪写错了(语法错误)哪能跳转(方法定义,引用)哪可以补全(自…...

【Redis】Redis 的常见客户端汇总
目录 一、命令行客户端 二、图形界面的客户端 三、Java 客户端 3.1 SpringDataRedis 3.2 Jedis 3.2.1 连接池的配置 3.3 Lettuce 3.3.1 RedisTemplate 工具类实现 3.3.2 自定义序列化器 3.3.3 StringRedisTemplate 3.3.4 集群配置 3.3.4.1 刷新节点集群拓扑动态感应…...

关于akka官方quickstart示例程序(scala)的记录
参考资料 https://doc.akka.io/libraries/akka-core/current/typed/actors.html#first-example 关于scala语法的注意事项 extends App是个语法糖,等同于直接在伴生对象中编写main 方法对象是通过apply方法创建的,也可以通过对象的名称单独创建&#x…...

2025年渗透测试面试题总结-腾讯[实习]玄武实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]玄武实验室-安全工程师 1. 自我介绍 2. CSRF原理 3. Web安全入门时间 4. 学习Web安全的原因 …...

网站首页菜单两种布局vue+elementui顶部和左侧栏导航
顶部菜单实现 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Vue.js Element UI 路由导航</…...
AWS之迁移与传输服务
目录 一、迁移管理与规划类 二、应用程序迁移类 1. MGN的迁移范围(能替代的场景) ✅ 自动包含以下数据: 🔹 适用场景举例: 2. 仍需DMS或DataSync的场景(不可替代) ❌ DMS仍必要的情况: ❌ DataSync仍必要的情况: 3. 技术原理对比 MGN的数据迁移机制: DMS…...

@Builder的用法
Builder 是 Lombok 提供的一个注解,用于简化 Java 中构建对象的方式(Builder 模式)。它可以让你以更加简洁、链式的方式来创建对象,尤其适用于构造参数较多或部分可选的类。...
Unity3D 逻辑代码性能优化策略
前言 在Unity3D中优化逻辑代码性能是提升游戏流畅度的关键。以下是系统性的优化策略和示例: 对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀! 1. 避免高频操作中的开销 缓存组件引用 private …...
【Python Cookbook】文件与 IO(二)
文件与 IO(二) 6.字符串的 I/O 操作7.读写压缩文件8.固定大小记录的文件迭代(⭐⭐) 6.字符串的 I/O 操作 你想使用操作类文件对象的程序来操作文本或二进制字符串。 使用 io.StringIO() 和 io.BytesIO() 类来创建类文件对象操作…...

vue实现点击按钮input保持聚焦状态
主要功能: 点击"停顿"按钮切换对话框显示状态输入框聚焦时保持状态点击对话框外的区域自动关闭 以下是代码版本: <template><div class"input-container"><el-inputv-model"input"style"width: 2…...

[蓝桥杯]取球博弈
取球博弈 题目描述 两个人玩取球的游戏。 一共有 NN 个球,每人轮流取球,每次可取集合 n1,n2,n3n1,n2,n3中的任何一个数目。 如果无法继续取球,则游戏结束。 此时,持有奇数个球的一方获胜。 如果两人都是奇数ÿ…...
Spring Security入门:创建第一个安全REST端点项目
项目初始化与基础配置 创建基础Spring Boot项目 我们首先创建一个名为ssia-ch2-ex1的空项目(该名称与配套源码中的示例项目保持一致)。项目需要添加以下两个核心依赖: org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-secur…...

[Java 基础]数组
什么是数组?想象一下,你需要存储 5 个学生的考试成绩。你可以声明 5 个不同的 int 变量,但这会显得很笨拙。数组提供了一种更简洁、更有组织的方式来存储和管理这些数据。 数组可以看作是相同类型元素的集合,这些元素在内存中是连…...
fastadmin fildList 动态下拉框默认选中
html页面 <td><select class"form-control dtselect" data-rule"required" data-dtselected"<%row.type%>" name"<%name%>[<%index%>][type]">{foreach nametypeList idvo}<option value"{$vo…...
java学习笔记——数组和二维数组
一、一维数组 1. 定义数组 语法: // 动态初始化(指定长度) 数据类型[] 数组名 = new 数据类型[长度]; // 示例: int[] arr1 = new int[5]; // 默认值:0// 静态初始化(直接赋值) 数据类型[] 数组名 = {元素1, 元素2, ...}; // 示例: String[]…...

‘pnpm‘ 不是内部或外部命令,也不是可运行的程序
npm install -g pnpm changed 1 package in 4s 1 package is looking for funding run npm fund for details C:\Users\gang>pnpm pnpm 不是内部或外部命令,也不是可运行的程序 或批处理文件。 原来是安装的全局路径被我改了 npm list -g --depth 0 把上述…...

Android Test2 获取系统android id
Android Test2 获取系统 android id 这篇文章针对一个常用的功能做一个测试。 在项目中,时常会遇到的一个需求就是:一台设备的唯一标识值。然后,在网络请求中将这个识别值传送到后端服务器,用作后端数据查询的条件。Android 设备…...

webpack打包学习
vue开发 现在项目里安装vue: npm install vue vue的文件后缀是.vue webpack不认识vue的话就接着安插件 npm install vue-loader -D 这是.vue文件: <template> <div><h2 class"title">{{title}}</h2><p cla…...

基于Java(Jsp+servelet+Javabean)+MySQL实现图书管理系统
图书管理系统 一、需求分析 1.1 功能描述 1.1.1“读者”功能 1)图书的查询:图书的查询可以通过搜索图书 id、书名、作者名、出版社来实现,显示结果中需要包括书籍信息以及是否被借阅的情况; 2)图书的借阅:借阅图书…...

服务器CPU被WMI Provider Host系统进程占用过高,导致系统偶尔卡顿的排查处理方案
问题现状 最近一个项目遇到一个非常奇葩的问题:正式服务器被一个WMI Provider Host的系统进程占用大量的CPU资源,导致我们的系统偶尔卡顿 任务管理器-详细信息中CPU时间,这个进程也是占用最多的 接口时不时慢很多 但单独访问我们的接口又正…...

JavaSwing之--JMenuBar
Java Swing之–JMenuBar(菜单栏) JMenuBar是 Java Swing 库中的一个组件,用于创建菜单栏,通常位于窗口的顶部。它是菜单系统的容器,用于组织和显示应用程序的菜单结构 菜单栏由菜单构成,菜单由菜单项或子菜单构成,也…...
vue3+elementplus表格表头加图标及文字提示
表头加自定义内容有很多种方法,包括使用el-icon,插槽,CSS 伪元素添加图标还有font-awesome等等。 一、方法一:使用render-header属性 <el-table :data"tableData"><el-table-column prop"name" la…...

【物联网-S7Comm协议】
物联网-S7Comm协议 ■ 调试工具■ S7协议-简介■ S7协议和modbusTCP协议区别■ OSI 层 S7 协议■ S7协议数据结构 (TPKTCOTPS7Comm)■ TPKT(第五层:会话层) 总共占4个字节■ COTP(第六层:表示层…...
NLP中的input_ids是什么?
在自然语言处理(NLP)中,input_ids 是什么 在自然语言处理(NLP)中,input_ids 是将文本转换为模型可处理的数字表示后的结果,是模型输入的核心参数之一。 一、基本概念 文本数字化 原始文本(如 “Hello world!”)无法直接被模型处理,需要通过分词器(Tokenizer) 将其…...
LeetCode Hot100刷题——划分字母区间
763.划分字母区间 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc" 能够被分为 ["abab", "cc"],但类似 ["aba", "bcc"…...
c++ 基于OpenSSL的EVP接口进行SHA3-512和SM3哈希计算
通过OpenSSL的EVP接口进行 SHA3-512 和 SM3 哈希计算 #include <iostream> #include <openssl/evp.h> #include <cstring>using namespace std;void PrintHex(const std::string &hexStr) {for (unsigned char c : hexStr){printf("%02x", c)…...
Vue3实现拖拽改变元素大小
代码实现 整体页面结构通过一个 dragResize-wrapper 包含左右两个区域,左侧区域包含一个可拖拽的边界。以下是关键代码 HTML 部分 <template><div class"dragResize-wrapper"><div class"dragResize-left"><div class&…...