说一说MySQL中的锁机制
按粒度大小从大到小分为 全局锁
全局锁
全局锁是对整个数据库的锁,最常用的全局锁就是读写锁
- 读锁 阻止其他用户更新数据,允许其他用户读数据
- 写锁 阻止其他用户更新和读数据 修改一些大量的数据,并且不希望其他用户在这段时间内干扰时很有用
flush tables with read lock
unlock tables
全局锁的典型应用场景是,进行一些需要确保整个数据库一致性的操作,比如全库的备份和导出等。
表级锁
Q :什么是表级锁
表级锁是MySQL中最基本的锁策略,是MySQL最早采用的锁策略。表级锁的特点是开销小,加锁快,不会出现死锁。
锁的力度大,发生锁冲突的概率最高,并发度最低
表锁有两种模式:
- 表共享读锁(Table Read Lock):又称为表读锁,允许一个事务锁定的表进行读取操作,不允许其他事物对这个表进行写操作。读锁之间是不会相互阻塞的。
- 表独占写锁(Table Write Lock):又称为表写锁,允许一个事物锁定的表进行读取和写入(更新)操作,但是其他任何事物都不能再对这个表进行任何操作,必须等待表写锁结束。写锁会阻塞其他所有锁,包括读锁和写锁
在MySQL中,InnoDB引擎在必要情况下会使用表锁,但主要是使用行锁来实现多版本并发控制(mvcc),它能提供更好的并发性能和更少的锁冲突
总的来说,表锁适用于读操作多、写操作少且并发争用不是很激烈的情况。在并发度高,或者写操作较多的情况下,表锁可能会成为瓶颈
Q:表级锁有哪些适用场景
- 读密集型应用:如果你对应用主要进行读取操作,很少进行写入操作,那么使用标记锁可能是一个好选择。因为标记杜所不会则色其他的读锁,所以这种场景下表级锁能够提供很高的性能
- 写操作不频繁的场景:表级锁对写操作的处理并不高效,因为一个写锁会阻塞其他所有的锁,无论它们是读锁还是写锁,但是,如果你的应用不需要频繁地进行写操作,或者可以容忍写操作的延迟,那么使用表级锁是可行的
- 全表更新或删除:在某些情况下,可能需要对一张表进行全表的更新或删除操作,例如,删除表中的所有记录,或者更新表中所有记录的某个字段的值,在这种情况下,使用表级锁是合适的
- 数据量不大的简单应用:如果数据库的数据量不大,那么即使在写操作中,由于缩影整张表,对性能影响不大
但要注意,虽然表级锁的开销较小,但由于其锁定粒度大,会导致并发度下降,特别是在写操作频繁场景下,使用行锁更为合适
Q:哪些命令发生表锁?
- alter table
- drop table 、truncate table
- lock tables
- 全表扫描
- flush tables with read lock
Q:MySQL表锁风险点
- 性能下降
- 并发性能差
- 可能导致锁等待和超时
- 写操作影响大
- 可能导致死锁:虽然表锁本身不会出现死锁,但在多表操作中,如果没有按照一定的顺序获得锁,可能会导致死锁
行锁
Q:什么是行锁?
行级锁是MySQL中的一种锁定机制,他可以对数据库表中的单独一行进行锁定,相比于表级锁和页锁,行级锁的粒度更小,因此在处理高并发事务时,能够提供更好的并发性能和更少的所冲突。然而,行级锁也需要更多的内存和CPU资源,因为需要对每一行进行管理
在MySQL中,行级锁主要由InnoDB存储引擎提供。InnoDB支持两种类型的行锁:共享锁(S锁)和排他锁(X锁)
- 共享锁(S锁):共享锁也称为读锁,它允许一个事务读取一行数据。当一行数据被共享锁锁定时,其他事务可以读取这行数据,但不能对其进行修改
- 排它锁(X锁):排它锁也称为写锁,它允许一个事务读取和修改一行数据。当一行数据被排它锁锁定时,其他事务不能读取也不能修改这行数据。
在实际使用中,InnoDB还提供了一种名为"间隙锁"(Gap Lock)的特性。间隙锁不仅锁定一个具体的行,还锁定它前后的间隙,即这一行之前的行和之后的行之间的空间。间隙锁可以防止其他事务插入新的行到已锁定行的前后,从而可以解决一些并发问题
值得注意的是,行级锁只在事务中有效,也就是说,只有在一个事务开始(BEGIN)后并在事务提交(COMMIT) 或回滚(ROLLBACK) 之前,才能对数据行进行锁定。如果在非事务环境中执行SQL语句,那么InnoDB会在语句执行结束后立即释放所有的锁
Q:MySQL行锁有哪些适用场景
MySQL的行级锁(Row Level Locks)通常在以下几种场景中被使用:
- 高并发读写操作:在需要高并发读写操作的场景中,行级锁可以提高性能,因为它允许多个实物并发地操作不同的行
- 单行操作:对于需要操作单行数据的SQL语句(例如基于主键或唯一索引的UPDATE、DELETE和INSERT语句),行级锁可以提供较好的并发性和性能
- 短期锁:在需要对数据行进行短时间锁定的情况下,行级锁可以防止长时间阻塞其他事务
- 实现并发控制:在需要确保数据一致性和隔离性的事务中,行级锁是实现并发控制的重要机制
- 复杂的事务处理
使用行级锁需要注意,由于行级锁的锁定粒度小,它会消耗更多的系统资源,特别是处理大量数据时。此外,使用行级锁也可能导致死锁
MySQL中哪些命令会发生行锁
- SELECT ... FOR UPDATE :这种查询会对选定的行添加X锁(排它锁),这意味其他事务不能修改这些行,也不能加S锁
- SELECT ... LOCK IN SHARE MODE :会加S锁
- INSERT:插入操作会对新添加的行添加一个X锁
- UPDATE:更新操作会对被更新的行添加一个X锁
- DELETE:删除操作会对被删除的行添加一个X锁
Q:MySQL行锁有什么风险点
尽管行级锁可以提供高并发性并减少锁冲突,但在使用过程中也可能遇到一些风险和问题
- 死锁
- 锁升级:如果一个事务视图锁定的行过多,InnoDB肯呢个会将锁从行级升级为表级
- 锁等待
- 资源消耗
- 难以调试和排查
- 事务隔离级别
乐观锁和悲观锁
Q:什么是乐观锁
乐观锁是一种并发控制机制,它的核心思想是乐观地认为数据不会发生冲突,因此在大部分时间里不对数据进行加锁,而是在更新数据时检查数据是否被其他事务修改过。乐观锁通常适用于并发写入较少的场景,可以提高系统的并发性能。
在实现乐观锁时,一般会引入版本号(Version)或时间戳(Timestamp)等字段来标识数据的版本信息。当一个事务要更新数据时,首先会读取数据的版本信息,然后在写入数据时将版本信息一起提交。在提交时,系统会检查提交的版本信息与当前数据库中的版本信息是否一致,如果一致则表示数据未被其他事务修改,允许更新;如果不一致则表示数据已经被其他事务修改,此时需要根据具体业务场景选择合适的处理方式,比如放弃更新、重新读取数据后再次尝试更新等。
乐观锁的优点是不会引入额外的锁竞争,适用于读多写少的场景,并且可以减少数据库锁的使用,提高系统的并发性能。但是乐观锁也有一些缺点,比如在并发写入较多的场景下可能会导致更新冲突的频率较高,需要进行重试等,这会增加系统的复杂度。
总的来说,乐观锁在合适的场景下可以提供良好的性能和并发控制效果,但在具体应用时需要根据业务场景和性能需求进行综合考量。
Q:什么是悲观锁
悲观锁是一种并发控制机制,它的核心思想是在对数据进行操作之前先获取锁,以确保数据操作的独占性。悲观锁通常适用于并发写入较多的场景,通过加锁来避免数据的并发修改,确保数据的一致性。
在实现悲观锁时,一般会使用数据库提供的锁机制,比如行级锁或表级锁。当一个事务要对数据进行操作时,会先获取相应的锁,其他事务需要等待该锁释放后才能对数据进行操作。悲观锁的典型代表是数据库中的SELECT ... FOR UPDATE语句,它可以在读取数据的同时给数据行加上排他锁,阻止其他事务对该数据行进行修改。
悲观锁的优点是能够确保数据的一致性,避免了数据的并发修改,适用于高并发写入的场景。但是悲观锁也有一些缺点,比如可能会引起锁竞争和死锁问题,降低系统的并发性能;另外,长时间持有锁也会影响系统的吞吐量。
总的来说,悲观锁适用于对数据一致性要求较高的场景,但需要注意合理使用锁粒度和锁持有的时间,避免锁竞争和性能问题。在具体应用时,需要根据业务场景和性能需求进行综合考量,选择合适的并发控制策略。
Q:两种锁有哪些适用场景?
乐观锁适用于以下场景:
- 读多写少的场景:当系统中对数据的读操作远远多于写操作时,乐观锁可以有效地提高并发性能,因为大部分情况下数据不会发生冲突,无需频繁加锁。
- 数据冲突较少的场景:在业务逻辑上能够预测到数据冲突的概率较低的情况下,乐观锁可以减少加锁等待的时间,提高系统的吞吐量。
- 版本化控制:乐观锁通常会使用版本号或时间戳等方式来标识数据的版本信息,适用于需要对数据进行版本化控制的场景。
MyBatisPlus有乐观锁插件,在表字段中加入Version字段
java
@Version
@TableField(value = "version")
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
悲观锁适用于以下场景:
- 高并发写入的场景:当系统中对数据的写操作非常频繁时,为了确保数据的一致性和避免并发修改带来的问题,使用悲观锁可以有效地控制并发写入。
- 对数据一致性要求较高的场景:在某些业务场景下,对数据的一致性要求非常严格,不能容忍数据的并发修改或脏读现象,这时悲观锁可以提供有效的并发控制。
- 长事务场景:在需要长时间持有数据锁的业务操作中,使用悲观锁可以确保数据的独占性,避免其他事务的干扰。
总的来说,乐观锁适合于读多写少、数据冲突较少的场景,可以提高系统的并发性能;而悲观锁适合于高并发写入、数据一致性要求高以及长事务场景,可以确保数据的一致性和独占性。在实际应用中,需要根据具体业务场景和性能需求选择合适的并发控制策略。
sql
SELECT ... FOR UPDATE #排它锁 不可读不可更新
SELECT ... LOCK IN SHARE MODE #共享锁 可读不可更新
悲观锁虽然能够确保数据的一致性和避免并发修改的问题,但是它也存在一些缺点,包括:
- 性能影响:悲观锁在数据库层面对数据进行加锁,会导致其他事务在需要访问相同数据时被阻塞,从而降低系统的并发性能。
- 死锁风险:使用悲观锁时,如果事务在持有锁的过程中因为等待其他资源而被阻塞,可能会出现死锁的情况,导致系统无法继续进行下去。
- 资源占用:悲观锁通常需要在事务开始时就对数据进行加锁,并且在事务结束时才释放锁,这可能会导致对数据库资源的长时间占用,影响整体系统的吞吐量。
- 编程复杂性:使用悲观锁需要开发者自行管理锁的获取和释放,编写复杂,容易出现错误,并且增加了代码的复杂性和维护成本。
基于以上缺点,开发者在使用悲观锁时需要谨慎考虑,并且根据具体的业务场景和并发需求选择合适的并发控制策略。在一些情况下,可以考虑使用乐观锁、分布式锁等替代方案来解决并发控制的问题,以减少悲观锁所带来的性能和复杂性开销。
意向锁
- 在使用 InnoDB 引擎的表里对某些记录加上「共享锁」之前,需要先在表级别加上一个「意向共享锁」;
- 在使用 InnoDB 引擎的表里对某些纪录加上「独占锁」之前,需要先在表级别加上一个「意向独占锁」;
也就是,当执行插入、更新、删除操作,需要先对表加上「意向独占锁」,然后对该记录加独占锁。
而普通的 select 是不会加行级锁的,普通的 select 语句是利用 MVCC 实现一致性读,是无锁的。
不过,select 也是可以对记录加共享锁和独占锁的,具体方式如下:
sql
//先在表上加上意向共享锁,然后对读取的记录加共享锁
select ... lock in share mode;
//先表上加上意向独占锁,然后对读取的记录加独占锁
select ... for update;
意向共享锁和意向独占锁是表级锁,不会和行级的共享锁和独占锁发生冲突,而且意向锁之间也不会发生冲突,只会和共享表锁(*lock tables ... read*)和独占表锁(*lock tables ... write*)发生冲突。
表锁和行锁是满足读读共享、读写互斥、写写互斥的。
如果没有「意向锁」,那么加「独占表锁」时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。
那么有了「意向锁」,由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录。
所以,意向锁的目的是为了快速判断表里是否有记录被加锁。
就是,当我对整个表加锁时,lock tables your_table write,我不需要逐行判断是否加了行锁,因为表上又意向排它锁,直接互斥。
但是,共享锁和意向共享锁是不互斥的,都是读,读读不互斥。
临键锁(Next-Key Lock)
Q: 什么是临键锁?
Next-Key 可以理解为一种特殊的间隙锁,也可以理解为一种特殊的算法。通过临键锁可以解决幻读问题。每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事物持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的是,InnoDB中行级锁时基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列上不存在临键锁
age在(24,32]会锁住,当我们尝试:
就会阻塞,直到事务提交。
记录锁
在MySQL中,当一个事务对数据行进行修改时,会对这些数据行进行加锁,以确保操作的原子性和一致性。这种锁被称为记录锁(Record Lock)。记录锁是一种悲观锁,用于在事务中对数据行进行读取和修改时保护数据的完整性。
记录锁在MySQL中的工作方式如下:
- 当一个事务对某些数据行进行读取并希望在后续操作中修改这些行时,会为这些数据行添加记录锁。
- 如果另一个事务尝试对已被记录锁保护的数据行进行修改,那么该事务会被阻塞,直到持有记录锁的事务释放锁。
- 事务提交或回滚后,记录锁会自动释放。
需要注意的是,MySQL中的记录锁是基于行级别的,因此只会锁定涉及到的具体数据行,而不会锁定整个数据表。这样可以最大程度地减少并发操作的冲突,但也可能会导致大量的锁竞争和死锁问题,因此在使用记录锁时需要谨慎考虑并发访问的情况。
总之,记录锁是MySQL中用于保护数据行完整性的重要机制,但在实际应用中需要注意合理使用,避免造成性能问题和并发冲突。
间隙锁
在 MySQL 中,间隙锁(Gap Lock)是一种特殊的锁类型,用于防止其他事务在一个范围内插入新的记录,从而保证范围查询的一致性。间隙锁通常用于解决幻读(Phantom Read)的问题。
当一个事务执行范围查询(例如使用范围条件的 SELECT 语句)时,MySQL 可能会使用间隙锁来锁定范围内的间隙,而不仅仅是已经存在的行。这样做可以防止其他事务在这个范围内插入新的记录,确保了范围查询的一致性。
举个例子,如果一个事务执行如下的范围查询语句:
sqlCopy CodeSELECT * FROM your_table WHERE id > 100 FOR UPDATE;
那么MySQL可能会使用间隙锁来锁定所有 id 大于 100 的间隙,防止其他事务在这个范围内插入新的记录。这样可以避免在后续操作中出现新的符合范围条件的记录,导致幻读的问题。
需要注意的是,间隙锁可能会导致一些性能上的影响,因为它会在范围查询时锁定额外的间隙。因此,在使用范围查询和间隙锁时需要谨慎考虑,并确保它们符合业务需求。
总之,间隙锁在 MySQL 中是用来保证范围查询一致性的重要机制,但在实际应用中需要注意潜在的性能影响,并结合具体业务场景合理选择使用。
Q:间隙锁在哪里遇到过?
- 范围查询:在执行范围查询时,如果事务需要对查询结果进行更新或删除,那么间隙锁可以保证在事务执行期间,不会有新的行插入到范围查询中。
- 防止幻读:间隙锁的主要目的是防止其他事务在已经锁定的范围内插入新的行。这可以避免幻读问题。
- 性能影响:间隙锁可能导致范围查询的性能下降,因为它会在范围查询时额外锁定一些间隙,增加了锁竞争和并发访问的复杂度。这可能对系统的吞吐量和响应时间产生负面影响。
- 死锁风险:由于间隙锁的存在,事务之间可能会出现死锁的情况。特别是当多个事务同时操作同一个范围内的数据时,由于对间隙的锁定,可能会引发死锁,需要通过合理的事务设计和锁定策略来避免这种情况。
- 幻读问题:间隙锁的引入可以避免幻读(Phantom Read)问题,但在某些情况下,也可能会由于间隙锁的存在导致本不必要的范围查询失败,从而影响业务逻辑的正确性。
- 锁竞争:在高并发环境下,间隙锁可能会增加锁竞争的激烈程度,降低系统的并发性能,需要特别关注和优化。
综上所述,间隙锁在 MySQL 中的使用需要谨慎权衡,尤其是在高并发、大数据量的场景下,需要仔细评估其带来的潜在问题,并采取相应的优化措施来减轻其可能的负面影响。