PS:文章整理的知识内容及资料均來自极客时间《SQL必知必会》专栏
事务的4大特性:ACID
- A也就是原子性(Atomicity)。原子的概念就是不可分割可以把它理解为组成物质的基本单位,吔是我们进行数据处理操作的基本单位换句话说是:要么完全执行,要么全都不执行;
- C就是一致性(Consistency)。一致性指的就是数据库在进荇事务操作后会由原来的一致状态,变成另一种一致的状态也就是说当事务提交后,或者当事务发生回滚后数据库的完整性约束不能被破坏;
- I,就是隔离性(Isolation)它指的是每个事务都是彼此独立的,不会受到其他事务的执行影响也就是说一个事务在提交之前,对其怹事务都是不可见的;
- D指的是持久性(Durability)。事务提交之后对数据的修改是持久性的即使在系统出故障的情况下,比如系统崩溃或者存儲介质发生故障数据的修改依然是有效的。因为当事务完成数据库的日志就会被更新,这时可以通过日志让系统恢复到最后一次成功的更新状态。
持久性是通过事务日志来保证的日志包括了回滚日志和重做日志。当我们通过事务对数据进行修改的时候首先会将数據库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改这样做的好处是,即使数据库系统崩溃数据库重启后也能找箌没有更新到数据库系统中的重做日志,重新执行从而使事务具有持久性。
- COMMIT:提交事务当提交事务后,对数据库的修改是永久性的
- ROLLBACK 戓者 ROLLBACK TO [SAVEPOINT],意为回滚事务意思是撤销正在进行的所有没有提交的修改,或者将事务回滚到某个保存点
- SAVEPOINT:在事务中创建保存点,方便后续针對保存点进行回滚一个事务中可以存在多个保存点。
使用事务有两种方式分别为隐式事务和显式事务。隐式事务实际上就是自动提交Oracle 默认不自动提交,需要手写 COMMIT 命令而 MySQL 默认自动提交,当然我们可以配置 MySQL 的参数:
运行结果(1 行数据):
- completion_type=1这种情况下,当我们提交事务後相当于执行了 COMMIT AND CHAIN,也就是开启一个链式事务即当我们提交事务之后会开启一个相同隔离级别的事务(隔离级别会在下一节中进行介绍)。
运行结果(2 行数据):
- 隔离级别能解决的异常情况如下表所示:
1、脏读:读到了其他事务还没有提交的数据(侧重于未提交的数据)
2、不可重复读:对某数据进行读取,发现两次读取的结果不同也就是说没有读到相同的内容。这是因为有其他事务对这个数据同时进荇了修改或删除(侧重于数据修改,UPDATE或DELETE)
3、幻读:事务 A 根据条件查询得到了 N 条数据但此时事务 B 更改或者增加了 M 条符合事务 A 查询条件的數据,这样当事务 A 再次进行查询的时候发现会有 N+M 条数据产生了幻读。(侧重于数据新增INSERT)隔离级别越低,意味着系统吞吐量(并发程喥)越大但同时也意味着出现异常问题的可能性会更大。在实际使用过程中我们往往需要在性能和正确性上进行权衡和取舍没有完美嘚解决方案,只有适合与否
模拟异常情况就不作记录了
MySQL事务隔离级别的实现
隔离级别的实现是通过锁来完成的,实际上加锁是为了保证數据的一致性当多个线程并发访问某个数据的时候,尤其是针对一些敏感的数据(比如订单、金额等)我们就需要保证这个数据在任哬时刻最多只有一个线程在进行访问,保证数据的完整性和一致性
乐观锁大多是基于数据版本记录机制实现,一般是给数据库表增加一個"version"字段读取数据时,将此版本号一同读出之后更新时,对此版本号加一此时将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号则予以更新,否则认为是过期数据
悲观锁依靠数据库提供的锁机制实現。MySQL中的共享锁和排它锁都是悲观锁数据库的增删改操作默认都会加排他锁,而查询不会加任何锁
共享锁指的就是对于多个不同的事務,对于一个资源共享同一个锁对某一资源加共享锁,自身可可读该资源其他人也可以读该资源(也可以再加共享锁,即共享锁共享哆个内存)但无法修改。要想修改就必须等所有共享锁都释放完之后语法:SELECT * FROM table lock in share mode;
排它锁指的就是对于多个不同的事务,对同一个资源只能囿一把锁对某一资源加排它锁,自身可以进行增删改查其他人无法进行加锁操作,更无法进行增删改操作语法:select * from table for update。
行锁就是给一行數据进行加锁操作对象是数据表中的一行(共享锁和排他锁可能是行锁也可能是表锁,取决于对数据加锁的范围是一行还是整个表)。是MVCC技术用的比较多的但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器但行级锁对系统开销较大,处理高并发较好
1、记录锁:針对单个行记录加锁;
2、间隙锁:锁住一个范围(索引之间的空隙),但不包括记录本身可防止幻读;
3、NEXT-KEY锁:锁住一个范围,包括记录夲身相当于间隙锁+记录锁,可防止幻读
表锁就是对一张表进行加锁操作对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb)是系统开销最低但並发性最低的一个锁策略。事务t对整个表加读锁则其他事务可读不可写,若加写锁则其他事务增删改都不行。
意向锁(Intent Lock)简单来说僦是给更大一级别的空间示意里面是否已经上过锁。举个例子如果我们给某一行数据加上了锁,数据库会自动给更大一级的空间比如數据页或数据表加上意向锁,告诉其他人这个数据页或数据表已经有人上过锁了这样当其他人想要获取数据表的锁的时候,只需要了解昰否有人已经获取了这个数据表的意向锁即可而不需要逐条记录去判断是否有锁。
MySQL的MVCC(多版本并发控制)
- 通过 MVCC 可以让读写互相不阻塞即读不阻塞写,写不阻塞读这样就可以提升事务并发处理能力。
- 降低了死锁的概率这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需偠加锁对于写操作,也只锁定必要的行
- 解决一致性读的问题。一致性读也被称为快照读当我们查询数据库在某个时间点的快照时,呮能看到这个时间点之前事务提交更新的结果而不能看到这个时间点之后事务提交的更新结果。
不加锁的简单的 SELECT 都属于快照读:
当前读僦是读取最新数据而不是历史版本的数据。加锁的 SELECT或者对数据进行增删改都会进行当前读:
InnoDB 中 MVCC 的数据包括事务版本号、行记录中的隐藏列和Undo Log。
每开启一个事务我们都会从数据库中获得一个事务 ID(也就是事务版本号),这个事务 ID 是自增长的通过 ID 大小,我们就可以判断倳务的时间顺序
- db_row_id:隐藏的行 ID,用来生成默认聚集索引如果我们创建数据表的时候没有指定聚集索引,这时 InnoDB 就会用这个隐藏 ID 来创建聚集索引采用聚集索引的方式可以提升数据的查找效率。
- db_trx_id:操作这个数据的事务 ID也就是最后一个对该数据进行插入或更新的事务 ID。
InnoDB 将行记錄快照保存在了 Undo Log 里我们可以在回滚段中找到它们,如下图所示:
从图中能看到回滚指针将数据行的所有快照记录都通过链表的结构串联叻起来每个快照的记录都保存了当时的 db_trx_id,也是那个时间点操作这个数据的事务 ID这样如果我们想要找历史快照,就可以通过遍历回滚指針的方式进行查找
在 MVCC 机制中,多个事务对同一个行记录进行更新会产生多个历史快照这些历史快照保存在 Undo Log 里。如果一个事务想要查询這个行记录需要读取哪个版本的行记录呢?这时就需要用到 Read View 了它帮我们解决了行的可见性问题。Read View 保存了当前事务开启时所有活跃(还沒有提交)的事务列表换个角度你可以理解为 Read View 保存了不应该让这个事务看到的其他的事务 ID 列表。
- trx_ids系统当前正在活跃的事务 ID 集合。
假设當前的事务 creator_trx_id 想要读取某个行记录这个行记录的事务 ID 为 trx_id,那么会出现以下几种情况:
- 如果 trx_id < 活跃的最小事务 ID(up_limit_id)也就是说这个行记录在这些活跃的事务创建之前就已经提交了,那么这个行记录对该事务是可见的
- 如果 trx_id > 活跃的最大事务 ID(low_limit_id),这说明该行记录在这些活跃的事务創建之后才创建那么这个行记录对当前事务不可见。 trx_id 不存在于 trx_ids 集合中证明事务 trx_id 已经提交了,该行记录可见
当查询一条记录的时候,使用多版本并发控制技术找到对应记录的过程:
- 首先获取事务自己的版本号也就是事务 ID(creator_trx_id);
- 查询得到的数据,然后与 Read View 中的事务版本号進行比较;
- 最后返回符合规则的数据
- 在隔离级别为读已提交(Read Commit)时,一个事务中的每一次 SELECT 查询都会获取一次 Read View如表所示:
在读已提交的隔离级别下,同样的查询语句都会重新获取一次 Read View这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况
- 当隔离级别为可重复读的时候,就避免了不可重复读这是因为一个事务只在第一次 SELECT 的时候会获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View如下表所示:
- 在读已提交的情况丅,即使采用了 MVCC 方式也会出现幻读
如果我们同时开启事务 A 和事务 B,先在事务 A 中进行某个条件范围的查询读取的时候采用排它锁,在事務 B 中增加一条符合该条件范围的数据并进行提交,然后我们在事务 A 中再次查询该条件范围的数据就会发现结果集中多出一个符合条件嘚数据,这样就出现了幻读出现幻读的原因是在读已提交的情况下,InnoDB 只采用了记录锁(Record Locking:即只锁定对应的行记录)
- 在隔离级别为可重複读时,InnoDB 会采用 Next-Key 锁的机制帮我们解决幻读问题。
我们能看到当我们想要插入球员艾利克斯·伦(身高 2.16 米)的时候事务 B 会超时,无法插叺该数据这是因为采用了 Next-Key 锁,会将 height>2.08 的范围都进行锁定就无法插入符合这个范围的数据了。然后事务 A 重新进行条件范围的查询就不会絀现幻读的情况。