数据库乐观锁原理
乐观锁并不是高深莫测的“原子操作”或“事务隔离魔法”,它是一种让数据在并发修改时“吵吵架”然后保持最终一致的实用策略。本文用3000+字、大量示例、卡片和时间轴,带你彻底搞懂数据库乐观锁原理及其关联话题。
乐观锁假定多用户并发修改数据的概率很低,所以不采用数据库自带的锁机制,而是在提交更新时检查数据是否被其他事务修改过。如果被修改,则拒绝本次操作并回滚或重试。
就像咖啡馆点单:服务员先给你咖啡(数据),如果你没付钱(未提交),别人也能点单;但结账时发现咖啡已被调换,则重新协商。
version 字段。用户A读取 version=1,用户B也读取 version=1。A 提交时 set version=2 where version=1;B 提交时同样条件,但此时 version 已变成2,更新影响行数为0,B 操作失败并重试。
与悲观锁对比 悲观锁像“死板老警察”,谁越界抓谁;乐观锁像“高冷观察员”,只盯着刚改完数据的人。
A 拿一块放一边,B 又拿一块塞进去——结局两块都变了形。乐观锁就是在“越界修改”时救场:记录当前登录用户ID或版本号,当有人改过,数据库拦截并报错:“嘿,张三,你刚改了我刚改过的价格!”
? 乐观锁本质是基于版本号或时间戳的CAS操作,但比CAS更直观——直接利用数据库行锁或更新影响行数。
一致性检查有时是事后诸葛亮(比如转账后扣钱),而乐观锁是事前管住:在你动数据之前,如果发现数据被改了,系统先给你泼盆冷水。乐观锁通常利用“版本号+更新条件”实现,避免了数据库连接长时间持有锁,性能更高。
网友们还关心:乐观锁会不会导致大量回滚? 实际上在冲突不频繁的场景(如读多写少),回滚极少;如果冲突率高,可考虑重试机制或改用悲观锁。
假设用户余额表 account(id, balance, version)。A和B都想给同一用户加100元。
UPDATE account SET balance=200, version=2 WHERE id=1 AND version=1 成功网友关心 如果B重试,会重新读取最新version=2,再基于200加100变成300,最终一致。
商品库存表 product(id, stock, version)。两个用户同时买同一商品。
UPDATE product SET stock=9, version=4 WHERE id=1 AND version=3 成功乐观锁避免了超卖,但高并发下重试可能增加负载。可结合分布式锁或限流。
UPDATE stock SET stock=stock-1, version=version+1 WHERE id=? AND version=? 检查影响行数。订单状态机:待支付→已支付→已发货。乐观锁防止状态错乱。
每次更新 SET status='已支付', version=version+1 WHERE id=1 AND version=5 AND status='待支付'。如果其他线程已经更新,则当前操作失败,不会出现“已支付”又被改成“待支付”。
网友常问:乐观锁和CAS区别? CAS是CPU原语,乐观锁是数据库层的“compare and swap”,本质上都是先检查再更新。
用户同时在手机和PC端操作购物车,乐观锁通过版本号防止修改丢失。每次更新携带版本,冲突时提示“购物车已变化”。
多管理员修改系统配置,使用乐观锁避免互相覆盖。类似Git的冲突检测。
选座时,利用版本号防止多人锁定同一座位。提交时若版本变化则释放座位。
读取数据时获取 version(或时间戳)。
执行业务逻辑,构造更新SQL:UPDATE table SET x=?, version=version+1 WHERE id=? AND version=oldVersion
检查影响行数:若为0,说明版本被其他事务修改,抛出异常或重试。
无需数据库锁,性能高;天然防止丢失更新。
INT 或 BIGINT,初始值0或1。last_modified,但需确保精度。CREATE TABLE item (id INT, data TEXT, version INT DEFAULT 0);
⚠️ 注意事项: 乐观锁在冲突频繁时会导致大量重试,增加DB压力。此时可引入重试队列或分布式锁。但大多数业务场景(如CMS、个人设置)冲突极少,乐观锁是首选。
不一定。也可使用时间戳或校验和。但版本号最直观且不受时钟影响。另外,CAS操作(如compare and set)在内存中常用,数据库乐观锁本质是CAS的数据库实现。
还有网友问:乐观锁导致ABA问题? 数据库层面版本号单调递增,不会出现ABA;但如果使用时间戳,可能因精度导致ABA,建议用版本号。
update_time作为乐观锁条件,若两次更新在同一毫秒,可能丢失更新。解决方案:使用version INT。
@Version 注解。? 乐观锁之所以长盛不衰,是因为它在大多数场景下“够用且快”。正如网友所说:“乐观锁像个老好人,不抓人也不放人,只盯着改数据的人。”
数据库乐观锁原理与事务隔离级别密切相关:在“可重复读”隔离级别下,乐观锁可以防止丢失更新。例如两个事务同时读取同一行,然后分别修改不同字段,最后一个提交会覆盖前者——乐观锁通过版本号避免。
? 与分布式锁结合: 在秒杀中,先用分布式锁扣减库存,再用乐观锁校验版本,双层保障。
? 与补偿事务配合: 乐观锁失败后,可记录日志并异步补偿,确保最终一致性。
某电商订单中心采用乐观锁后,死锁率降低90%,吞吐量提升2倍。冲突重试率仅0.3%。
MyBatis-Plus 的 @Version 注解,JPA 的 @Version,以及 jOOQ 都原生支持乐观锁。
? 记住:乐观锁不是银弹,但它是数据库并发控制里最优雅的“糙”办法。正如本文开头所说——“让数据在两个人同时改同一个文件时,略微吵吵架,确保最终文件不烂”。