
👋 大家好,欢迎来到我的技术博客!
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕Oracle这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!
文章目录
Oracle 锁信息深度解析:定位阻塞会话、诊断死锁与安全释放锁 🔐🔍
在企业级 Java 应用系统中,Oracle 数据库作为核心数据存储层,其并发控制机制(尤其是行级锁与事务隔离)既是高并发的基石,也是生产故障的“隐形推手”。当一个 UPDATE 语句未提交却长时间挂起,它可能悄然锁住关键业务表的某几行——而后续所有试图修改同一行的请求将被无情阻塞 ⏳,最终导致接口超时、线程池耗尽、服务雪崩。更棘手的是,锁本身不报错,只沉默等待;而阻塞链可能层层嵌套,形成难以察觉的“锁迷宫”。
本文将带你从 Oracle 内核视角出发,系统性拆解锁的生命周期、深入剖析 V$LOCK, V$SESSION, V$LOCKED_OBJECT, DBA_BLOCKERS, DBA_WAITERS 等核心动态性能视图的语义与关联逻辑 🧩;手把手演示如何用 SQL 快速定位“谁锁了谁、为什么锁、锁了多久”;并结合真实 Java Spring Boot 场景,展示如何通过 JdbcTemplate 与 @Transactional 的精准配合规避锁风险,以及在紧急时刻如何安全终止会话(ALTER SYSTEM KILL SESSION)而不引发 ORA-00031 或数据库异常重启 ❗。全文包含可直接运行的 Java 示例、交互式 Mermaid 可视化流程图、权威官方文档链接,以及大量实战避坑指南 —— 这不是一篇“理论手册”,而是一份你能在凌晨三点打开、复制粘贴、立即生效的 Oracle 锁应急操作指南 🚨。
🔑 一、Oracle 锁机制的本质:不只是“加锁”,而是“资源协商协议”
Oracle 的锁(Lock)并非传统意义上的“互斥开关”,而是一套基于 事务上下文 + 资源粒度 + 模式语义 的分布式协商协议。理解这一点,是读懂锁信息的前提。
✅ 锁的三个核心维度
| 维度 | 说明 | 典型值示例 |
|---|---|---|
| 资源类型(TYPE) | 锁保护的对象类别 | TX(事务锁)、TM(DML 表锁)、UL(用户定义锁)、DX(分布式事务锁) |
| 锁模式(LMODE / REQUEST) | 当前持有或请求的兼容级别 | 6=Exclusive (X), 3=Row Share (RS), 2=Row Exclusive (RX), 0=None |
| 锁定对象(ID1, ID2) | 唯一标识被锁资源 | TX: ID1=USN<<16 | SLOT, ID2=SEQ; TM: ID1=OBJECT_ID |
💡 关键洞察:
TX锁(事务锁)永远存在——每个活动事务都持有一个TX锁,用于保证事务原子性;而真正造成阻塞的,往往是TX锁与其他会话对同一数据块(Block)中同一行(Row) 的ROW EXCLUSIVE(RX)或更高模式竞争。TM锁(表级锁)则在 DML 执行时自动申请,用于防止 DDL 并发修改结构,其阻塞多见于ALTER TABLE与长事务共存场景。
📜 官方权威参考
- Oracle Database Concepts → Locking Mechanisms
- Oracle Database Reference → Dynamic Performance Views: V$LOCK
🧩 二、核心动态视图详解:构建你的“锁地图”
Oracle 不提供“一键锁视图”,但通过组合查询 V$ 视图,你能拼出完整锁拓扑。以下是最常用、最可靠的 5 张视图及其关系:
✅ 图表说明:箭头表示逻辑关联方向。
V$SESSION是会话元数据中心;V$LOCK记录每个会话持有的/请求的所有锁;V$LOCKED_OBJECT明确列出当前被锁住的对象(含OBJECT_ID,SESSION_ID,ORACLE_USERNAME,OS_USER_NAME);V$TRANSACTION提供事务起始 SCN、回滚段信息,对分析长事务至关重要。
🔍 2.1 V$LOCK:锁的“身份证档案”
这是所有锁分析的起点。关键字段:
SID: 会话 ID(关联V$SESSION)TYPE: 锁类型(重点看TX,TM)LMODE: 当前持有模式(0=无,1=null,2=RS,3=RX,4= S,5=SSX,6=X)REQUEST: 请求模式(>0 表示正在等待该模式锁)CTIME: 持有/等待秒数(⚠️ 注意:此为自锁建立/请求发起至今的秒数,非精确阻塞时长)BLOCK: 是否为阻塞者(1=是,0=否)
✅ 黄金 SQL:快速识别阻塞链
SELECT
l1.sid AS blocking_session,
s1.username AS blocker_user,
s1.osuser AS blocker_osuser,
s1.machine AS blocker_machine,
s1.program AS blocker_program,
l2.sid AS blocked_session,
s2.username AS blocked_user,
s2.osuser AS blocked_osuser,
s2.machine AS blocked_machine,
s2.program AS blocked_program,
lo.object_name AS locked_object,
lo.object_type AS object_type,
ROUND(l2.ctime / 60, 1) AS wait_minutes
FROM v$lock l1, v$lock l2, v$session s1, v$session s2, v$locked_object lo
WHERE l1.block = 1
AND l2.request > 0
AND l1.id1 = l2.id1
AND l1.id2 = l2.id2
AND l1.sid = s1.sid
AND l2.sid = s2.sid
AND lo.session_id = l2.sid
AND lo.xidusn = l2.id1
AND lo.xidslot = l2.id2;
⚠️ 注意:
v$locked_object.xidusn/xidslot对应v$lock.id1/id2,但仅对TX锁有效。若需兼容TM锁,应改用lo.object_id关联dba_objects.object_id。
🔍 2.2 V$LOCKED_OBJECT:被锁对象的“实名登记簿”
它比 V$LOCK 更聚焦业务实体,字段简洁有力:
SESSION_ID: 持有锁的会话 IDORACLE_USERNAME: 数据库用户名OS_USER_NAME: 操作系统用户名(常用于定位应用服务器进程)OBJECT_ID: 被锁对象编号(查DBA_OBJECTS得表名)LOCKED_MODE: 锁定模式(1=Null, 2=Row-S, 3=Row-X, 4=Share, 5=S/Row-X, 6=Exclusive)XIDUSN, XIDSLOT, XIDSQN: 事务 ID 三元组(唯一标识一个事务)
✅ 实用 SQL:按表名查所有锁持有者
SELECT
lo.session_id,
s.username,
s.osuser,
s.machine,
s.program,
o.object_name,
o.object_type,
DECODE(lo.locked_mode,
0, 'None', 1, 'Null', 2, 'Row-S (SS)', 3, 'Row-X (SX)',
4, 'Share', 5, 'S/Row-X (SSX)', 6, 'Exclusive', 'Unknown') AS lock_mode,
s.status,
s.sql_id,
s.last_call_et AS seconds_since_last_call
FROM v$locked_object lo
JOIN dba_objects o ON lo.object_id = o.object_id
JOIN v$session s ON lo.session_id = s.sid
WHERE o.object_name = UPPER('ORDERS') -- 替换为你关心的表名
ORDER BY lo.session_id;
🔍 2.3 V$SESSION:会话的“全息画像”
没有它,你无法知道 SID=123 到底是谁、在哪台机器、跑什么程序、执行什么 SQL。关键字段:
SID,SERIAL#: 会话唯一标识(ALTER SYSTEM KILL SESSION '123,456'必需)USERNAME,OSUSER,MACHINE,PROGRAM: 用户与环境指纹STATUS:ACTIVE(正在执行)、INACTIVE(空闲但未断开)SQL_ID,PREV_SQL_ID: 当前/上一条执行 SQL(关联V$SQL.SQL_TEXT可查完整语句)EVENT: 当前等待事件(如enq: TX - row lock contention是典型阻塞信号)BLOCKING_SESSION: 直接阻塞它的会话 SID(Oracle 10g+ 新增,比V$LOCK.BLOCK更直观!)
✅ 杀手级 SQL:带 SQL 文本的实时阻塞快照
SELECT
s1.sid AS blocker_sid,
s1.serial# AS blocker_serial,
s1.username AS blocker_user,
s1.osuser AS blocker_osuser,
s1.machine AS blocker_machine,
s1.program AS blocker_program,
s1.event AS blocker_event,
q1.sql_text AS blocker_sql,
s2.sid AS blocked_sid,
s2.serial# AS blocked_serial,
s2.username AS blocked_user,
s2.osuser AS blocked_osuser,
s2.machine AS blocked_machine,
s2.program AS blocked_program,
s2.event AS blocked_event,
q2.sql_text AS blocked_sql,
s2.seconds_in_wait AS wait_seconds
FROM v$session s1
JOIN v$session s2 ON s1.sid = s2.blocking_session
LEFT JOIN v$sql q1 ON s1.sql_id = q1.sql_id AND q1.child_number = s1.sql_child_number
LEFT JOIN v$sql q2 ON s2.sql_id = q2.sql_id AND q2.child_number = s2.sql_child_number
WHERE s2.blocking_session IS NOT NULL
AND s1.status = 'ACTIVE';
🌟 小技巧:
s2.seconds_in_wait是 Oracle 自动计算的等待秒数,比V$LOCK.CTIME更可靠,因为它基于会话状态变更时间戳。
🚨 三、实战:定位一场真实的订单支付阻塞事故
假设某天下午 14:23,电商系统“订单支付”接口大面积超时(HTTP 504),监控显示数据库 CPU 正常,但 db time 飙升,慢 SQL 排行榜榜首出现大量 UPDATE ORDERS SET STATUS='PAID' WHERE ORDER_ID=?。
我们立刻登录数据库执行诊断:
步骤 1:发现阻塞源头
-- 执行前述“杀手级 SQL”
-- 结果返回 1 条记录:
-- blocker_sid=245, blocker_user=APP_USER, blocker_machine=app-srv-03, blocker_program=Java(TM)...
-- blocker_sql=UPDATE ORDERS SET STATUS='PROCESSING' WHERE ORDER_ID=1000001 AND STATUS='CREATED'
-- blocked_sid=312, blocked_user=APP_USER, blocked_machine=app-srv-05, ...
-- blocked_sql=UPDATE ORDERS SET STATUS='PAID' WHERE ORDER_ID=1000001 AND STATUS='PROCESSING'
✅ 结论清晰:会话 245 正在将订单 1000001 状态从 CREATED 改为 PROCESSING,但事务未提交;会话 312 试图将其改为 PAID,因同一行被锁而阻塞。
步骤 2:深挖会话 245 的行为
-- 查看会话 245 的详细信息
SELECT sid, serial#, username, status, sql_id, prev_sql_id,
event, seconds_in_wait, last_call_et, logon_time
FROM v$session WHERE sid = 245;
-- 输出:
-- SID=245, SERIAL#=12345, USERNAME=APP_USER, STATUS=INACTIVE
-- SQL_ID=null, PREV_SQL_ID=a1b2c3d4e5f67890, EVENT=null
-- LAST_CALL_ET=18200 (5小时!), LOGON_TIME=2023-10-05 09:12:33
-- 关联查 PREV_SQL_ID 对应的 SQL 文本
SELECT sql_text FROM v$sql WHERE sql_id = 'a1b2c3d4e5f67890';
-- 输出:
-- UPDATE ORDERS SET STATUS='PROCESSING' WHERE ORDER_ID=1000001 AND STATUS='CREATED'
✅ 关键证据:LAST_CALL_ET=18200 秒(5 小时),STATUS=INACTIVE,且 EVENT 为空 —— 这是一个典型的 “长事务挂起”:应用获取了连接、执行了 SQL、但因网络抖动、代码 bug 或人为调试,忘记调用 connection.commit()。
步骤 3:检查事务详情(确认是否可安全终止)
-- 查会话 245 的事务信息
SELECT t.start_time, t.used_ublk, t.used_urec,
r.name AS rollback_segment,
s.sql_id, s.prev_sql_id
FROM v$transaction t
JOIN v$rollname r ON t.xidusn = r.usn
JOIN v$session s ON t.ses_addr = s.saddr
WHERE s.sid = 245;
-- 输出:
-- START_TIME=2023-10-05 09:12:33, USED_UBLK=2, USED_UREC=1
-- ROLLBACK_SEGMENT=_SYSSMU10_1234567890$, SQL_ID=null
✅ USED_UBLK=2(仅用了 2 个回滚块)、USED_UREC=1(仅 1 条记录变更)表明事务非常轻量,回滚代价极低,安全终止风险可控。
🛠 四、安全释放锁:Kill Session 的艺术与科学
ALTER SYSTEM KILL SESSION 是终极武器,但滥用会导致会话异常中断、应用连接池混乱甚至 ORA-00031(session marked for kill)。必须遵循“三步法”。
✅ 第一步:尝试优雅中断(推荐!)
-- 发送中断信号,让会话自行清理(Oracle 10g+)
ALTER SYSTEM DISCONNECT SESSION '245,12345' IMMEDIATE;
IMMEDIATE:强制断开网络连接,触发客户端 JDBC 的SQLException,应用层可捕获并重试。- 优点:不杀 OS 进程,不触发
ORA-00031,对数据库实例零影响。 - 缺点:若客户端已崩溃或网络不可达,可能无效。
✅ 第二步:强硬终止(当优雅中断失败)
-- 终止会话(注意:SERIAL# 必须准确!)
ALTER SYSTEM KILL SESSION '245,12345';
- 执行后,
V$SESSION.STATUS变为KILLED,但 OS 进程可能仍存在(V$PROCESS.SPID不变)。 - Oracle 会在下次该会话尝试执行任何操作时,抛出
ORA-00028: your session has been killed并彻底清理。
✅ 第三步:终极手段(极罕见,仅限 OS 进程僵死)
# 在数据库服务器上,查 SPID
SELECT p.spid, s.sid, s.serial#, s.program
FROM v$process p, v$session s
WHERE p.addr = s.paddr AND s.sid = 245;
# 输出:SPID=12345
# 然后在 Linux 执行:
kill -9 12345
⚠️ 警告:
kill -9是最后手段!它绕过 Oracle 实例管理,可能导致回滚段不一致、SMON 进程需要长时间恢复。生产环境严禁随意使用!
📜 官方最佳实践指南
- Oracle Support Doc ID 26513.1 → How to Kill a Session
- Oracle Database Administrator’s Guide → Ending Sessions
💻 五、Java Spring Boot 实战:从代码层面预防锁问题
再强大的 DBA 工具,也不如写出健壮的 Java 代码来得根本。以下是 Spring Boot 中规避锁风险的四大黄金法则,并附完整可运行示例。
✅ 法则 1:事务边界最小化 —— 用 @Transactional 精确控制
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
// ❌ 危险:大事务包裹无关操作,延长锁持有时间
@Transactional
public void dangerousProcessOrder(Long orderId) {
// 1. 查询订单(可能锁住行)
Order order = getOrderById(orderId);
// 2. 调用外部支付网关(耗时 3s+,锁一直持有!)
PaymentResult result = paymentGateway.pay(order.getAmount());
// 3. 更新状态
updateOrderStatus(orderId, "PAID");
}
// ✅ 安全:拆分事务,查询与更新分离
@Transactional(readOnly = true)
public Order getOrderForUpdate(Long orderId) {
return jdbcTemplate.queryForObject(
"SELECT * FROM ORDERS WHERE ORDER_ID = ? FOR UPDATE",
new Object[]{orderId},
new OrderRowMapper()
);
}
@Transactional
public void safeProcessOrder(Long orderId) {
// 1. 读取并加锁(只在此事务内)
Order order = getOrderForUpdate(orderId);
// 2. 外部调用(不在事务内!)
PaymentResult result = paymentGateway.pay(order.getAmount());
// 3. 更新(新事务,锁时间极短)
updateOrderStatus(orderId, "PAID");
}
@Transactional
public void updateOrderStatus(Long orderId, String status) {
jdbcTemplate.update(
"UPDATE ORDERS SET STATUS = ?, UPDATED_AT = SYSDATE WHERE ORDER_ID = ?",
status, orderId
);
}
}
💡 原理:
@Transactional(readOnly = true)会禁用写操作,但SELECT ... FOR UPDATE仍可加锁(因它是 DML 的一部分)。Spring 的DataSourceTransactionManager会确保getOrderForUpdate()的FOR UPDATE在事务提交时才释放锁,而updateOrderStatus()是另一个独立短事务。
✅ 法则 2:使用 SELECT FOR UPDATE NOWAIT 避免无限等待
@Repository
public class InventoryRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
// ✅ 加锁失败立即抛异常,而非阻塞
public boolean tryReserveStock(Long productId, int quantity) {
try {
int updated = jdbcTemplate.update(
"UPDATE INVENTORY SET STOCK = STOCK - ? " +
"WHERE PRODUCT_ID = ? AND STOCK >= ? " +
"AND ROWNUM = 1", // 防止多行误更新
quantity, productId, quantity
);
return updated > 0;
} catch (DataAccessException e) {
// 若因锁冲突,Oracle 抛 ORA-00054: resource busy
if (e.getCause() instanceof SQLException) {
SQLException sqlEx = (SQLException) e.getCause();
if ("54".equals(sqlEx.getSQLState()) || sqlEx.getErrorCode() == 54) {
throw new InventoryLockedException("Product " + productId + " is locked by another transaction");
}
}
throw e;
}
}
}
✅ 法则 3:设置 JDBC 连接超时与查询超时
# application.yml
spring:
datasource:
url: jdbc:oracle:thin:@//db.example.com:1521/ORCLPDB1
username: app_user
password: ${DB_PASSWORD}
hikari:
connection-timeout: 30000 # 连接池获取连接最大等待时间:30s
validation-timeout: 3000 # 连接有效性检测超时:3s
idle-timeout: 600000 # 连接空闲最大时间:10分钟
max-lifetime: 1800000 # 连接最大存活时间:30分钟
# 👇 关键:为每个 Statement 设置查询超时
data-source-properties:
oracle.jdbc.ReadTimeout: 10000 # 10秒,防 SQL 长期卡住
oracle.jdbc.ConnectionTimeout: 30000
✅ 法则 4:使用乐观锁替代悲观锁(推荐高并发场景)
@Entity
@Table(name = "ORDERS")
public class Order {
@Id
private Long orderId;
private String status;
@Version // JPA 乐观锁版本字段
private Integer version;
// getters & setters...
}
@Repository
public class OptimisticOrderRepository {
@PersistenceContext
private EntityManager entityManager;
// ✅ 乐观锁:UPDATE ... WHERE VERSION = ?
@Transactional
public void updateStatusOptimistic(Long orderId, String newStatus, Integer expectedVersion) {
int updated = entityManager.createQuery(
"UPDATE Order o SET o.status = :status, o.version = o.version + 1 " +
"WHERE o.orderId = :id AND o.version = :version")
.setParameter("status", newStatus)
.setParameter("id", orderId)
.setParameter("version", expectedVersion)
.executeUpdate();
if (updated == 0) {
throw new OptimisticLockException("Order " + orderId + " has been modified by another transaction");
}
}
}
🌐 对比优势:乐观锁不依赖数据库行锁,避免了
TX阻塞链;适合“读多写少”场景(如电商下单)。HikariCP 连接池配置参考:HikariCP Configuration
🧪 六、模拟阻塞实验:亲手构建并破解锁链(本地可验证)
为了彻底理解,我们在本地 Oracle XE(免费版)搭建一个微型实验环境。无需生产库,5 分钟即可复现。
步骤 1:准备测试表与数据
-- 创建测试表
CREATE TABLE test_lock (
id NUMBER PRIMARY KEY,
name VARCHAR2(50),
status VARCHAR2(20)
);
-- 插入测试数据
INSERT INTO test_lock VALUES (1, 'TEST_ROW', 'INIT');
COMMIT;
步骤 2:开启两个 SQL*Plus 会话(Session A 和 Session B)
Session A(模拟长事务):
-- 执行更新但不提交!
UPDATE test_lock SET status = 'LOCKED_BY_A' WHERE id = 1;
-- 此时,Session A 持有 TX 锁,且未 COMMIT
Session B(尝试更新同一行):
-- 执行相同更新,将被阻塞
UPDATE test_lock SET status = 'LOCKED_BY_B' WHERE id = 1;
-- 控制台将卡住,等待 Session A 释放锁...
步骤 3:在第三个会话中执行诊断 SQL
运行本文第二部分的“黄金 SQL”,你将立即看到:
blocking_session=SID_A,blocked_session=SID_Blocked_object='TEST_LOCK',wait_minutes > 0
步骤 4:安全释放
在 Session A 中执行 COMMIT;,Session B 立即完成;或在诊断会话中执行 ALTER SYSTEM KILL SESSION 'SID_A,SERIAL#',Session B 将收到 ORA-00028 后退出阻塞。
✅ 这个实验让你亲眼见证锁的诞生、传播与消亡,是理解一切的基础。
🧩 七、进阶:识别与破解死锁(Deadlock)
死锁是两个或多个会话互相等待对方持有的资源,形成循环依赖。Oracle 自动检测(每 3 秒轮询),并牺牲其中一个会话(ORA-00060: deadlock detected),输出 trace 文件。
🔍 死锁特征与日志分析
- 现象:应用日志突现
ORA-00060,且伴随Deadlock graph片段。 - 关键线索:
Current SQL statement for this session:当前阻塞 SQLRows waited on:等待的具体行(<object#> <file#> <block#> <row#>)Session 1/Session 2:参与死锁的双方,及各自持有/等待的锁
✅ 预防死锁的 Java 实践
@Service
public class DeadlockPreventionService {
// ✅ 黄金法则:所有事务按固定顺序访问资源(如按主键升序)
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
// 强制从小到大更新,打破循环可能
Long firstId = Math.min(fromAccountId, toAccountId);
Long secondId = Math.max(fromAccountId, toAccountId);
// 先更新小 ID 账户
jdbcTemplate.update(
"UPDATE ACCOUNTS SET BALANCE = BALANCE - ? WHERE ACCOUNT_ID = ?",
amount, firstId
);
// 再更新大 ID 账户
jdbcTemplate.update(
"UPDATE ACCOUNTS SET BALANCE = BALANCE + ? WHERE ACCOUNT_ID = ?",
amount, secondId
);
}
}
📜 Oracle 死锁官方文档:Understanding and Resolving Deadlocks
🛡 八、生产环境加固清单(Checklist)
一份可直接打印张贴在工位的运维守则:
| 类别 | 检查项 | 状态 ✅/❌ | 备注 |
|---|---|---|---|
| 监控 | 已部署 v$session 阻塞会话告警(阈值 > 60s) | 使用 Prometheus + Grafana | |
| 连接池 | HikariCP connection-timeout ≤ 30s,max-lifetime ≤ 30m | 防连接泄漏 | |
| SQL 规范 | 所有 UPDATE/DELETE 必带 WHERE 条件,禁止全表更新 | 用 SonarQube 静态扫描 | |
| 事务设计 | @Transactional 方法内无远程调用、文件 I/O、复杂计算 | 用 AOP 切面审计 | |
| 索引健康 | ORDERS.ORDER_ID 等高频查询字段有主键/唯一索引 | 防 UPDATE 锁全表 | |
| 应急预案 | DBA 已授权 ALTER SYSTEM KILL SESSION 权限,且团队知晓流程 | 每季度演练一次 |
🌈 九、结语:锁不是敌人,而是并发的协作者
当我们深夜被一条 ORA-00060 惊醒,或是看着监控里 db time 曲线陡然拉高,焦虑是本能。但请记住:Oracle 的锁机制,是经过数十年金融、电信级场景千锤百炼的精密设计。它不脆弱,它只是需要被正确理解、被敬畏地使用。
本文所授的每一条 SQL、每一个 Java 注解、每一处配置参数,都不是“魔法咒语”,而是与 Oracle 对话的语言。当你能从容说出 “这个阻塞是因为会话 245 持有 TX 锁,而 312 在等它释放同一行的 RX 模式”,你就已经站在了问题之上,而非困于其中。
最后,送给你一句来自 Oracle 官方文档的箴言 🌟:
“Concurrency control is not about preventing conflict — it’s about managing it gracefully.”
(并发控制的目的,不在于杜绝冲突,而在于优雅地管理冲突。)
愿你每一次 COMMIT 都坚定,每一次 ROLLBACK 都从容,每一次 KILL SESSION 都心中有谱。数据库的寂静之下,自有山河万里 🌍。
✨ 延伸学习推荐
- Oracle Database Performance Tuning Guide → Managing Locks
- Java Persistence with Hibernate → Chapter 5: Transactions and Concurrency
- The Art of SQL → Chapter 7: Concurrency Control
🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨
654

被折叠的 条评论
为什么被折叠?



