零、mysql锁机制
-
数据库锁机制简单来说,就是数据库为了保证数据的一致性,使各种
共享资源
在被访问时变得有序而设计
的一种规则! -
MysQL的锁机制比较简单最著的特点是不同的存储引擎支持不同的锁机制。InoDB支持行锁(有时也会升级为表锁);MyISAM只支持表锁;
行锁:锁数据的一行,开销小、加锁快、不会出现死锁。锁粒度大,发生锁冲突的概率小,并发相对较低。
表锁:锁整个表,开销大、加锁慢、会出现死锁。锁粒度小,发生锁冲突的概率高,并发相对较高
一、InnoDB行锁的种类
InnoDB默认的事务隔离级别是RR(可重复读),并且参数innob_locks_unsafe_for_binling=0的模式下,行锁有三种。
1.1、记录锁、(Record Lock)
把这条记录加锁:记录锁
行锁是加在索引上的,有索引,行锁才会生效。
测试:不加索引,两个事务修改同一条记录
测试:不加索引,两个事务修改不同行的记录
测试:加索引,修改同一行记录
测试:加索引,修改不同行的记录
1.2、间隙锁(GAP Lock)
在RR这个级别下
,为了避免幻读,引入了间隙锁,他锁定的是记录范围,不包含记录本身,也就是不允许在范围内插数据。
间隙:根据检索条件向下寻找最靠近检索条件的记录值A作为左区间,向上寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)。
注意:唯一索引 等值判断只会产生记录锁,范围查询会产生间隙锁
注意:非唯一索引 等值判断也会产生间隙锁,因为查出来可能会有多条数据
1.3、记录锁和间隙锁的组合(next-key-lock)
临键锁,是记录锁与间隙锁的组合,它的*范围,既包含索引记录,又包含索引区间。
注:临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。
二、表锁
1、对于InnoDB表, 在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个另特殊事务中,也可以考虑使用表级锁。
-
第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。(有300W数据,150W条需要修改,直接上表锁,因为行锁需要锁150W次)
-
第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,j造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
三、InnoDB的锁类型
3.1、读锁
读锁(共享锁,shared lock)简称S锁。一个事务获取了一个数据行的读锁,其他事务能获得该行对应的读锁但不能获得写锁,即一个事务在读取一个数据行时,其他事务也可以读,但不能对该数行增删改的操作。(简而言之:多个事务可以读,只能一个事务写)
读锁有两种select方式的应用:
1.第一种是自动提交模式下的select查询语句,不需加任何锁,直接返回查询结果,这就是一致性非锁定读。
⒉第二种就是通过select.lock in share mode被读取的行记录或行记录的范围上加一个读锁让其他事务可以读,但是要想申请加写锁那就会被阻塞。
3.2、写锁
写锁,也叫排他锁,或者叫独占所,简称x锁。一个事务获取了一个数据行的写锁,其他事务就不能再获取该行的其他锁与锁优先级最高。(简而言之:就是只能有一个事务操作这个数据,别的事务不能同时和它操作)
3.3、MDL锁(meta data lock)
- 什么叫元数据:表结构信息;
表开启了查询事务后,就会默认上一个MDL锁,上了 MDL 锁 ,就不能修改表结构。
3.4、意向锁
在mysql的innob引擎中,意向锁是表级锁,意向锁有两种:
-
意向共享锁(IS)是指在给一个数据行加共享锁前必须获取该表的意向共享锁(维护表结构)
-
意向排它锁(IX)是指在给一个数据行加排他锁前必须获取该表的意向排他锁
-
意向锁和MIDL锁都是为了防止在事务进行中,执行DDL语句导致数据不一致。
四、从另一个角度区分锁的分配
4.1、乐观锁(需要自己手动实现的)
乐观锁大多是基于数据版本记录机制实现,一般是给数据库表增加一个"version"字段。读取数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
4.2、悲观锁(前面讲的锁都是悲观锁)
不保护必出问题。
悲观锁依靠数据库提供的锁机制实现。MySQL中的共享锁和排它锁都是悲观锁。数据库的增删改操作默认都会加排他锁,而查询不会加任何锁。此处不赘述。
五、锁等待和死锁
5.1、锁等待:
一个事务加锁修改表数据,锁表,另一个事务得等待:锁等待;
可以设置锁等待时间的,超过的话,就会自动结束。默认50S,
5.2、死锁:
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,就是所谓的锁资源请求产生了回路现象,即死循环。
InnoDB引擎可以自动检测死锁并回滚该事务好不容易执行了一个业务给我回滚了,所以死锁尽量不要出现。
六、如何避免死锁
-
出现死锁不可怕,但我们要尽量避免死锁
-
如果不同的程序会并发的处理同一张表,或者涉及表中多行记录,尽量约定使用相同顺序访问表,可以大大减少死锁概率的发生
-
业务中采用小事务,避免使用大事务,要及时提交和回滚事务,可以减少死锁的发生
-
同一个事务中尽量做到一次锁定所需要的所有资源(表锁),减少死锁发生的概率。
-
对于非常容易发生死锁的业务,可以尝试使用升级锁的力度,该用表锁减少死锁的发生
七、锁总结
行锁,记录锁,间隙锁,临建锁。
mdl锁 > 查询数据一开始,不能修改表结构
意向锁 > 获取锁前要先获取意向锁,不能修改表结构
乐观锁 > 无锁,version控制
悲观锁 > mysql都是悲观锁 上锁
读锁(共享锁、S锁),
写锁(独占锁、排它锁、互斥锁、X锁)
八、多版本并发控制(MVCC)
MVCC(Mutil-Version Concurrency Control),就是多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。
重要:版本链
8.1、什么是当前读和快照读?
8.1.1、当前读
当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
当前读是基于 临键锁(行锁 + 间歇锁)来实现的,适用于 insert,update,delete, select ... for update, select ... lock in share mode 语句,以及加锁了的 select 语句。
8.2.2、快照读
快照读,读取的是快照数据,不加锁的简单 Select 都属于快照读.
在Read Committed和Repeatable Read隔离级别下,普通的SELECT查询都是读取MVCC版本链中的一个版本,相当于读取一个快照,因此称为快照读。这种读取方式不会加锁,因此读操作时非阻塞的,因此也叫非阻塞读。
在标准的Repeatable Read隔离级别下读操作会加S锁,直到事务结束,因此可以阻止其他事务的写操作;但在MySQL的Repeatable Read隔离级别下读操作没有加锁,不会阻止其他事务对相同记录的写操作,因此在后续进行写操作时就有可能写入基于版本链中的旧数据计算得到的结果,这就导致了提交覆盖的问题。想要避免此问题,就需要另外加锁来实现。
8.2、当前读、快照读和MVCC的关系
-
准确的说,MVCC多版本并发控制指的是“维护一个数据的多个版本,使得读-写操作没有冲突”这么一个概念,仅仅是一个理想概念。
-
而在mysql中,实现这么一个理想概念,我们就需要mysql提供具体的功能去实现它,而快照读就是mysql为我们实现MVCC理想模型中的一个非阻塞式读功能。而相对而言,当前读就是悲观锁的具体功能实现
8.3、MVCC解决什么问题
** 数据库并发有三种场景,分别是:**
- 读-读:不存在线程安全,也不需要版本控制
- 读-写:有线程安全,可能会造成事务的隔离性的问题,可能造成脏读、幻读、不可重复读。
- 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失或者第二类更新丢失。
** MVCC带来的好处:**
MVCC是一种用来解决读写冲突
的无锁版本控制,也就是为事务分配单项增长的时间戳,为每一次修改给定一个版本,并且通过指针去指向,读操作只读事务开始前的数据库快照,所以MVCC可以为数据库解决一下问题:
-
读-写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,读不相互阻塞,写不阻塞读,这样可以提升数据并发处理能力。
-
降低了死锁的概率,这个是因为 MVCC 采用了乐观锁的方式,读取数据时,不需要加锁,写操作,只需要锁定必要的行。
-
解决了一致性读的问题,当我们朝向某个数据库在时间点的快照是,只能看到这个时间点之前事务提交更新的结果,不能看到时间点之后事务提交的更新结果。
小结一下:
总之,MVCC就是因为大牛们,不满意只让数据库采用悲观锁这样性能不加的形式去解决读写冲突,而提出的解决方案,所以在数据库中,因为有了MVCC,我们有了一下两个组合:
- MVCC + 悲观锁
MVCC用来解决读-写冲突,悲观锁用来解决 写-写冲突
- MVCC + 乐观锁
MVCC用来解决读-写冲突,乐观锁用来解决 写-写冲突
这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题
8.4、MVCC实现原理
MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的3个隐式字段,undo日志(回滚记录日志),Read View来实现的
。所以我们先来看看这个三个point的概念
8.4.1、3个隐式字段
8.4.2、用途
8.4.2、undo日志
记录旧的数据链,版本选择,帮助我们实现快照读, 实现回滚,如上图↑
8.5、Read View(读视图)
什么是Read View,说白了Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)
九、Redo log 日志
记录状态的:
比如 我把一些缓存中数据存储到磁盘里面了,突然停电啦,我的数据就没有啦吗?不是的,首先我们会先把数据写到redo log日志里面,然后再去修改缓冲池里面的数据,最后在写会磁盘,记录我那时候的状态,用于恢复。
不恰当的比喻>>>>>>>>>
将数据加载到内存进行修改,修改完之后不立马刷回到磁盘,先赞一赞,如果此时电脑重启了,这时就需要redo log日志,来处理了,因为它记录了状态。
9.1、redo log 工作原理
说白了,redo log就是存储了数据被修改后的值。当我们提交一个事务时,lnnoDB会先去把要修改的数据写入日志,然后再去修改缓冲池里面的真正数据页。
我们着重看看redo log是怎么一步步写入磁盘的。redo log本身也由两部分所构成即重做日志缓冲(redo log buffer、内存)和重做日志文件(redolog file、磁盘)。这样的设计同样也是为了调和内存与磁盘的速度差异。InnoDB写入磁盘的策略可以通过innodb_f lush_log_at_trx_commit这个参数来控制。
9.2、宕机恢复
DB宕机后重启,InnoDB会首先去查看数据页中的LSN的数值。这个值代表数据页被刷新回磁盘的LSN的大小。然后再去查看redo log的LSN的大小。如果数据页中的LSN值大说明数据页领先于redo log刷新回磁盘,不需要进行恢复。反之需要从redo log中恢复数据。