做者:z小赵web
★一枚用心坚持写原创的“无趣”程序猿,在自身受益的同时也让朋友们在技术上有所提高。面试
目录
-
为何须要锁? -
MySQL 中锁分类? -
什么是事务? -
事务的隔离级别 -
MySQL 是怎么实现事务机制的? -
MVCC 机制 -
总结
为何须要锁?
相信你们都比较熟悉电商系统中库存管理的场景,对于平常活动促销、61八、双 11 等场景,会在规定时间内对商品进行促销活动,假设如今有一款 HHKB 机械键盘要参与促销活动,数据库中准备了 10 件,促销活动开始时,多位买家开始争抢,每卖出一件商品,库存减 1,直到卖完,那么怎么能保证商品不会卖超呢?数据库
对于以上这个场景来讲,咱们须要用到锁机制来保证每卖出一件商品,对库存进行更新操做时,其余用户请求不能对该商品库存进行修改;换句话说,用户 1 拿到了修改库存的锁,则只有用户 1 能修改数据,而用户 2 只能等着不能修改数据。以下图所示:缓存
相反,若是没有锁的加持,用户 1 和用户 2 发现库存还有 1 件商品,同时都开始下单,用户 1 先将库存更新为 0,此时商品已经售完,而用户 2 也将库存更新为 0,就致使了卖超的尴尬状况。微信
MySQL 中锁分类?
锁根据使用场景不一样,被分红了各类各样的锁。好比读写能够分为读锁和写锁,对于读请求之间相互是互不影响的,由于数据没有被全部,你们读取到的数据都是同样的,因此读锁也称之为共享锁;对于写请求,因为存在数据的变动,因此请求之间是互斥的,因此也称之为排它锁。并发
对于根据锁锁定的范围大小,能够分为全局锁、表锁、元数据锁、行锁:app
-
全局锁:顾名思义就是对整个数据库进行加锁操做,加锁期间,整个数据库只可以进行读操做。 -
表锁:是对数据库中的某张表进行加锁,此时表与表之间能够同时进行写操做而互不影响,可是同一时刻 同一张表只能有一个写操做。 -
页面锁:页面锁是介于表锁和行锁之间的一种锁,其优点是中和表锁和行锁的锁开销。 -
行锁:是对数据库表中的某一行进行加锁操做。
从上也可以看出,锁的范围是逐渐减少的,在实际生产环境中须要根据业务场景来选择不一样粒度的锁。编辑器
什么是事务?
由多个事件组成的一组动做,要么同时成功,要么发生失败时全体进行回滚;换句话说就是多个原子操做合并为一个原子操做。怎么可以保证一个事务可以正确执行呢?想要保证一个事务正确执行的依据是其必须知足 ACID,即原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)。flex
举个例子:初始状态 A 帐户有 100 块,B 帐户有 100 块。atom
-
原子性:一个事务或执行动做不能被分割为多个阶段去处理,因此整个执行流程要么所有成功,要么失败 所有回滚。举例:A 向 B 转帐 100 块,A 的帐户变成 0,B 的帐户变成 200,两个帐户的钱增减总体视为一个动做。 -
一致性:一个状态在经历一些动做以后转变成另一个状态。举例:A 向 B 转帐 100 块,通过 A 向 B 的一个转帐动做而且执行成功时,A 的帐户变成 0,B 的帐户变成 200 块,执行转帐动做先后帐户的总金额没有发生改变。 -
隔离性:多个事务在并发操做时,相互之间不可以看到对方执行的动做,即相互之间是隔离的。隔离也分为多个级别,不一样的隔离级别所保证的隔离程度也是不同的。 -
持久性:一个事务一旦提交之后,其对数据源的修改是永久性的保存到了数据库中。如何作到 100%的持久性是一个几乎不太可能实现的事情,好比数据虽然持久化到了磁盘上,但因为一些不可抗拒的外力因素致使数据发生了丢失,因此在实际状况中须要规定一个持久性的级别,即认为只须要数据持久化到磁盘上就能够认为达到了持久性的特性。
事务的隔离级别
MySQL 的事务隔离级别分为一下几种:
举个例子:初始状态 A 帐户有 100 块,B 帐户有 100 块。
-
读未提交(Read Uncommitted):在一个事务中,部分提交后对其余事务可见。举例:A 转帐给 B,A 的帐户金额变成 0,B 的帐户还未增长到 200,A 读取本身的帐户时,发现本身的帐户金额变为 0,可是因为事务执行中途出现故障(假设 B 帐户由于某种缘由帐户被锁定不能被转帐),此时事务进行了回滚操做,那么就会致使 A 读取到帐户金额是错误的。这种现象也称之为 脏读。 -
读已提交(Read Committed):读操做只可以读取到本身事务中的数据或者是事务提交后的结果。举例:转帐操做,若是一个请求读取帐户 A 的金额,若是事务正常提交了,则其读取的帐户金额必定是 0,若是事务回滚,则读取到的金额必定是 100。可是读已提交不可以避免重复读,有可能两次读取到结果不一致。 -
可重复读(Repeatable Read):该级别可以保证屡次读取到的结果是相同的,可是不可以解决 幻读的状况。幻读在数据库中具体的体现是范围查询,好比第一次一个范围查询到的结果集的同时,另一个事务插入了数据,第二次查询的结果和第一次查询的结果集不同。 -
串行化(Serializable):强制全部事务按照顺序依次执行,只有一个事务执行完成后,下一个事务才能接着执行。这样就可以避免产生脏读、不可重复读、幻读等状况,可是同时也下降了数据库的并发度。
在实际开发场景中,一样须要根据实际业务场景来选择合适的隔离级别,通常用的比较广泛的两种隔离级别是读已提交和可重复读。
MySQL 是怎么实现事务机制的?
MySQL 中实现了事务机制的常见存储引擎有 InnoDB 和 NDB(上篇文章也介绍过,本系列全程以 InnoDB 展开介绍)。使用 InnoDB 提交事务的时候,首先须要关闭自动提交,经过 set autocommit = 0
命令关闭自动提交功能,而后经过 start transaction
开启一个事务,接着编写在事务内要执行的 SQL,最后经过 commit
提交事务。
若是两个事务同时提交,而且都须要操做同一个资源,此时会产生 死锁,那么 MySQL 是怎么处理这样状况的呢?InnoDB 采用的是将持有最少行级排他锁进行回滚操做,什么叫持有最少行级排排它锁?大白话解释一下:就是每一个事务开启后,须要执行增删改等操做 SQL 的个数。好比有两个事务,一个须要执行 2 个 select 语句和 3 个 update 语句,另一个须要执行 1 个 select 语句和 1 个 update 语句,当两个事务的 update 同一时刻须要对方锁住的资源时,会将后者的事务进行回滚操做。
MVCC 机制
为了下降死锁状况的发生,MySQL 引入录入 MVCC 机制,从而来进一步下降由于加减锁而形成的系统开销。经过在数据表上增长两个隐藏列,一个列用于存储当前行的建立时的版本,另一个列用于存储当前行的删除时的版本。下面咱们来看看 MVCC 机制在 CRUD 中是怎么工做的。
-
SELECT 操做(由两个条件决定): -
InnoDB 会查找建立行的版本小于等于当前事务版本的数据行。好比目前数据表里的第一行如今有两个版本,分别为 1 和 2,而当前事务的版本号是 1,则此时会选择版本号为 1 的这一行. -
InnoDB 会查找删除行的版本大于当前事务版本的数据行。好比目前数据表里的第一行有两个版本,分别为 1 和 2,可是版本号为 1 的那行的删除列标记为 0,而当前书屋的版本号为 1,则此时会选择版本号为 1 的这一行。
当同时知足以上两个条件的时候,select 语句就能够获得一条最新的已提交的且未被删除的行记录。
-
UPDATE 操做:新插入一条数据到表中,而且将建立列的版本号设置为当前事务的版本号,而且将当前事务的版本号保存到原来记录的删除列上。
-
INSERT 操做:新插入一条数据到表中,而且将建立列的版本号设置为当前的事务版本号。
-
DELETE 操做:会将要删除的行的历史全部版本号所有设置上当前的事务版本号。
经过 CRUD 四种操做能够看出,MVCC 机制只能做用于 读已提交 和 可重复读 两个事务隔离级别下,由于读未提交会使得 SELECT 产生脏读,而串行化自己已是顺序操做了,没有增长 MVCC 机制的必要。
至于 MVCC 的具体实现细节,它结合了 undo log 来实现的,咱们在后面讲解 MySQL 的相关日志文件功能的时候再详细展开。
总结
本文介绍了 MySQL 的锁机制和事务的概念及实现原理。下篇文章咱们来接着研究MySQL 的索引机制,看看它到底该怎么用才能使得查询操做变得更加高效,敬请期待。
z小赵
关注系列优秀文章不走丢
进群的小伙伴请加右侧私人微信(备注:进群)
本文分享自微信公众号 - z小赵(Id_Bean)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。