根据锁的粒度,SQL锁主要分为表级锁、行级锁和页级锁。表级锁,顾名思义,直接锁住整张表。当某个会话对表进行写操作时,会先获得写锁,此时其他会话的所有操作都会被阻塞,直到锁被释放。这种锁开销小、加锁快,但并发能力较差,在MyISAM存储引擎中比较常见。行级锁则精细得多,它只锁定需要操作的那些数据行,其他行仍然可以正常访问。这在并发写操作频繁的场景下优势明显,但管理开销较大。InnoDB存储引擎支持行级锁,这也是它比MyISAM更适合高并发环境的重要原因。页级锁算是折中方案,锁定数据页(通常为4KB或8KB),粒度介于表锁和行锁之间。
从锁的模式来看,可以分为共享锁(S锁)和排他锁(X锁)。共享锁允许多个会话同时读取同一资源,但任何会话都不能修改——简单说就是“能读不能写”。排他锁则霸道得多,一个会话对资源加了排他锁后,其他会话既不能读也不能写,必须等待锁释放。这两种锁的兼容关系很简单:共享锁之间可以共存,但共享锁与排他锁、排他锁与排他锁之间互斥。
除了基本锁类型,还有意向锁这种机制。意向共享锁(IS)和意向排他锁(IX)是表级锁,它们的主要作用是“声明意图”。比如某个会话要对某些行加排他锁前,会先在表级别加一个意向排他锁。这样当另一个会话想要给整张表加锁时,通过检查意向锁就能快速知道表中是否有行被锁定,而不需要逐行检查,大大提升了效率。
说到锁就不得不提数据库的隔离级别。读未提交(Read Uncommitted)基本不加锁,可能读到别人未提交的数据;读已提交(Read Committed)通过加锁避免脏读,但可能出现不可重复读;可重复读(Repeatable Read)通过间隙锁等机制防止幻读;串行化(Serializable)则对所有读取都加锁,完全保证隔离性。以MySQL的InnoDB为例,在可重复读级别下,不仅会对现有数据行加锁,还会通过间隙锁锁定不存在的键值范围,有效防止其他事务在范围内插入新数据。
更新锁(U锁)是SQL Server中的特殊锁类型,它在更新操作初期使用。当需要先查找再更新时,更新锁可以防止多个会话同时查询数据后都试图更新,从而避免死锁。在找到需要更新的数据后,更新锁会升级为排他锁。
实际开发中,死锁是令人头疼的问题。两个会话各自持有对方需要的锁,又都在等待对方释放,形成无限等待。比如事务A先锁定了表T1,请求T2;同时事务B锁定了表T2,请求T1——典型的死锁场景。数据库通常有死锁检测和解除机制,会选择一个事务作为“牺牲者”回滚,让另一个继续执行。
合理使用锁需要结合业务场景。在查询语句中,可以通过SELECT ... FOR UPDATE对读取的行加排他锁,防止其他会话修改;或者使用SELECT ... LOCK IN SHARE MODE加共享锁。设置锁等待超时时间也是常用技巧,避免长时间阻塞。另外,尽量让事务简短,按相同顺序访问表资源,都能有效减少锁冲突。
锁机制是数据库并发控制的基石,理解不同锁的特性和适用场景,对设计高并发、高可用的系统至关重要。作为开发者,我们需要在数据安全性和系统性能间找到平衡点,让锁成为保障数据安全的利器,而不是系统性能的瓶颈。
1351

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



