Mysql基础知识整理笔记(事务)

PS:文章整理的知识内容及资料均来自极客时间《SQL必知必会》专栏mysql

MySQL的InnoDB引擎支持事务,MyISAM不支持事务;

事务基础

事务的4大特性:ACIDsql


  1. A,也就是原子性(Atomicity)。原子的概念就是不可分割,能够把它理解为组成物质的基本单位,也是咱们进行数据处理操做的基本单位,换句话说是:要么彻底执行,要么全都不执行;
  2. C,就是一致性(Consistency)。一致性指的就是数据库在进行事务操做后,会由原来的一致状态,变成另外一种一致的状态。也就是说当事务提交后,或者当事务发生回滚后,数据库的完整性约束不能被破坏;
  3. I,就是隔离性(Isolation)。它指的是每一个事务都是彼此独立的,不会受到其余事务的执行影响。也就是说一个事务在提交以前,对其余事务都是不可见的;
  4. D,指的是持久性(Durability)。事务提交以后对数据的修改是持久性的,即便在系统出故障的状况下,好比系统崩溃或者存储介质发生故障,数据的修改依然是有效的。由于当事务完成,数据库的日志就会被更新,这时能够经过日志,让系统恢复到最后一次成功的更新状态。
持久性是经过事务日志来保证的。日志包括了回滚日志和重作日志。当咱们经过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重作日志中,而后再对数据库中对应的行进行修改。这样作的好处是,即便数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重作日志,从新执行,从而使事务具备持久性。

事务的经常使用操做语句数据库


  1. START TRANSACTION 或者 BEGIN,做用是显式开启一个事务。
  2. COMMIT:提交事务。当提交事务后,对数据库的修改是永久性的。
  3. ROLLBACK 或者 ROLLBACK TO [SAVEPOINT],意为回滚事务。意思是撤销正在进行的全部没有提交的修改,或者将事务回滚到某个保存点。
  4. SAVEPOINT:在事务中建立保存点,方便后续针对保存点进行回滚。一个事务中能够存在多个保存点。
  5. RELEASE SAVEPOINT:删除某个保存点。
  6. SET TRANSACTION,设置事务的隔离级别。
使用事务有两种方式,分别为隐式事务和显式事务。隐式事务实际上就是自动提交,Oracle 默认不自动提交,须要手写 COMMIT 命令,而 MySQL 默认自动提交,固然咱们能够配置 MySQL 的参数:

MySQL 中 completion_type 参数对于事务的做用

  • completion_type=0,这是默认状况。也就是说当咱们执行 COMMIT 的时候会提交事务,在执行下一个事务时,还须要咱们使用 START TRANSACTION 或者 BEGIN 来开启。
CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB;
BEGIN;
INSERT INTO test SELECT '关羽';
COMMIT;
INSERT INTO test SELECT '张飞';
INSERT INTO test SELECT '张飞';
ROLLBACK;
SELECT * FROM test;

运行结果(1 行数据):
26d1f5a4a534eb9b1415ce867f006b80.png服务器

  • completion_type=1,这种状况下,当咱们提交事务后,至关于执行了 COMMIT AND CHAIN,也就是开启一个链式事务,即当咱们提交事务以后会开启一个相同隔离级别的事务(隔离级别会在下一节中进行介绍)。
CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB;
SET @@completion_type = 1;
BEGIN;
INSERT INTO test SELECT '关羽';
COMMIT;
INSERT INTO test SELECT '张飞';
INSERT INTO test SELECT '张飞';
ROLLBACK;
SELECT * FROM test;

运行结果(2 行数据):
df437e577ce75363f5eb22dc362dbff7.png并发

  • completion_type=2,这种状况下 COMMIT=COMMIT AND RELEASE,也就是当咱们提交后,会自动与服务器断开链接。

MySQL事务隔离

20697e62019613bc49f7ed88872b3a5d.png

  • 隔离级别能解决的异常状况以下表所示:

b07103c5f5486aec5e2daf1dacfd6f95.png

三种异常状况的特色:
一、脏读:读到了其余事务尚未提交的数据。(侧重于未提交的数据)
二、不可重复读:对某数据进行读取,发现两次读取的结果不一样,也就是说没有读到相同的内容。这是由于有其余事务对这个数据同时进行了修改或删除。(侧重于数据修改,UPDATE或DELETE)
三、幻读:事务 A 根据条件查询获得了 N 条数据,但此时事务 B 更改或者增长了 M 条符合事务 A 查询条件的数据,这样当事务 A 再次进行查询的时候发现会有 N+M 条数据,产生了幻读。(侧重于数据新增,INSERT)

隔离级别越低,意味着系统吞吐量(并发程度)越大,但同时也意味着出现异常问题的可能性会更大。在实际使用过程当中咱们每每须要在性能和正确性上进行权衡和取舍,没有完美的解决方案,只有适合与否。高并发

模拟异常状况就不做记录了性能

MySQL事务隔离级别的实现

MySQL中的锁spa


隔离级别的实现是经过锁来完成的,实际上加锁是为了保证数据的一致性,当多个线程并发访问某个数据的时候,尤为是针对一些敏感的数据(好比订单、金额等),咱们就须要保证这个数据在任什么时候刻最多只有一个线程在进行访问,保证数据的完整性和一致性。
  • 乐观锁

乐观锁大可能是基于数据版本记录机制实现,通常是给数据库表增长一个"version"字段。读取数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。线程

  • 悲观锁

悲观锁依靠数据库提供的锁机制实现。MySQL中的共享锁和排它锁都是悲观锁。数据库的增删改操做默认都会加排他锁,而查询不会加任何锁。3d

  • 共享锁(读锁,S锁)

共享锁指的就是对于多个不一样的事务,对于一个资源共享同一个锁。对某一资源加共享锁,自身可可读该资源,其余人也能够读该资源(也能够再加共享锁,即共享锁共享多个内存),但没法修改。要想修改就必须等全部共享锁都释放完以后。语法:SELECT * FROM table lock in share mode;

  • 排它锁(写锁,X锁)

排它锁指的就是对于多个不一样的事务,对同一个资源只能有一把锁。对某一资源加排它锁,自身能够进行增删改查,其余人没法进行加锁操做,更没法进行增删改操做。语法:select * from table for update。

  • 行锁

行锁就是给一行数据进行加锁,操做对象是数据表中的一行(共享锁和排他锁多是行锁也多是表锁,取决于对数据加锁的范围,是一行仍是整个表)。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。

InnoDB行锁的3种方式:
一、记录锁:针对单个行记录加锁;
二、间隙锁:锁住一个范围(索引之间的空隙),但不包括记录自己,可防止幻读;
三、NEXT-KEY锁:锁住一个范围,包括记录自己,至关于间隙锁+记录锁,可防止幻读
  • 表锁

表锁就是对一张表进行加锁,操做对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其余事务可读不可写,若加写锁,则其余事务增删改都不行。

  • 意向锁

意向锁(Intent Lock),简单来讲就是给更大一级别的空间示意里面是否已经上过锁。举个例子,若是咱们给某一行数据加上了锁,数据库会自动给更大一级的空间,好比数据页或数据表加上意向锁,告诉其余人这个数据页或数据表已经有人上过锁了,这样当其余人想要获取数据表的锁的时候,只须要了解是否有人已经获取了这个数据表的意向锁便可,而不须要逐条记录去判断是否有锁。

MySQL的MVCC(多版本并发控制)


  1. 经过 MVCC 可让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就能够提高事务并发处理能力。
  2. 下降了死锁的几率。这是由于 MVCC 采用了乐观锁的方式,读取数据时并不须要加锁,对于写操做,也只锁定必要的行。
  3. 解决一致性读的问题。一致性读也被称为快照读,当咱们查询数据库在某个时间点的快照时,只能看到这个时间点以前事务提交更新的结果,而不能看到这个时间点以后事务提交的更新结果。

快照读和当前读

  • 快照读

不加锁的简单的 SELECT 都属于快照读:

SELECT * FROM table WHERE ...
  • 当前读

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

SELECT * FROM table LOCK IN SHARE MODE;
SELECT * FROM table FOR UPDATE;
INSERT INTO table values ...;
DELETE FROM table WHERE ...;
UPDATE table SET ...;

MVCC 的核心:Undo Log(MV) + Read View(CC)

InnoDB 中 MVCC 的数据包括事务版本号行记录中的隐藏列Undo Log

  • 事务版本号

每开启一个事务,咱们都会从数据库中得到一个事务 ID(也就是事务版本号),这个事务 ID 是自增加的,经过 ID 大小,咱们就能够判断事务的时间顺序。

  • 行记录中的隐藏列
  1. db_row_id:隐藏的行 ID,用来生成默认汇集索引。若是咱们建立数据表的时候没有指定汇集索引,这时 InnoDB 就会用这个隐藏 ID 来建立汇集索引。采用汇集索引的方式能够提高数据的查找效率。
  2. db_trx_id:操做这个数据的事务 ID,也就是最后一个对该数据进行插入或更新的事务 ID。
  3. db_roll_ptr:回滚指针,也就是指向这个记录的 Undo Log 信息。

clipboard.png

  • Undo Log

InnoDB 将行记录快照保存在了 Undo Log 里,咱们能够在回滚段中找到它们,以下图所示:

clipboard.png

从图中能看到回滚指针将数据行的全部快照记录都经过链表的结构串联了起来,每一个快照的记录都保存了当时的 db_trx_id,也是那个时间点操做这个数据的事务 ID。这样若是咱们想要找历史快照,就能够经过遍历回滚指针的方式进行查找。

  • Read View

在 MVCC 机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在 Undo Log 里。若是一个事务想要查询这个行记录,须要读取哪一个版本的行记录呢?这时就须要用到 Read View 了,它帮咱们解决了行的可见性问题。Read View 保存了当前事务开启时全部活跃(尚未提交)的事务列表,换个角度你能够理解为 Read View 保存了不该该让这个事务看到的其余的事务 ID 列表。

Read VIew 几个重要的属性:

  1. trx_ids,系统当前正在活跃的事务 ID 集合。
  2. low_limit_id,活跃的事务中最大的事务 ID。
  3. up_limit_id,活跃的事务中最小的事务 ID。
  4. creator_trx_id,建立这个 Read View 的事务 ID。

如图所示,trx_ids 为 trx二、trx三、trx5 和 trx8 的集合,活跃的最大事务 ID(low_limit_id)为 trx8,活跃的最小事务 ID(up_limit_id)为 trx2。

clipboard.png

假设当前的事务 creator_trx_id 想要读取某个行记录,这个行记录的事务 ID 为 trx_id,那么会出现如下几种状况:

  1. 若是 trx_id < 活跃的最小事务 ID(up_limit_id),也就是说这个行记录在这些活跃的事务建立以前就已经提交了,那么这个行记录对该事务是可见的。
  2. 若是 trx_id > 活跃的最大事务 ID(low_limit_id),这说明该行记录在这些活跃的事务建立以后才建立,那么这个行记录对当前事务不可见。
  3. 若是 up_limit_id < trx_id < low_limit_id,说明该行记录所在的事务 trx_id 在目前 creator_trx_id 这个事务建立的时候,可能还处于活跃的状态,所以咱们须要在 trx_ids 集合中进行遍历,若是 trx_id 存在于 trx_ids 集合中,证实这个事务 trx_id 还处于活跃状态,不可见。不然,若是 trx_id 不存在于 trx_ids 集合中,证实事务 trx_id 已经提交了,该行记录可见。

当查询一条记录的时候,使用多版本并发控制技术找到对应记录的过程:

  1. 首先获取事务本身的版本号,也就是事务 ID(creator_trx_id);
  2. 使用creator_trx_id获取 Read View;
  3. 查询获得的数据,而后与 Read View 中的事务版本号进行比较;
  4. 若是不符合 Read View 规则,就须要从 Undo Log 中获取历史快照;
  5. 最后返回符合规则的数据。
InnoDB 中,MVCC 是经过 Undo Log + Read View 进行数据读取,Undo Log 保存了历史快照,而 Read View 规则帮咱们判断当前版本的数据是否可见。
  • 在隔离级别为读已提交(Read Commit)时,一个事务中的每一次 SELECT 查询都会获取一次 Read View。如表所示:

clipboard.png

在读已提交的隔离级别下,一样的查询语句都会从新获取一次 Read View,这时若是 Read View 不一样,就可能产生不可重复读或者幻读的状况。

  • 当隔离级别为可重复读的时候,就避免了不可重复读,这是由于一个事务只在第一次 SELECT 的时候会获取一次 Read View,然后面全部的 SELECT 都会复用这个 Read View,以下表所示:

clipboard.png

InnoDB解决幻读的方法:NEXT-KEY锁 + MVCC

  • 在读已提交的状况下,即便采用了 MVCC 方式也会出现幻读。

若是咱们同时开启事务 A 和事务 B,先在事务 A 中进行某个条件范围的查询,读取的时候采用排它锁,在事务 B 中增长一条符合该条件范围的数据,并进行提交,而后咱们在事务 A 中再次查询该条件范围的数据,就会发现结果集中多出一个符合条件的数据,这样就出现了幻读。出现幻读的缘由是在读已提交的状况下,InnoDB 只采用了记录锁(Record Locking:即只锁定对应的行记录)。

clipboard.png

  • 在隔离级别为可重复读时,InnoDB 会采用 Next-Key 锁的机制,帮咱们解决幻读问题。

咱们能看到当咱们想要插入球员艾利克斯·伦(身高 2.16 米)的时候,事务 B 会超时,没法插入该数据。这是由于采用了 Next-Key 锁,会将 height>2.08 的范围都进行锁定,就没法插入符合这个范围的数据了。而后事务 A 从新进行条件范围的查询,就不会出现幻读的状况。

clipboard.png

相关文章
相关标签/搜索