数据库里那些事物锁到底咋用,作用和实现细节慢慢聊聊
- 问答
- 2025-12-28 19:31:16
- 3
保证数据“正确”和“完整”
为啥要这么麻烦呢?想象一个经典的场景:你和朋友共用一张银行卡,里面还剩100块钱,你们俩同时在不同的ATM机上取钱,都输入了取100块的指令,如果没有锁,两台机器可能都会先读到“余额=100”,然后各自计算“100-100=0”,最后都把余额更新为0,结果呢?你们俩成功取走了200块,但卡里只扣了100块,银行就亏大了。
这就是“并发操作”带来的问题,锁就是为了防止这种“脏读”(读到没确认的中间数据)、“不可重复读”(两次读同一数据结果不一样)、“幻读”(读出一批数据,中间突然多了或少了一条)等情况,确保数据的ACID特性里的“I”(隔离性),隔离性保证了,最终的数据才能正确(一致性)。
锁的分类:从“读锁”和“写锁”说起
锁的种类很多,但最核心、最好理解的就是这两把锁:

- 共享锁(Shared Lock),也叫读锁:就像好几个人可以同时看同一本书,我加读锁的时候,别人也可以来加读锁,大家一起读,没问题,但我不能加写锁(因为我要改内容的话,会影响到正在读的人),别人也不能加写锁,这保证了在我读的过程中,数据不会被别人改掉,我读到的是一份“快照”。
- 排他锁(Exclusive Lock),也叫写锁:这把锁是独占的,就像我拿到一个笔记本要写东西,在我写完保存之前,这本书别人既不能读(怕读到不完整的修改)也不能写,这是最霸道的一把锁。
锁的“地盘”有多大:从行到表
锁可以锁住不同大小的范围,这叫锁的粒度。
- 行级锁:这是最精细的锁,只锁住我正在修改的那一行数据,比如我更新用户表里ID=5的用户信息,就只锁住这一行,其他会话可以随意读写ID=6、7、8...的用户,并发性非常高,现代关系型数据库(如MySQL的InnoDB、PostgreSQL)主要就用行级锁。
- 表级锁:这是比较粗的锁,直接锁住整张表,比如我要执行一个需要全表扫描的更新操作,数据库可能会直接给整张表加个写锁,这时候,其他任何对这张表的操作(不管是读还是写哪一行)都得等着,并发性很差,但管理起来简单,开销小,MySQL的MyISAM引擎就用表级锁。
- 还有页锁、间隙锁等:页锁是锁住一页数据(多个行);间隙锁是锁住一个索引范围,防止别人在这个范围内插入新数据,专门解决“幻读”问题,这些就更细节了。
锁的实现与“坑”:死锁

锁机制听起来很美,但有个著名的“坑”叫死锁,还用储物柜的例子:张三需要先打开A柜子拿钥匙,再用钥匙打开B柜子;李四呢,需要先打开B柜子拿地图,再用地图找A柜子里的宝藏,如果张三锁住了A柜、李四锁住了B柜,那么张三在等B柜的锁释放,李四在等A柜的锁释放,俩人互相等待,永远卡住了。
数据库里也一样,两个事务可能互相持有对方需要的锁,又都在等待,就死锁了,那数据库怎么办?它不会让系统永远挂起,会有一个死锁检测机制,一旦发现死锁,它会选择一个“牺牲品”(通常是比较小、回滚代价低的事务),强行把它回滚,释放掉它持有的所有锁,这样另一个事务就可以继续进行了,所以你写程序时,有时会遇到事务莫名其妙被回滚,可能就是成了死锁的“牺牲品”。
实际怎么用?
作为开发者,大部分时候你不需要手动去加锁,当你执行一条UPDATE、DELETE语句,或者用SELECT ... FOR UPDATE时,数据库会自动给你加上合适的排他锁或共享锁,你需要做的是:
- 事务要短小精悍:尽快完成事务并提交,锁占用的时间越短,并发性能越好,别在事务里做网络请求、复杂计算等耗时操作。
- 注意访问资源的顺序:如果多个事务都可能更新A和B两个资源,尽量让它们都以相同的顺序(比如先A后B)去访问,可以大大降低死锁概率。
- 理解隔离级别:数据库有读未提交、读已提交、可重复读、序列化等隔离级别,级别越高,数据越安全,但加的锁越多、越严格,并发性能也越差,要根据业务场景选择最合适的级别,很多业务场景“读已提交”就足够了。
锁是数据库协调并发访问、保证数据安全的“交通警察”,它的核心是读锁(共享)和写锁(排他),作用范围可以从一行到整张表,虽然它会带来性能开销和死锁风险,但没有它,多用户环境下的数据库就会乱套,我们用好它的关键,就是理解其原理,并遵循最佳实践来编写事务。
本文由邝冷亦于2025-12-28发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://waw.haoid.cn/wenda/70209.html
