在数据库开发中,并发操作是常态——多用户同时读写数据、多服务同步操作同一表、分布式系统跨节点访问数据库,稍有不慎就会出现数据脏读、不可重复读、幻读等问题,导致数据不一致。而数据库锁,就是解决这些并发问题的核心工具,也是数据库模块开发中不可或缺的核心能力。
一、为什么需要数据库锁?
简单来说,数据库锁的核心作用是控制并发访问,避免多个事务同时操作数据导致的冲突。举个最常见的开发场景:
用户A和用户B同时给同一个账户充值100元,账户初始余额1000元。正常情况下,最终余额应为1200元,但如果没有锁控制,可能出现以下问题:
用户A读取余额:1000元;
用户B同时读取余额:1000元;
用户A操作:1000+100=1100,写入数据库;
用户B操作:1000+100=1100,覆盖写入数据库;
最终余额为1100元,少了100元——这就是典型的并发冲突(丢失更新)。而数据库锁,就是通过“排队”机制,让多个并发操作有序执行,避免此类问题。
核心目标:在保证数据一致性的前提下,尽可能提升并发性能(锁太严会导致性能下降,锁太松会导致数据错乱)。
二、锁的粒度
锁的粒度决定锁的影响范围
锁的粒度越小,影响范围越小,并发性能越好;粒度越大,数据越安全,但并发性能越差。开发中需根据业务场景选择合适粒度。
1. 全局锁
· 作用范围:整个数据库实例;
· 核心用途:全库备份、全库维护(如升级);
· 特点:锁定整个数据库,期间所有读写操作都被阻塞,并发性能极差,仅用于特殊场景;
· 示例:MySQL的FLUSH TABLES WITH READ LOCK(FTWRL),执行后全库只读,禁止写入。
2. 表锁
· 作用范围:整个数据表;
· 核心用途:表级操作(如ALTER TABLE、批量更新全表数据);
· 特点:锁定整个表,读写操作互斥(读锁共享,写锁排他),粒度大,并发性能一般;
· 适用场景:数据批量操作、表结构修改,避免操作过程中表数据被修改;
· 示例:MySQL的LOCK TABLES 表名 READ/WRITE,Redis的SETNX实现的表级分布式锁。
3. 行锁
· 作用范围:数据表中的某一行或多行数据;
· 核心用途:单条/多条数据的并发读写(如用户充值、订单修改);
· 特点:粒度最小,并发性能最好,仅锁定需要操作的行,不影响其他行的读写;
· 适用场景:高频并发读写、单条数据修改(最常用的锁粒度);
· 示例:MySQL InnoDB引擎的行锁(基于索引实现),Redis的Hash结构实现的行级分布式锁。
4. 页锁
· 作用范围:数据库的“页”(介于表和行之间的中间粒度);
· 特点:粒度介于表锁和行锁之间,并发性能中等;
· 说明:实际开发中极少用到,仅部分数据库(如MySQL MyISAM引擎、SQL Server)支持,InnoDB引擎不支持页锁。
三、锁的性质
所有锁本质上都围绕“读写”操作设计,核心分为共享锁和排他锁,两者的互斥规则是并发控制的核心。
1. 共享锁(S锁,读锁)
· 核心作用:用于读取数据,不允许修改;
· 互斥规则:多个事务可同时持有共享锁(读-读不互斥),但不能同时持有排他锁(读-写互斥);
· 示例:MySQL中执行SELECT ... FOR SHARE,会为查询的行添加共享锁,其他事务可读取,但不能修改。
2. 排他锁(X锁,写锁)
· 核心作用:用于修改数据(插入、更新、删除);
· 互斥规则:一个事务持有排他锁时,其他事务既不能持有共享锁,也不能持有排他锁(写-读、写-写都互斥);
· 示例:MySQL中执行UPDATE、DELETE、INSERT,或SELECT ... FOR UPDATE,会自动为操作的行添加排他锁,阻塞其他读写操作。
四、锁的实现方式
结合你数据库模块中的MySQL、Redis,重点区分“本地锁”和“分布式锁”,这是开发中最易混淆也最常用的分类。
1. 本地锁(单机锁)
· 作用范围:单个数据库实例、单个应用节点;
· 实现方式:数据库原生支持(如MySQL InnoDB行锁、表锁);
· 适用场景:单机应用、单数据库实例,无分布式部署需求;
· 注意:仅能控制单个节点的并发,多节点部署时会失效(如分布式应用中,多个节点同时操作同一数据,本地锁无法跨节点控制)。
2. 分布式锁
· 作用范围:多个应用节点、多个数据库实例(分布式系统);
· 实现方式:基于第三方组件实现(如Redis、ZooKeeper),跨节点同步锁状态;
· 适用场景:分布式应用、微服务架构(你当前的项目大概率会用到);
· 示例:用Redis的SETNX命令实现分布式锁,用ZooKeeper的临时有序节点实现分布式锁。
五、总结
数据库锁的核心是“平衡数据一致性与并发性能”,开发中无需追求“最复杂的锁机制”,只需根据业务场景选择合适的锁粒度、锁类型即可。