MySQL锁
锁的意义:
用来解决并发事务的访问问题,事务并发执行时可能带来的各种问题,最大的一个难点是:一方面要最大程度地利用数据库的并发访问,另外一方面还要确保每个用户能以一致的方式读取和修改数据,尤其是一个事务进行读取操作,另一个同时进行改动操作的情况下。
一个事务进行读取操作,另一个进行改动操作,这种情况下可能发生脏读、不可重复读、幻读的问题。怎么解决脏读、不可重复读、幻读这些问题呢?其实有两种可选的解决方案:
- 读操作MVCC,写操作进行加锁:该方案性能较好,但可能会读到旧版本记录
- 读写操作都加锁:该方案性能一般,但是每次都可以读取到最新的记录,比如在银行场景中,对安全性要求非常高
乐观和悲观锁
悲观锁:每次去拿数据的时候都认为别人会修改,每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。悲观锁通过共享锁和排他锁实现,适用于并发量不大,写多读少的场景
乐观锁:用数据版本(Version)记录机制实现,为数据增加一个版本标识,增加一个数字类型的 version 字段来实现。当读取数据时,将 version 字段的值一同读出,数据每更新一次,对此 version 值加 1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的 version 值相等,则予以更新,否则认为是过期数据。
乐观锁有什么优点和缺点:因为没有加锁所以乐观锁的优点就是执行性能高。它的缺点就是有可能产生 ABA 的问题,ABA 问题指的是有一个变量 V 初次读取的时候是 A 值,并且在准备赋值的时候检查到它仍然是 A 值,会误以为没有被修改会正常的执行修改操作,实际上这段时间它的值可能被改了其他值,之后又改回为 A 值,这个问题被称为 ABA 问题。乐观锁适用于读多写少的场景
共享锁排它锁
- 共享锁:
- 读锁/S 锁。当事务对数据加上读锁后,其他事务只能对该数据加读锁,不能加写锁。
- 共享锁加锁方法:select …lock in share mode
- 排他锁:
- 写锁/X 锁,当事务对数据加上排他锁后,其他事务无法对该数据进行查询或者修改
- InnoDB引擎默认增删改都会自动给涉及到的数据加上排他锁,select 语句默认不会加任何锁类型,排他锁加锁方式:select …for update
全局锁:对整个数据库实例加锁,当需要让整个库处于只读状态,可使用这个命令加全局读锁。典型使用场景是:全库逻辑备份,避免系统备份得到的库与主库不是同一个逻辑时间点,数据不一致。命令是:Flush tables with read lock (FTWRL)
表级锁和页锁
表级锁:
表锁和元数据锁(MDL),支持表级锁的引擎有MyISAM、MEMORY、CSV等。
表锁:显示加锁,unlock tables主动释放锁,客户端断开时也会主动释放。
- 读锁:不阻塞读,会阻塞写。lock tables xxx read local
- 写锁:会阻塞其他的读写操作。lock tables xxx write
注意:lock tables会限制别的线程读写,也会限制本线程接下来的操作对象。如果A线程执行lock table t1 read,t2 write。则其他线程写t1,读t2都会被阻塞,同时,线程A在释放表锁之前,也只能执行读t1,读写t2的操作,写t1也不行,也不能访问其他表。
MDL:自动加锁,用来保证DDL操作与DML操作之间的一致性,分为两类:
- MDL读锁:对表增删改查时加读锁
- MDL写锁:对表结构修改时加写锁
读锁之间不互斥,保证多线程操作一张表的数据;写锁之间互斥,多线程更改表结构操作的安全性;读写锁互斥。
注意:事务中MDL锁,在语句执行开始时申请,但在语句结束后不会立即释放,而是等到整个事务提交后再释放。在DML与DDL操作交互时,如果客户端有查询重试机制,就容易产生session爆满,导致内存增高。
例子:sessionA对表t1进行查询,此时表t1会加MDL读锁,此时sessionC发起DDL操作请求,但A的MDL读锁未释放,导致C申请MDL写锁被阻塞,后面如果有其他的session发起查询申请MDL读锁,都会被堵塞,当客户端发起重试时,查询过多。会导致大量session被阻塞,导致内存升高。
页级锁:页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。因此,采取了折衷的页级锁,一次锁定相邻的一组记录。BDB 引擎支持页级锁
行锁和意向锁
行锁(记录锁):
粒度最小的一种锁,发生锁冲突概率最低,但是加锁慢,开销大,MyISAM不支持行锁。行级锁并不是之间锁记录,而是锁的索引
- 读锁:根据索引条件查询自动加锁,前提是当前线程没有对该结果集中的任何行使用写锁,否则申请会阻塞。
- 写锁:对记录的更新与删除操作会自动加上排他锁。前提是当前没有线程对该结果集中的任何行使用X/S锁,否则申请会阻塞。查询显示加锁:select * from user where id = 1 for update; 在InnoDB事务中,行锁是在需要的时候才加上,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。因此当遇到事务中需要锁多个行时,要把最可能造成冲突、最可能影响并发度的锁尽量往后放。
InnoDB的行锁特性:
- 不通过索引条件查询时,InnoDB使用表锁非行锁
- 行锁是针对索引加锁,而不是针对记录。即使访问不同行的记录,如果使用了相同的索引键,也会有锁冲突
- 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
意向锁:属于表锁,只与表锁冲突,不与行锁冲突。为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存
作用:当有事务A有行锁时,MySQL会自动为该表添加意向锁,事务B如果想申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁,而直接判断是否存在意向锁,增强性能。
间隙锁、临键锁、记录锁
记录锁:锁定索引中单条具体记录,解决最基本的并发修改冲突,防止两个事务修改同一条记录。只锁定记录本身,不影响其他记录的插入和修改。
间隙锁:锁定索引记录之间的间隙(不包括记录本身),防止其他事务在范围内插入新数据。只在RR隔离级别生效,解决幻读的关键,即使间隙中不存在的数据也会被锁定。唯一索引等值查记录存在时退化为记录锁,普通索引必带间隙锁,复合唯一索引覆盖所以列且记录存在则记录锁,否则间隙锁。
临键锁:记录锁+间隙锁组合,锁定索引记录及该记录之间的间隙,是InnoDB默认的行锁实现方式。左开右闭,同时防止幻读和保证当前记录不被修改。
降级为记录锁的条件:仅当同时满足,唯一索引+等值查询+查询条件匹配到确切记录
本文来自博客园,作者:难得,转载请注明原文链接:https://www.cnblogs.com/zhangbLearn/p/18829170

浙公网安备 33010602011771号