全局锁

设在多个事务同时访问同一条数据时,冲突发生的概率较低,因此在操作数据时不会立即进行锁定,而是在提交数据更改时检查是否有其它事务修改了这条数据,若没有就提交更改,否则就回滚事务

MYSQL中并没有内置实现乐观锁,但可以通过一些技巧实现,常见的实现方式是使用版本号(或时间戳)字段,每当一条记录被修改时,就增加版本号(或更新时间戳)。 在更新记录时,先检查版本号(或时间戳)是否和读取记录时的版本号(或时间戳)一致,如果一致则执行更新并增加版本号(或更新时间戳),否则拒绝更新

优点:在大部分时间都不需要锁定,所以在冲突较少的情况下可以获得较高的并发性能,然而,如果冲突较多,那么乐观锁可能会导致大量的事务回滚,从而影响性能,因此,选择乐观锁还是其它锁定技术,需要根据实际的并发情况和性能需求来决定

使用场景

  • 低冲突环境
  • 读多写少的场景:在读操作远多于写操作的情况下,乐观锁可以避免由于频繁的读操作导致不必要的锁定开销
  • 短事务操作
  • 分布式系统:由于网络延迟等原因,事务冲突的可能性较低,因此乐观锁是一个合适的选择
  • 互联网应用:并发修改同一条数据的几率较小,因此使用乐观锁可以提高系统性能

缺点

  • 冲突检测
  • 处理开销:冲突发生时需要进行回滚和重试,可能会增加系统的开销,在一些场景中可能会导致性能下降
  • 版本管理:乐观锁通常通过版本号(或时间戳)来检测冲突
  • 编程复杂性:使用乐观锁需要更复杂的编程,程序需要处理可能发生的冲突和重试

有效的并发控制策略,在冲突较多的情况下可能会带来更大的开销和编程复杂性,因此是否使用乐观锁现需要根据应用的具体需求和场景来决定


表锁

mysql中最基本的锁策略 表级锁开销小、加锁快,不会出现死锁 锁定粒度大,发生锁冲突的概率最高 并发度最低

表共享读锁(Table Read Lock):表读锁,允许一个事务锁定的表进行读取操作,不允许其它事务对其进行写操作,但可以进行读操作,读锁之间是不会互相阻塞的

表独占写锁(Table Write Lock)表写锁 允许一个事务锁定的表进行读取和写入(更新)操作,但其它任何事务都不能再对该表进行任何操作,必须等待表写锁结束,写锁会阻塞其它所有锁,包括读锁和写锁

在MySQL中对MylSAM表的读操作,会自动加上读锁,对MylSAM表的写操作,会自动加上写锁

InnoDB引擎在必要的情况下会使用表锁,但主要是使用行锁来实现多版本并发控制(MVCC)能提供更好的并发性能和更少的锁冲突

适用读操作多,写操作少的应用,当并发争用5不是特别激烈,以及记录级锁并发控制开销大于访问冲突开销的情况,在并发度高,或写操作较多的情况下,表锁可能会成为瓶颈

使用场景

  • 读密集型

  • 写操作不频繁的场景

  • 数据量不大的简单应用

  • 全表更新或删除

虽然表级锁的开销小,但由于其锁定粒度大,可能会导致并发度下降

MYSQL中会发生表级锁的命令

  • ALTER TABLE
  • DROP TABLE & TRUNCATE TABLE 删除整个表以及删除表中的所有数据
  • LOCK TABLES
  • MYlSAM存储引擎,全表扫描或大范围扫描
  • 全局锁

表锁的风险点

  • 性能下降:在高并发的环境中可能导致大量的请求阻塞,从而降低性能,对于读取和写入混合密集的负载,表锁可能会成为一个性能瓶颈
  • 并发性能差:一旦一个线程对表加了写锁,其它线程的任何读写操作都会被阻塞,直到写锁被释放,同样的,如果一个读锁被持有,那么其它的写操作将被阻塞,这就使得并发性能大大降低
  • 可能导致锁等待和超时:在高并发的环境中,由于表级锁的粒度较大,可能会有很多线程在等待锁,如果等待的时间过长,可能会导致锁超时,进一步影响应用的性能和可用性
  • 写操作影响大
  • 死锁的可能性:表锁本身不会出现死锁,但在多表操作中,若没有按照一定的顺序获得锁,可能会导致读锁

为避免此类问题,通常会选择InnoDB存储引擎,主要使用行级锁,可提供更好的并发性能,并在一定程度上减少锁争用的问题 并且InnoDB支持事务,可保证数据的一致性和完整性,在实际应用中,我们应根据具体的业务需求和系统负载,选择合适的存储引擎和锁策略


行锁

单表中单行进行锁定

相比于表级锁和页锁,行级锁的粒度更小,因此在处理高并发事务时,能提供更好的并发性能和更少的锁冲突,然而,行级锁也需要更多的内存和CPU资源,需要对每一行都进行管理

在MYSQL中行级锁主要有InnoDB提供,InnoDB支持两种类型的行级锁

  • 共享锁(S锁)读锁
  • 排他锁(X锁)写锁

在实际应用中InnoDB还提供了一种间隙锁的特性,不仅锁定一个具体的行,还锁定其前后的间隙,可以防止其它事务插入新的行到已锁定行的前后,从而解决一些并发问题

使用场景

  • 高并发读写操作
  • 单行操作
  • 短期锁
  • 实现并发控制
  • 复杂的事务处理

由于行级锁的锁定粒度较小,可能消耗更多的系统资源(例如内存和CPU),特别是在处理大量数据时,此外使用行级锁也可能导致死锁,需要使用合适的策略来避免死锁,例如在事务中按照一定的顺序锁定行

发生行锁的命令

  • Select ... FOR UPDATE X
  • Select ... LOCK IN SHARE MODE S
  • INSERT X
  • UPDATE X
  • DELETE X

MYSQL行锁风险

  • 死锁: 粒度越小需要消耗资源更多,当两个或更多的事务相互等待对方释放资源时,就会发生死锁
  • 锁升级:若一个事务试图锁定的行过多,InnoDB可能会将锁从行级升级到表级,这就可能导致更多的锁冲突
  • 锁等待:如果一个事务已经锁定了某行,其它试图访问这行的事务就必须等待,可能导致性能下降
  • 资源消耗:行级锁需要更多的内存来存储锁信息,而且需要更多的CPU时间来处理锁请求和释放锁
  • 难以调试和排查:若出现性能问题或锁冲突,可能需要复杂的调试和排查工作来找出问题的原因
  • 事务隔离级别:不同的事务隔离级别会影响锁的行为和性能,可能需要根据具体的应用场景来调整事务隔离级别

乐观锁

假设在多个事务同时访问同一条数据时,冲突发生的概率较低,因此在操作数据时不会立即进行锁定,而是在提交数据更改时检查是否有其它事务修改了这条数据,若没有就提交更改,否则就回滚事务

MYSQL中并没有内置实现乐观锁,但可以通过一些技巧实现,常见的实现方式是使用版本号(或时间戳)字段,每当一条记录被修改时,就增加版本号(或更新时间戳)。 在更新记录时,先检查版本号(或时间戳)是否和读取记录时的版本号(或时间戳)一致,如果一致则执行更新并增加版本号(或更新时间戳),否则拒绝更新

优点:在大部分时间都不需要锁定,所以在冲突较少的情况下可以获得较高的并发性能,然而,如果冲突较多,那么乐观锁可能会导致大量的事务回滚,从而影响性能,因此,选择乐观锁还是其它锁定技术,需要根据实际的并发情况和性能需求来决定

使用场景

  • 低冲突环境
  • 读多写少的场景:在读操作远多于写操作的情况下,乐观锁可以避免由于频繁的读操作导致不必要的锁定开销
  • 短事务操作
  • 分布式系统:由于网络延迟等原因,事务冲突的可能性较低,因此乐观锁是一个合适的选择
  • 互联网应用:并发修改同一条数据的几率较小,因此使用乐观锁可以提高系统性能

缺点

  • 冲突检测
  • 处理开销:冲突发生时需要进行回滚和重试,可能会增加系统的开销,在一些场景中可能会导致性能下降
  • 版本管理:乐观锁通常通过版本号(或时间戳)来检测冲突
  • 编程复杂性:使用乐观锁需要更复杂的编程,程序需要处理可能发生的冲突和重试

有效的并发控制策略,在冲突较多的情况下可能会带来更大的开销和编程复杂性,因此是否使用乐观锁现需要根据应用的具体需求和场景来决定


悲观锁

认为数据在并发除里过程中很可能会出现冲突,为了保证数据的完整性和一致性,每次在读写数据时都会先加锁,这样可以避免其它事务进行并发的读写操作

使用场景

  • 写操作较多的场景
  • 并发冲突高的场景
  • 业务需要强一致性的场景

悲观锁也可能引入死锁等问题,也可能因为锁定过程中事务长时间等待而影响性能

MYSQL使用悲观锁

  • SELECT ... FOR UPDATE 在所选行上设置排他锁
  • SELECT ... LOCK IN SHARE MODE 在所选行上设置共享锁

缺点

  • 性能开销:锁定资源会影响到系统性能,每次对数据的读写都需要进行加锁和解锁的操作,势必会增加系统开销,特别是在高并发的环境下,锁的竞争更会严重影响到系统性能
  • 并发程度低:悲观锁在操作数据前就会加锁,导致在同一时间,只有一个事务能操作数据,其它数据只能等待,大大降低了系统的并发度
  • 死锁
  • 锁超时:事务长时间持有锁而不释放,可能导致其它等待锁的事务超时,不仅可能导致等待的事务失败,还可能影响到整个系统的稳定性

意向共享锁和意向排他锁

意向锁是表锁,为了协调行锁表锁的关系,支持多粒度(表锁和行锁)的锁并存

当事务A有行锁时,MYSQL会自动为该表添加意向锁 事务B如果想申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁 而是直接判断是否存在意向锁,增加性能

意向锁的兼容互斥性

意向共享锁(IS)意向排他锁(IX)
共享锁(S)兼容互斥
排他锁(X)互斥互斥

间隙锁

Gap Locks 是 MySQL InnoDB存储引擎提供的一种锁定机制 锁定的不是具体的行记录,而是两个索引之间的间隙(或称为区间)这样可以防止新的记录插入到该gap,确保数据的一致性和事务的隔离性

间隙锁常常和记录锁一起使用,共享形成Next-Key锁 保护索引记录的范围查询和扫描操作

间隙锁主要类型

  • 区间-区间间隙锁:锁定两个索引键之间的间隙,或者是第一个索引键之间的间隙
  • 区间-记录间隙锁:锁定一个索引键和一个记录之间的间隙
  • 记录-区间间隙锁:锁定一个记录和一个索引键之间的间隙

间隙锁的存在主要是为了解决幻读问题

幻读指在一个事务内读取某个范围的记录时,另外一个事务在该范围内插入了新的记录,当第一个事务再次读取该范围的记录时,会发现有些原本不存在的记录,这就是幻读

使用场景

  • 避免幻读
  • 范围查询

缺点

  • 性能影响:间隙锁会阻止其它事务在已经锁定的范围内插入新的行,这可能会影响到数据库的并发性能,尤其在需要大量插入操作的高并发场景下
  • 死锁风险
  • 复杂性
  • 锁定范围可能过大:可能会比实际需要锁定的行多,若一个事务需要锁定的只是表中的一小部分行,但由于间隙锁的存在,可能会锁定更大范围的数据,导致不必要的锁定冲突

临键锁和记录锁

临键锁 Next-Key 可以理解为一种特殊的间隙锁, 也可以理解为一种特殊的算法,通过临键锁可以解决幻读的问题,每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据,InnoDB中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列上不存在临键锁

记录锁 Record Lock ,主要用于锁定和控制对单个行记录的访问,记录锁是在索引记录上设置的,对于表没有主键或唯一索引的表,InnoDB会生成一个隐藏的聚簇索引,并在这个隐藏索引上加锁