Mysql的几个灵魂拷问(三)

今天这篇就来讲讲Mysql中比较高频的锁和事务吧。

一、Mysql锁事

1、锁的类型有哪些呢

总的来说,InnoDB共有七种类型的锁:

  • 共享/排它锁(Shared and Exclusive Locks)
  • 意向锁(Intention Locks)
  • 记录锁(Record Locks)
  • 间隙锁(Gap Locks)
  • 临键锁(Next-key Locks)
  • 插入意向锁(Insert Intention Locks)
  • 自增锁(Auto-inc Locks)

要记住这么多类型的锁不容易,但是总结一下来看,可以按照几个维度将锁分做一个分类,按照锁的粒度分类,分为表级锁和行级锁,

  • 表级锁,粒度最大的一种锁,对当前操作的整张表加锁,实现简单 ,资源消耗也比较少,加锁快,不会出现死锁 。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。
  • 行级锁,Mysql中锁定 粒度最小的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。 InnoDB支持的行级锁。

虽然使用行级索具有粒度小、并发度高等特点,但是表级锁有时候也是非常必要的:事务更新大表中的大部分数据直接使用表级锁效率更高;事务比较复杂,使用行级索很可能引起死锁导致回滚。

锁按照是否互斥划分为 共享锁(Share Locks,记为S锁)和排他锁(eXclusive Locks,记为X锁)。

  • 共享锁(Share Locks,记为S锁),读取数据时加S锁,共享锁之间不互斥,也就是读读可以并行
  • 排他锁(eXclusive Locks,记为X锁),修改数据时加X锁,排他锁与任何锁互斥——也就是写读,写写不可以并行

2、InnoDB的行锁模式和加锁方法

InnoDB的行锁模式就是分为如下的两种:

  • 共享锁(Share Locks,记为S锁),读取数据时加S锁
  • 排他锁(eXclusive Locks,记为X锁),修改数据时加X锁

为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks):意向共享锁(IS),意向排他锁(IX)。这两种意向锁都是表锁。 (意向锁了解即可)

InnoDB行锁模式兼容性列表:

意向锁是InnoDB自动加的;对于UPDATE、DELETE和INSERT语句,InnoDB会自动给设计数据集加排他锁(X);对于普通的SELECT语句,InnoDB不会加锁。 可以通过以下语句显示给记录集加共享锁或排他锁:

共享锁(S):SELECT * FROM TABLE_NAME WHERE ... LOCK IN SHARE MODE.
排他锁(X):SELECT * FROM TABLE_NAME WHERE ... FOR UPDATE.
以上使用完后记得要提交事务。

SELECT ... IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE方式获得排他锁。

所以在使用共享锁模式下,查询完数据后不要进行更新操作,不然又可能会造成死锁;要更新数据,应该使用排他锁模式。mysql锁机制详解

InnoDB的行锁原理与使用

InnoDB行锁是通过给索引上的索引项加锁来实现的,这意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!(这个问题遇到过,由于没加索引,行锁变表锁)

实际使用案例,此处就是用的行锁来实现一个事务更新操作。

//0.开始事务
begin;
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;

关于行锁的使用和注意可以看看这篇文章。你知道吗?使用 MySQL 中InnoDB行锁需要避免这些坑?

锁与事务

依赖锁的标准事务

标准SQL事务隔离级别的实现是依赖锁的,但是Mysql在只使用锁来实现隔离级别的控制的时候,需要频繁的加锁解锁,而且很容易发生读写的冲突。为了不加锁解决读写冲突的问题,MySQL引入了MVCC机制,详细可见:微信文章深入理解 MySQL 中事务隔离级别的实现原理

二、Mysql事务


事务的基本特性

  • 原子性指的是一个事务中的操作要么全部成功,要么全部失败。
  • 一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设中间sql执行过程中系统崩溃A也不会损失100块,因为事务没有提交,修改也就不会保存到数据库。
  • 隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
  • 持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。

事务的隔离级别

而隔离性有4个隔离级别,分别是:

  • read uncommit 读未提交,可能会读到其他事务未提交的数据,也叫做脏读。
  • read commit 读已提交,两次读取结果不一致,叫做不可重复读。
  • repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
  • serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。
事务隔离级别

更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题 —— 最后的更新覆盖了其他事务所做的更新。如何避免这个问题呢,最好在一个事务对数据进行更改但还未提交时,其他事务不能访问修改同一个数据。

脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些尚未提交的脏数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做 “脏读”。

不可重复读(Non-Repeatable Reads):一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”。

幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为 “幻读”。

隔离线和问题

ACID靠什么保证

  • A 原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
  • C 一致性一般由代码层面来保证
  • I 隔离性由MVCC来保证
  • D 持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复

快照读、当前读?

快照读,读取的是快照数据,不加锁的简单 Select 都属于快照读.

SELECT * FROM player WHERE ...

当前读就是读的是最新数据,而不是历史的数据,加锁的 SELECT,或者对数据进行增删改都会进行当前读。

SELECT FROM player FOR UPDATE;
INSERT INTO player values ...
DELETE FROM player WHERE ...
UPDATE player SET ...

什么是MVCC?

解决更新丢失可以交给应用,但是后三者问题的解决需要数据库提供事务间的隔离机制来解决。实现隔离机制的方法主要有两种:加读写锁、MVCC

MVCC 的英文全称是 Multiversion Concurrency Control ,中文意思是多版本并发控制技术。简单来说就是保存数据的历史版本。可以通过比较版本号决定数据是否显示出来。读取数据的时候不需要加锁可以保证事务的隔离效果(乐观锁)。每个行记录都有隐藏的三列:

  • row_id :隐藏的行 ID,聚集索引。
  • trx_id: 操作这个数据事务 ID ,也就是最后一个对数据插入或者更新的事务 ID 。
  • roll_ptr:回滚指针,指向这个记录的 Undo Log 信息。
行记录的列

InnoDB 将行记录快照保存在 Undo Log 里。

如果一个事务要查询行记录,需要读取哪个版本的行记录呢。Read View 保存了当前事务开启时所有活跃的事务列表,可以理解为: Read View 保存了不应该让这个事务看到的其他事务 ID 列表。

  • trx_ids 系统当前正在活跃的事务ID集合。
  • low_limit_id ,活跃事务的最大的事务 ID。
  • up_limit_id 活跃的事务中最小的事务 ID。
  • creator_trx_id,创建这个 ReadView 的事务ID。

如果当前事务的 creator_trx_id 想要读取某个行记录,这个行记录ID 的trx_id

  • 如果 trx_id < 活跃的最小事务ID(up_limit_id),也就是说这个行记录在这些活跃的事务创建前就已经提交了,那么这个行记录对当前事务是可见的。
  • 如果trx_id > 活跃的最大事务ID(low_limit_id),这个说明行记录在这些活跃的事务之后才创建,说明这个行记录对当前事务是不可见的。
  • 如果 up_limit_id < trx_id <low_limit_id,说明该记录需要在 trx_ids 集合中,可能还处于活跃状态,因此我们需要在 trx_ids 集合中遍历 ,如果trx_id 存在于 trx_ids 集合中,证明这个事务 trx_id 还处于活跃状态,不可见,否则 ,trx_id 不存在于 trx_ids 集合中,说明事务trx_id 已经提交了,这行记录是可见的。

MVCC如何查询一条记录

获取事务自己的版本号,即 事务ID
获取 Read View
查询得到的数据,然后 Read View 中的事务版本号进行比较。
如果不符合 ReadView 规则, 那么就需要 UndoLog 中历史快照;
最后返回符合规则的数据

InnoDB 实现多版本控制 (MVCC)是通过 ReadView+ UndoLog 实现的,UndoLog 保存了历史快照,ReadView 规则帮助判断当前版本的数据是否可见。

微信文章:MVCC 原理

MVCC能解决幻读问题吗?

MVCC并不能解决幻读问题。间隙锁是可重复读级别下才会有的锁,结合MVCC和间隙锁可以解决幻读的问题。

begin;
#假设users表为空,下面查出来的数据为空
select * from users; #没有加锁
#此时另一个事务提交了,且插入了一条id=1的数据
select * from users; #读快照,查出来的数据为空
update users set name='mysql' where id=1;#update是当前读,所以更新成功,并生成一个更新的快照
select * from users; #读快照,查出来id为1的一条记录,因为MVCC可以查到当前事务生成的快照
commit;

可以看到前后查出来的数据行不一致,发生了幻读。所以说只有MVCC是不能解决幻读问题的,解决幻读问题靠的是间隙锁。啥叫间隙锁:当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁。例如,来看下下面这个案例:

begin;
#假设users表为空,下面查出来的数据为空
select * from users for update; #加上共享锁
#此时另一个事务B想提交且插入了一条id=1的数据,由于有间隙锁,所以要等待
select * from users; #读快照,查出来的数据为空
update users set name='mysql' where id=1;#update是当前读,由于不存在数据,不进行更新
select * from users; #读快照,查出来的数据为空
commit;
#事务B提交成功并插入数据

注意,Mysql默认是RR(repeatable read)级别下想解决幻读问题,需要我们显式加锁,不然查询的时候还是不会加锁的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容