面试官:小伙子你给我说说 MySql 并发事务处理细节
「 说完特性,再聊聊 MySql 中的几种事务隔离级别: 」
============================
RU 读未提交:
========
顾名思义,在这种隔离级别下,当多个事务并行对同一数据进行操作时,会读取未提交的数据,也被称之为?「 脏读 」?。
这种隔离级别因为会出现脏读现象,所以在实际场景中很少用。
RC 读提交:
=======
一个事务只能看见已经提交事务所做的改变。
但这种隔离级别会出现?「 不可重复读 」?现象,即在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
RR 可重复读:
========
这是 MySQL 的?「 默认事务隔离级别 」?,在这种隔离级别下,解决了 RC 存在的不可重复读问题,确保在同一事务中,会看到同样的数据行。
但可能会出现?「 幻读 」?,即当一个事务在执行读取操作,第一次查询数据总量后,另一个事务执行了新增数据的操作并提交后,这个时候第一个事务读取的数据总量和之前统计的不一样,就像产生幻觉一样。
SERIALIZABLE 串行化:
=================
此隔离级别是四种隔离级别中最高的级别,解决了?「 脏读、可重复读、幻读 」?的问题。
但是性能最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,在并行事务执行过程中,后一个事务的执行必须等待前一个事务结束。
MySql 中各种类型的锁:
=============
在 MySQL 中,按锁类型划分,有以下种类:
=====================
?
提到锁到种类,需要提一下 MySQL 到存储引擎,MySQL 常用引擎有?「 MyISAM 和 InnoDB 」,而 InnoDB 是 mysql 默认的引擎。MyISAM 是不支持行锁的,而 InnoDB 支持行锁和表锁。
?
MyISAM 存储引擎下表锁:
===============
MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁;
读锁会阻塞对同一张表的写操作,而写锁既会阻塞对同一张表的写操作,也会阻塞此表的读操作。
排他锁、共享锁、意向锁 是什么东东?
==================
「 排他锁: 」
========
通常我们在 InnoDB 存储引擎中对表执行一个更新操作,针对这一行数据会持有排他锁;
持有排他锁时,不允许再在数据行上添加写锁与读锁,其他事务对此行数据的读、写操作都会被阻塞,只有当前事务提交了,锁释放了才允许其他事务进行读写,达到避免?脏读?的效果。
「 共享锁: 」
========
主要是为了支持并发的读取数据而出现的,当一个事务持有某一数据行的共享锁时,允许其它事务可以再获取共享锁,但不允许其它事务在此基础上获取排他锁;
也就是说,在持有共享锁时,多个事务可以同时读取当前数据,但是不允许任何事务同时对当前数据进行修改操作,阻塞添加排它锁。
「 意向锁: 」
========
首先需要明白一点,意向锁的作用是在?表上?的,当一个事务需要获取共享锁或排他锁时,首先要获取对应的意向锁;
为什么要这样做呢?举个例子,假设在事务 A 中,对某一行数据添加共享锁,这一行只能读,不能写;此时事务 B 申请获得表的写锁,假如加锁成功,那么事务 B 将能够对整个表的数据进行读写,与事务 A 冲突,这种操作肯定是不允许的呢;
所以 MySQL 会在申请共享锁或者排他锁的时候,先获取对应的意向锁,也就是说,你要操作表中的某一行锁数据,先要看看整个表能不能被操作;意向锁的申请是由数据库完成的,不需要人为申请。
Innodb 存储引擎下的行锁:
================
?
上文对几种锁类型进行了简要分析,其实平时工作开发中接触到最多的还是行锁,行锁的实现有以下几种:
注意:在 Innodb 存储引擎中,行锁的实现是基于索引的
?
「 Record Lock(记录锁): 」
=====================
它是会锁住索引记录,比如 update table where id = 1, id 是主键,然后在聚簇索引上对 id=1 的个索引记录进行加锁;
「 Gap Lock(间隙锁): 」
==================
实质上是对索引前后的间隙上锁,不对索引本身上锁,目的是为了防止幻读。
当使用范围条件查询而不是等值条件检索数据,并请求排他锁、或共享锁时,对于该范围内不存在的记录,不允许其修改插入。
举个例子:当表中只有一条 id=101 的记录,一个事务执行 select * from user where user_id > 100 for update;此时另一个事务执行插入一条 id=102 的数据是会阻塞的,必须等待第一个事务提交后才能完成。
间隙锁是针对事务隔离级别为可重复读或以上级别的。
「 Next-Key Lock: 」
==================
Next-Key Lock 是 记录锁和间隙锁 的结合,会同时锁住记录与间隙。
在 innodb 存储引擎中,如果没有通过 索引项 进行查询时:
①、在 RR 隔离级别下,会以 Next-Key Lock 的方式对数据行进行加锁,通过 行锁+间隙锁 实现了 "锁表" 的效果,但请记住这不是添加的表锁;
②、而在 RU、RC 隔离级别下还是只会锁行记录,为什么呢?因为在 innodb 存储引擎下的四种事务隔离级别中都支持行锁,但是间隙锁只存在于 RR、Serializable 两种隔离级别下。
可以通过下面这篇文章了解为什么在 RR 隔离级别下会实现"锁表"的效果,而在 RC 隔离级别下只会锁行记录: 互联网项目中 mysql 应该选什么事务隔离级别
MVCC 是什么:
=========
?
锁机制可以控制并发操作,来保证一致性,但是系统开销会很大;在 RC、RR 的隔离级别下,MySQL 的 InnoDB 存储引擎通过?MVCC (多版本并发控制)?机制来解决幻读。
?
「 使用 MVCC 时具体的体现是什么呢? 」
=====================
使事务在并发过程中,SELECT 操作不用加锁,读写不冲突从而提高性能。
「 那么实现 MVCC 机制的原理是什么呢? 」
======================
其原理是通过保存数据在某个时间点的快照来实现的;通过在每行记录后面保存隐藏列来存放事务 ID,这样每一个事务,都会对应一个递增的事务 ID。
假设三个事务同时更新来同一行数据,那么就会对应三个数据版本,但实际上版本 1、版本 2 并不是物理存在的,而是通过关联记录在 undo log 回滚日志中,这样就可以通过 undo log 找回数据的历史版本,比如回滚的操作,会使用上一个版本的数据覆盖数据页上的数据。
「 举例一个 RR 隔离级别下快照读的例子: 」
======================
开启事务 A 按条件 A 查询到两条数据,此时事务 B 再插入 1 条数据且满足条件 A 的数据,并提交事务;
此时事务 A 再按条件 A 进行查询,查询到的依然是两条数据,也就是说,事务 A 查询到的并不是当前最新的数据版本(三条数据),而是
通过 MVCC 实现的历史快照版本;这也是可重复读的实现。
「 介绍了完读操作,再举例一个 RR 隔离级别下 更新 写操作的例子: 」
===================================
注意:对数据修改的操作(update、insert、delete)都会读到已提交事务的最新数据,因为这就是 当前读。
假设事务 A 执行一个更新语句,满足更新条件 A(条件 A 字段无索引或者存在非唯一索引)的数据是 2 条,更新成功后不提交事务;
此时事务 B 插入一条新的满足条件 A 的数据时会被阻塞的,为什么呢?
因为这里在事务 A 更新时使用到了 Next-Key Lock 锁,它会使用行锁+间隙锁实现了"锁表",所以后面事务 B 再进行插入数据时会被阻塞的;这也防止了幻读的出现。
这里如果看的不是很明白的话,可以同时再参考下此文章,此文章详细分析了加锁情况: 惊!史上最全的 select 加锁分析(Mysql)
注意:
MVCC 只在 RC 和 RR 两个隔离级别下支持;其他两个隔离级别和 MVCC 不兼容,因为 RU 总是读取最新的数据行,而不是符合当前事务版本的数据行;而 S ERIALIZABLE 则会对所有读取的行都加锁,是当前读,也是读取的最新数据。
数据库锁的触发及升级,以及死锁:
================
数据库锁的触发及升级:
===========
什么时候会出现 DeadLock:
================
「 什么是死锁呢? 」
===========
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
评论