行业资讯
📅 2026/7/10 8:41:28
数据库的锁
锁的分类1. 按粒度分类锁的范围粒度决定了锁影响的数据量直接影响并发性能。全局锁锁定整个数据库实例。常用于全库备份以确保数据一致性。代价很高会使整个库进入只读状态通常业务停摆。表级锁锁定整张表。开销小、加锁快但并发度最低。比如MyISAM引擎主要使用表锁。页级锁介于表锁和行锁之间锁定一个数据页。BDB存储引擎使用并发度一般。行级锁锁定一行或几行数据粒度最细并发度最高但开销也最大。InnoDB引擎默认使用行锁。需要注意若查询未使用索引行锁可能升级为表锁。2. 按模式分类锁的类型这是最核心的分类决定了锁的具体用途。共享锁S锁也叫读锁。允许事务读取数据但不能修改。多个事务可以同时持有共享锁。排他锁X锁也叫写锁。允许事务读取和修改数据与其他任何锁都不兼容。INSERT、UPDATE、DELETE等操作会自动添加排他锁。意向锁IS/IX一种表级锁由数据库自动添加。它的存在是为了提高加锁效率当事务想给某行加锁时会先在表上加意向锁其他事务想加表锁时直接看这个意向锁就能判断是否有行锁冲突无需遍历所有行。更新锁U锁是SQL Server特有的一种锁。它主要用于更新操作初期加在数据上允许其他事务读但不允许再加更新锁之后会升级为排他锁目的是为了避免一种特定的死锁更新丢失导致的死锁。元数据锁MDL在MySQL 5.5后引入是一种表级锁用于保护表结构元数据。当对表进行增删改查时会自动加MDL共享锁当执行ALTER TABLE等DDL操作时会加MDL排他锁这会阻塞所有对该表的读写操作。间隙锁Gap Lock与临键锁Next-Key Lock这是MySQL InnoDB在可重复读Repeatable Read隔离级别下用来解决幻读问题的锁。间隙锁锁住两行之间的“空隙”防止其他事务插入新数据临键锁则是“行锁间隙锁”的组合。3. 从程序员视角看乐观锁与悲观锁这是一种设计思想上的分类。悲观锁认为并发冲突很可能发生所以提前加锁。数据库提供的锁如SELECT ... FOR UPDATE都属于悲观锁。乐观锁认为冲突很少发生不加锁而是在更新时通过版本号或时间戳检查数据是否被改动。如果版本号不对则更新失败需要重试。这通常由应用程序实现。锁会引发哪些问题加锁是为了保证一致性但也会带来新的问题1. 死锁Deadlock情况两个或多个事务互相持有对方需要的锁形成循环等待谁也无法继续。案例事务A更新表T1后想更新表T2事务B更新表T2后想更新表T1二者相互等待就形成了死锁。后果所有涉及的事务都被阻塞直到数据库的死锁检测机制介入强行回滚其中一个事务牺牲者并抛出错误1205。解决数据库通常会自动检测并回滚代价较小的事务。应用层需要捕获错误1205并进行重试。从根本上预防需要尽量让事务按相同顺序访问资源。2. 锁等待超时Lock Wait Timeout情况一个事务等待另一个事务所持有的锁但等待时间超过了设定的阈值如lockwait_timeout。后果当前SQL语句失败并报错LOCK_WAIT_TIMEOUT。解决需要定位并优化持锁时间长的大事务或调整超时参数。可以通过查询pg_locksPostgreSQL或performance_schema.data_locksMySQL找到阻塞源。3. 锁资源耗尽情况在一个大事务中如删除百万行数据数据库为每一行都加了行锁并且由于与其他锁冲突无法升级为表锁最终耗尽了数据库的锁资源上限。后果事务失败报错“锁资源耗尽”。解决将大事务拆分成多个小事务处理例如每次只删除几千条数据并提交避免累积过多行锁。4. 性能下降与“闩锁Latch”争用情况“闩锁”是数据库引擎内部的一种轻量级锁用于保护内存中的数据结构如数据页、索引页与用于保证业务一致性的“锁”不同。在高并发场景下多个线程争抢同一个数据页或索引页的闩锁会导致性能瓶颈。后果CPU资源被大量消耗在等待闩锁上导致吞吐量急剧下降例如在进行大量并发插入时索引“最后一页”的闩锁争用就非常典型。解决这是数据库内核层面的问题用户能做的是优化表结构和索引设计例如使用哈希分区打散热点数据页的写入压力减少对同一页的争用。总结与核心解决思路面对锁引发的问题可以遵循以下思路来排查和解决1. 定位阻塞源通过show processlist;MySQL或查询pg_locks、pg_stat_activityPostgreSQL等视图找到是哪个会话pid或Id在阻塞其他查询。2. 杀死阻塞会话在紧急情况下可以使用kill [会话ID];命令终止阻塞事务释放锁快速恢复业务。3. 优化应用程序这是根本。确保事务尽可能短避免在事务中进行大批量操作或耗时逻辑为查询语句创建高效的索引防止行锁升级为表锁合理安排批量操作与联机交易的执行时间避免冲突。4. 调整数据库设计在高并发写入场景下可考虑使用哈希分区表来分散写入热点降低“闩锁”争用的可能。