浙江商会网站建设策划方案,做类似淘宝网站怎么做的,丹东做网站公司,佛山网站制作好处目录 前言
1.为什么存在事务
2.什么是事务
3.事务的版本支持
4.事务提交方式
5.事务常见操作方式
6.事务隔离级别
6.1如何理解隔离性
6.2隔离级别
6.3隔离性的查看与设置
6.4读未提交
6.5读提交
6.6可重复读
6.7串行化
7.多版本并发控制
7.1 3个记录隐藏列字段…目录 前言
1.为什么存在事务
2.什么是事务
3.事务的版本支持
4.事务提交方式
5.事务常见操作方式
6.事务隔离级别
6.1如何理解隔离性
6.2隔离级别
6.3隔离性的查看与设置
6.4读未提交
6.5读提交
6.6可重复读
6.7串行化
7.多版本并发控制
7.1 3个记录隐藏列字段 7.2undo日志
7.3模拟MVCC的流程
7.4Read View
7.5 RR与RC的本质区别
总结 前言 hello,各位小伙伴大家好本篇文章为大家介绍MySQL中一个重要的话题关于事务该如何理解。相信第一次接触到这个话题的小伙伴是比较茫然的不过不用担心下面我们就通过一个生活中的例子来引入事务存在的原因然后再来介绍什么是事务以及事务的实现策略相信看完之后大家对事务就会有一个清晰的认识。 1.为什么存在事务
相信大家坐车都买过票吧但是关于买票的逻辑又是如何样的有没有小伙伴考虑过呢在这里我们不考虑具体细节大致来看一下买票的逻辑。
如图所示当有客户买票的时候可能会出现下面这种情况 因为是并发访问的所以就会有可能出现图中的问题一张票被卖了两次在现实生活中这是一种不合理的现象。那应该如何保证正确性呢
只要满足下面几个条件就能保证合理性了
a.买票的过程保证原子性
b.买票的时候互相不能影响
c.买完票应该保证永久有效
d.买前和买后都要是确定的状态
问题产生了接下来就是如何解决问题了MySQL解决这种问题的方式便是引入事务事务存在的意义便是解决上述问题的。
下面就来一起看看在MySQL中对事务的定义是如何样的
2.什么是事务 事务在MySQL中就是一组DML语句组成这些语句在逻辑上存在相关性这一组DML语句要么全部成功要么全部失败是一个整体。MySQL提供一种机制保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。 事务就是要做的或所做的事情主要用于处理操作量大复杂度高的数据。假设一种场景你毕业了学校的教务系统后台 MySQL 中不在需要你的数据要删除你的所有信息(一般不会:) ), 那么要删除你的基本信息(姓名电话籍贯等)的同时也删除和你有关的其他信息比如你的各科成绩你在校表现甚至你在论坛发过的文章等。这样就需要多条 MySQL 语句构成那么所有这些操作合起来就构成了一个事务。 正如我们上面所说一个 MySQL 数据库可不止你一个事务在运行同一时刻甚至有大量的请求被包装成事务在向 MySQL 服务器发起事务处理请求。而每条事务至少一条 SQL 最多很多 SQL ,这样如果大家都访问同样的表数据在不加保护的情况就绝对会出现问题。甚至因为事务由多条 SQL 构成那么也会存在执行到一半出错或者不想再执行的情况那么已经执行的怎么办呢 所以一个完整的事务绝对不是简单的 sql 集合还需要满足如下四个属性原子性一个事务transaction中的所有操作要么全部完成要么全部不完成不会结束在中间某个环节。事务在执行过程中发生错误会被回滚Rollback到事务开始前的状态就像这个事务从来没有执行过一样。一致性在事务开始之前和事务结束以后数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。隔离性数据库允许多个并发事务同时对其数据进行读写和修改的能力隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别包括读未提交 Readuncommitted 、读提交 read committed 、可重复读 repeatable read 和串行化 Serializable 持久性事务处理结束后对数据的修改就是永久的即便系统故障也不会丢失。 上面四个属性可以简称为 ACID 看到这里相信此时的你一定是这样的
是不是理解起来很简单呀。明白了事务的概念之后下面我们继续进一步学习事务的相关话题。
3.事务的版本支持
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务 MyISAM 不支持。 查看数据库引擎
show engines\G; 查看数据库引擎
Engine: InnoDBSupport: DEFAULTComment: Supports transactions, row-level locking, and foreign keys
Transactions: YES --InnoDB支持事务XA: YESSavepoints: YES
*************************** 2. row ***************************Engine: MyISAMSupport: YESComment: MyISAM storage engine
Transactions: NO --MyISAM不支持事务XA: NOSavepoints: NO4.事务提交方式
事务的提交方式常见的有两种 自动提交 手动提交 查看事务提交方式
mysql show variables like autocommit;
----------------------
| Variable_name | Value |
----------------------
| autocommit | ON |
----------------------
1 row in set (0.03 sec)用set来改变MySQL的提交方式
SET AUTOCOMMIT0 禁止自动提交
mysql SET AUTOCOMMIT0;
Query OK, 0 rows affected (0.00 sec)
mysql show variables like autocommit;
----------------------
| Variable_name | Value |
----------------------
| autocommit | OFF |
----------------------
1 row in set (0.00 sec)
SET AUTOCOMMIT1 开启自动提交
mysql SET AUTOCOMMIT1;
Query OK, 0 rows affected (0.00 sec)
mysql show variables like autocommit;
----------------------
| Variable_name | Value |
----------------------
| autocommit | ON |
----------------------
1 row in set (0.01 sec)
5.事务常见操作方式 为了便于演示我们将MySQL的默认隔离级别设置成读未提交。具体操作我们后面专门会讲现在已使用为主。 mysql set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql quit
Bye
##需要重启终端进行查看
mysql select tx_isolation;
------------------
| tx_isolation |
------------------
| READ-UNCOMMITTED |
------------------
1 row in set, 1 warning (0.00 sec)
创建测试表
mysql create table if not exists account(- id int primary key,- name varchar(20) not null default ,- balance decimal(10,2) not null default 0.0- );正常演示 - 证明事务的开始与回滚
mysql show variables like autocommit; -- 查看事务是否自动提交。我们故意设置成自
动提交看看该选项是否影响begin
----------------------
| Variable_name | Value |
----------------------
| autocommit | ON |
----------------------
1 row in set (0.00 sec)
mysql start transaction; -- 开始一个事务begin也可以推荐begin
Query OK, 0 rows affected (0.00 sec)
mysql savepoint save1; -- 创建一个保存点save1
Query OK, 0 rows affected (0.00 sec)
mysql insert into account values (1, 张三, 100); -- 插入一条记录
Query OK, 1 row affected (0.05 sec)
mysql savepoint save2; -- 创建一个保存点save2
Query OK, 0 rows affected (0.01 sec)mysql insert into account values (2, 李四, 10000); -- 在插入一条记录
Query OK, 1 row affected (0.00 sec)
mysql select * from account; -- 两条记录都在了
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql rollback to save2; -- 回滚到保存点save2
Query OK, 0 rows affected (0.03 sec)
mysql select * from account; -- 一条记录没有了
--------------------
| id | name | blance |
--------------------
| 1 | 张三 | 100.00 |
--------------------
1 row in set (0.00 sec)
mysql rollback; -- 直接rollback回滚在最开始
Query OK, 0 rows affected (0.00 sec)
mysql select * from account; -- 所有刚刚的记录没有了
Empty set (0.00 sec)
非正常演示1 - 证明未commit客户端崩溃MySQL自动会回滚隔离级别设置为读未提交
-- 终端A
mysql select * from account; -- 当前表内无数据
Empty set (0.00 sec)
mysql show variables like autocommit; -- 依旧自动提交
----------------------
| Variable_name | Value |
----------------------
| autocommit | ON |
----------------------
1 row in set (0.00 sec)
mysql begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql insert into account values (1, 张三, 100); -- 插入记录
Query OK, 1 row affected (0.00 sec)
mysql select * from account; --数据已经存在但没有commit此时同时查看
终端B
--------------------
| id | name | blance |
--------------------
| 1 | 张三 | 100.00 |
--------------------1 row in set (0.00 sec)
mysql Aborted -- ctrl \ 异常终止MySQL
--终端B
mysql select * from account; --终端A崩溃前
--------------------
| id | name | blance |
--------------------
| 1 | 张三 | 100.00 |
--------------------
1 row in set (0.00 sec)
mysql select * from account; --数据自动回滚
Empty set (0.00 sec)
非正常演示2 - 证明commit了客户端崩溃MySQL数据不会在受影响已经持久化
--终端 A
mysql show variables like autocommit; -- 依旧自动提交
----------------------
| Variable_name | Value |
----------------------
| autocommit | ON |
----------------------
1 row in set (0.00 sec)
mysql select * from account; -- 当前表内无数据
Empty set (0.00 sec)
mysql begin; -- 开启事务
Query OK, 0 rows affected (0.00 sec)
mysql insert into account values (1, 张三, 100); -- 插入记录
Query OK, 1 row affected (0.00 sec)
mysql commit; --提交事务
Query OK, 0 rows affected (0.04 sec)
mysql Aborted -- ctrl \ 异常终止MySQL
--终端 B
mysql select * from account; --数据存在了所以commit的作用是将数据持久
化到MySQL中
--------------------
| id | name | blance |
--------------------
| 1 | 张三 | 100.00 |
--------------------
1 row in set (0.00 sec)
非正常演示3 - 对比试验。证明begin操作会自动更改提交方式不会受MySQL是否自动提交影响
-- 终端 A
mysql select *from account; --查看历史数据
--------------------
| id | name | blance |
--------------------
| 1 | 张三 | 100.00 |
--------------------
1 row in set (0.00 sec)
mysql show variables like autocommit; --查看事务提交方式
----------------------
| Variable_name | Value |
----------------------
| autocommit | ON |
----------------------
1 row in set (0.00 sec)
mysql set autocommit0; --关闭自动提交
Query OK, 0 rows affected (0.00 sec)
mysql show variables like autocommit; --查看关闭之后结果
----------------------
| Variable_name | Value |
----------------------
| autocommit | OFF |
----------------------
1 row in set (0.00 sec)
mysql begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql insert into account values (2, 李四, 10000); --插入记录
Query OK, 1 row affected (0.00 sec)
mysql select *from account; --查看插入记录同时查看终端B
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql Aborted --再次异常终止
-- 终端B
mysql select * from account; --终端A崩溃前
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql select * from account; --终端A崩溃后自动回滚
--------------------
| id | name | blance |
--------------------
| 1 | 张三 | 100.00 |
--------------------
1 row in set (0.00 sec)
非正常演示4 - 证明单条 SQL 与事务的关系
--实验一
-- 终端A
mysql select * from account;
--------------------
| id | name | blance |
--------------------
| 1 | 张三 | 100.00 |
--------------------
1 row in set (0.00 sec)
mysql show variables like autocommit;
----------------------
| Variable_name | Value |
----------------------
| autocommit | ON |
----------------------
1 row in set (0.00 sec)
mysql set autocommit0; --关闭自动提交
Query OK, 0 rows affected (0.00 sec)
mysql insert into account values (2, 李四, 10000); --插入记录
Query OK, 1 row affected (0.00 sec)
mysql select *from account; --查看结果已经插入。此时可以在查
看终端B
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql ^DBye --ctrl \ or ctrl d,终止终
端
--终端B
mysql select * from account; --终端A崩溃前
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql select * from account; --终端A崩溃后
--------------------
| id | name | blance |
--------------------
比特就业课
| 1 | 张三 | 100.00 |
--------------------
1 row in set (0.00 sec)
-- 实验二
--终端A
mysql show variables like autocommit; --开启默认提交
----------------------
| Variable_name | Value |
----------------------
| autocommit | ON |
----------------------
1 row in set (0.00 sec)
mysql select * from account;
--------------------
| id | name | blance |
--------------------
| 1 | 张三 | 100.00 |
--------------------
1 row in set (0.00 sec)
mysql insert into account values (2, 李四, 10000);
Query OK, 1 row affected (0.01 sec)
mysql select *from account; --数据已经插入
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql Aborted --异常终止
--终端B
mysql select * from account; --终端A崩溃前
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql select * from account; --终端A崩溃后并不影响已经持久化。autocommit
起作用
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec) 从上面的实验中我们可以得出以下这些结论 1.只要输入begin或者start transaction事务便必须要通过commit提交才会持久化与是否设置set autocommit无关。 2.事务可以手动回滚同时当操作异常MySQL会自动回滚 3.对于 InnoDB 每一条 SQL 语言都默认封装成事务自动提交。 事务操作注意事项 a.如果没有设置保存点也可以回滚只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交) b.如果一个事务被提交了commit则不可以回退rollback可以选择回退到哪个保存点 c.InnoDB 支持事务 MyISAM 不支持事务 d.开始事务可以使 start transaction 或者 begin 通过上面的学习关于事务我们已经有了一个宏观的认识了包括什么是事务以及MySQL中对事务如何操作等下面关于我们继续来看一些关于事务实现隔离性的相关策略
6.事务隔离级别
6.1如何理解隔离性 MySQL服务可能会同时被多个客户端进程(线程)访问访问的方式以事务方式进行 一个事务可能由多条SQL构成也就意味着任何一个事务都有执行前执行中执行后的阶段。而所谓的原子性其实就是让用户层要么看到执行前要么看到执行后。执行中出现问题可以随时回滚。所以单个事务对用户表现出来的特性就是原子性。 但毕竟所有事务都要有个执行过程那么在多个事务各自执行多个SQL的时候就还是有可能会出现互相影响的情况。比如多个事务同时访问同一张表甚至同一行数据。 就如同你妈妈给你说你要么别学要学就学到最好。至于你怎么学中间有什么困难你妈妈不关心。那么你的学习对你妈妈来讲就是原子的。那么你学习过程中很容易受别人干扰此时就需要将你的学习隔离开保证你的学习环境是健康的。 数据库中为了保证事务执行过程中尽量不受干扰就有了一个重要特征隔离性 数据库中允许事务受不同程度的干扰就有了一种重要特征隔离级别 6.2隔离级别 读未提交【Read Uncommitted】 在该隔离级别所有的事务都可以看到其他事务没有提交的执行结果。实际生产中不可能使用这种隔离级别的但是相当于没有任何隔离性也会有很多并发问题如脏读幻读不可重复读等我们上面为了做实验方便用的就是这个隔离性。读提交【Read Committed】 该隔离级别是大多数数据库的默认的隔离级别不是 MySQL 默认的。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读即一个事务执行时如果多次 select 可能得到不同的结果。可重复读【Repeatable Read】 这是 MySQL 默认的隔离级别它确保同一个事务在执行中多次读取操作数据时会看到同样的数据行。但是会有幻读问题。串行化【Serializable】: 这是事务的最高隔离级别它通过强制事务排序使之不可能相互冲突从而解决了幻读的问题。它在每个读的数据行上面加上共享锁。但是可能会导致超时和锁竞争这种隔离级别太极端实际生产基本不使用 相信看完上面的文字对事务实现隔离性有了一个宏观的认识下面我们就一起通过实验再深入了解以下关于每一种隔离策略是如何实现的。
6.3隔离性的查看与设置
- 查看
mysql SELECT global.tx_isolation; --查看全局隔级别
-----------------------
| global.tx_isolation |
-----------------------
| REPEATABLE-READ |
-----------------------
1 row in set, 1 warning (0.00 sec)
mysql SELECT session.tx_isolation; --查看会话(当前)全局隔级别
------------------------
| session.tx_isolation |
------------------------
| REPEATABLE-READ |
------------------------
1 row in set, 1 warning (0.00 sec)
mysql SELECT tx_isolation; --默认同上
-----------------
| tx_isolation |
-----------------
| REPEATABLE-READ |
-----------------
1 row in set, 1 warning (0.00 sec)
--设置
-- 设置当前会话 or 全局隔离级别语法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ
COMMITTED | REPEATABLE READ | SERIALIZABLE}
--设置当前会话隔离性另起一个会话看不多只影响当前会话
mysql set session transaction isolation level serializable; -- 串行化
Query OK, 0 rows affected (0.00 sec)
mysql SELECT global.tx_isolation; --全局隔离性还是RR
-----------------------
| global.tx_isolation |
-----------------------
| REPEATABLE-READ |
-----------------------
1 row in set, 1 warning (0.00 sec)
mysql SELECT session.tx_isolation; --会话隔离性成为串行化
------------------------
| session.tx_isolation |
------------------------
| SERIALIZABLE |
------------------------
1 row in set, 1 warning (0.00 sec)
mysql SELECT tx_isolation; --同上
----------------
| tx_isolation |
----------------
| SERIALIZABLE |
----------------
1 row in set, 1 warning (0.00 sec)
--设置全局隔离性另起一个会话会被影响
mysql set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql SELECT global.tx_isolation;
-----------------------
| global.tx_isolation |
-----------------------
| READ-UNCOMMITTED |
-----------------------
1 row in set, 1 warning (0.00 sec)
mysql SELECT session.tx_isolation;
------------------------
| session.tx_isolation |
------------------------
| READ-UNCOMMITTED |
------------------------
1 row in set, 1 warning (0.00 sec)
mysql SELECT tx_isolation;
------------------
| tx_isolation |
------------------
| READ-UNCOMMITTED |
------------------
1 row in set, 1 warning (0.00 sec)
-- 注意如果没有现象关闭mysql客户端重新连接
6.4读未提交
几乎没有加锁虽然效率高但是问题太多严重不建议采用。
--终端A
-- 设置隔离级别为 读未提交
mysql set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
--重启客户端
mysql select tx_isolation;
------------------
| tx_isolation |
------------------
| READ-UNCOMMITTED |
------------------
1 row in set, 1 warning (0.00 sec)
mysql select * from account;
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql update account set blance123.0 where id1; --更新指定行
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--没有commit哦
--终端B
mysql begin;
mysql select * from account;
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 123.00 | --读到终端A更新但是未commit的数据[insert
delete同样]
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
一个事务在执行中读到另一个执行中事务的更新(或其他操作)但是未commit的数据这种现象叫做脏读(dirty read)
6.5读提交
-- 终端A
mysql set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
--重启客户端
mysql select * from account; --查看当前数据
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 123.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql begin; --手动开启事务同步的开始终端B事务
Query OK, 0 rows affected (0.00 sec)
mysql update account set blance321.0 where id1; --更新张三数据
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--切换终端到终端B查看数据。
mysql commit; --commit提交
Query OK, 0 rows affected (0.01 sec)
--切换终端到终端B再次查看数据。
--终端B
mysql begin; --手动开启事务和终端A一前一后
Query OK, 0 rows affected (0.00 sec)
mysql select * from account; --终端A commit之前查看不到
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 123.00 | --老的值
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
--终端A commit之后看到了
--but此时还在当前事务中并未commit那么就造成了同一个事务内同样的读取在不同的时间段
--(依旧还在事务操作中)读取到了不同的值这种现象叫做不可重复读(non reapeatable read)
--这个是问题吗
mysql select *from account;
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 321.00 | --新的值
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
终端A在commit之前终端B是看不到的当终端Acommit之后终端B即使本身事务没有结束也是能够看到更新后的值这种现象被称作是不可重复读。这种现象存在的问题是终端A在查询终端B在修改当终端A在查找的过程中终端B修改完成并且提交了这就导致终端A可能会查询到数据不一致的问题。所以针对这种问题MySQL采取了另一种策略就是可重复读。
6.6可重复读
--终端A
mysql set global transaction isolation level repeatable read; --设置全局隔离级别
RR
Query OK, 0 rows affected (0.01 sec)
--关闭终端重启
mysql select tx_isolation;
-----------------
| tx_isolation |
-----------------
| REPEATABLE-READ | --隔离级别RR
-----------------
1 row in set, 1 warning (0.00 sec)
mysql select *from account; --查看当前数据
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql begin; --开启事务同步的终端B也开始事务
Query OK, 0 rows affected (0.00 sec)
mysql update account set blance4321.0 where id1; --更新数据
Query OK, 1 row affected (0.00 sec)
比特就业课
Rows matched: 1 Changed: 1 Warnings: 0
--切换到终端B查看另一个事务是否能看到
mysql commit; --提交事务
--切换终端到终端B查看数据。
--终端B
mysql begin;
Query OK, 0 rows affected (0.00 sec)
mysql select * from account; --终端A中事务 commit之前查看当前表中数据数据未更新
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql select * from account; --终端A中事务 commit 之后查看当前表中数据数据未更新
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
--可以看到在终端B中事务无论什么时候进行查找看到的结果都是一致的这叫做可重复读
mysql commit; --结束事务
Query OK, 0 rows affected (0.00 sec)
mysql select * from account; --再次查看看到最新的更新数据
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
----------------------------------------------------------------
--如果将上面的终端A中的update操作改成insert操作会有什么问题
--终端A
mysql select *from account;
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql begin; --开启事务终端B同步开启
Query OK, 0 rows affected (0.00 sec)
mysql insert into account (id,name,blance) values(3, 王五, 5432.0);
Query OK, 1 row affected (0.00 sec)
--切换到终端B查看另一个事务是否能看到
mysql commit; --提交事务
Query OK, 0 rows affected (0.00 sec)
--切换终端到终端B查看数据。
mysql select * from account;
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
----------------------
3 rows in set (0.00 sec)
--终端B
mysql begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql select * from account; --终端A commit前 查看
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql select * from account; --终端A commit后 查看
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql select * from account; --多次查看发现终端A在对应事务中insert的数据在终端B的事
务周期中也没有什么影响也符合可重复的特点。但是一般的数据库在可重复读情况的时候无法屏蔽其
他事务insert的数据(为什么因为隔离性实现是对数据加锁完成的而insert待插入的数据因为并不存
在那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的但是insert的数据在可重复读
情况被读取出来导致多次查找时会多查找出来新的记录就如同产生了幻觉。这种现象叫做幻读
(phantom read)。很明显MySQL在RR级别的时候是解决了幻读问题的(解决的方式是用Next-Key锁
(GAP行锁)解决的。
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
----------------------
2 rows in set (0.00 sec)
mysql commit; --结束事务
Query OK, 0 rows affected (0.00 sec)
mysql select * from account; --看到更新
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
----------------------
3 rows in set (0.00 sec)
6.7串行化 对所有操作全部加锁进行串行化不会有问题但是只要串行化效率很低几乎完全不会被采用
--终端A
mysql set global transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql select tx_isolation;
----------------
| tx_isolation |
----------------
| SERIALIZABLE |
----------------
1 row in set, 1 warning (0.00 sec)
mysql begin; --开启事务终端B同步开启
Query OK, 0 rows affected (0.00 sec)
mysql select * from account; --两个读取不会串行化共享锁
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
----------------------
3 rows in set (0.00 sec)
mysql update account set blance1.00 where id1; --终端A中有更新或者其他操作会阻
塞。直到终端B事务提交。
Query OK, 1 row affected (18.19 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--终端B
mysql begin;
Query OK, 0 rows affected (0.00 sec)
mysql select * from account; --两个读取不会串行化
----------------------
| id | name | blance |
----------------------
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
----------------------
3 rows in set (0.00 sec)
mysql commit; --提交之后终端A中的update才会提交。
Query OK, 0 rows affected (0.00 sec) 总结 其中隔离级别越严格安全性越高但数据库的并发性能也就越低往往需要在两者之间找一个平衡点。 不可重复读的重点是修改和删除同样的条件, 你读取过的数据,再次读取出来发现值不一样了 幻读的重点在于新增同样的条件, 第1次和第2次读出来的记录数不一样 说明 mysql 默认的隔离级别是可重复读,一般情况下不要修改 上面的例子可以看出事务也有长短事务这样的概念。事务间互相影响指的是事务在并行执行的时候即都没有commit的时候影响会比较大。 可能会出现的问题如图所示 上面关于事务的隔离级别进行了相关介绍并通过实验演示了每一种实现事务隔离的策略所具有的特点其中包含读未提交读提交可重复读以及串行化。其中串行化是最好理解的就是每一个事务按照先后顺序进行执行但是读未提交读提交可重复读是如何实现的以及它们为什么会出现脏读不可重复读以及幻读问题。除了这些之外最开始我们介绍的事务在未提交之前可以进行回滚包含指定回滚和回滚到最开始这些MySQL都是如何实现的呢下面我们就关于这些问题一一为大家揭晓答案。
7.多版本并发控制
多版本并发控制又称MVCC,是MySQL实现上述问题的所采用的策略。 实现方式为事务分配单向增长的事务ID,为每个修改保存一个版本版本与事务ID关联读操作只读该事务开始前的数据库快照。 通过这样的策略实现了在并发读写数据库时可以做到在读操作时不用阻塞写操作写操作不用阻塞读操作提高了数据库并发读写的能力。
相信看完上面的介绍大多数小伙伴是处于懵逼状态的不理解什么是事务ID,以及如何保存一个版本版本与事务ID又是如何实现什么又是数据库快照不过不用担心上面只是对MySQL如何实现多版本并发控制的整体介绍下面我们针对中间出现每个新名词一一为大家介绍相信看完之后再返过来理解上面这句话就会明白了。
7.1 3个记录隐藏列字段 DB_TRX_ID 6 byte最近修改( 修改/插入 )事务ID记录创建这条记录/最后一次修改该记录的事务IDDB_ROLL_PTR : 7 byte回滚指针指向这条记录的上一个版本简单理解成指向历史版本就行这些数据一般在 undo log 中DB_ROW_ID : 6 byte隐含的自增ID隐藏主键如果数据表没有主键 InnoDB 会自动以DB_ROW_ID 产生一个聚簇索引 举例说明
mysql create table if not exists student(name varchar(11) not null,age int not null
);
mysql insert into student (name, age) values (张三, 28);
Query OK, 1 row affected (0.05 sec)
mysql select * from student;
-------------
| name | age |
-------------
| 张三 | 28 |
-------------
1 row in set (0.00 sec)
上面描述的意思为 目前并不知道创建该记录的事务ID隐式主键我们就默认设置成null1。第一条记录也没有其他 版本我们设置回滚指针为null。 7.2undo日志 MySQL 将来是以服务进程的方式在内存中运行。我们之前所讲的所有机制索引事务隔离性日志等都是在内存中完成的即在 MySQL 内部的相关缓冲区中保存相关数据完成各种判断操作。然后在合适的时候将相关数据刷新到磁盘当中的。所以我们这里理解undo log简单理解成就是 MySQL 中的一段内存缓冲区用来保存日志数据的就行。上面介绍的为每个修改保存一个版本保存的位置就是在undo log中。 7.3模拟MVCC的流程
现在有一个事务10对student表中记录进行修改(update)将name(张三)改成name(李四)。 事务10,因为要修改所以要先给该记录加行锁。 修改前现将改行记录拷贝到undo log中所以undo log中就有了一行副本数据。 所以现在 MySQL 中有两行同样的记录。现在修改原始记录中的name改成 李四。并且修改原始 记录的隐藏字段 DB_TRX_ID 为当前 事务10 的ID, 我们默认从 10 开始之后递增。而原始记录的回滚指针 DB_ROLL_PTR 列里面写入undo log中副本数据的地址从而指向副本记录既表示我的上一个版本就是它。 事务10提交释放锁。
如图所示 备注此时最新的记录是’李四‘那条记录。
现在又有一个事务11对student表中记录进行修改(update)将age(28)改成age(38)。 事务11,因为也要修改所以要先给该记录加行锁。该记录是那条 修改前现将改行记录拷贝到undo log中所以undo log中就又有了一行副本数据。此时新的 副本我们采用头插方式插入undo log。 现在修改原始记录中的age改成 38。并且修改原始记录的隐藏字段 DB_TRX_ID 为当前 事务11 的ID。而原始记录的回滚指针 DB_ROLL_PTR 列里面写入undo log中副本数据的地址从而指向副本记录既表示我的上一个版本就是它。 事务11提交释放锁。
如图所示 这样我们就有了一个基于链表记录的历史版本链。所谓的回滚无非就是用历史数据覆盖当前数据。上面的一个一个版本我们可以称之为一个一个的快照。
那么是如何保证不同的事务看到不同的内容是如何实现隔离级别的呢
7.4Read View Read View就是事务进行 快照读 操作的时候生产的 读视图 (Read View)在该事务执行的快照读的那一刻会生成数据库系统当前的一个快照记录并维护系统当前活跃事务的ID(当每个事务开启时都会被分配一个ID, 这个ID是递增的所以最新的事务ID值越大) Read View 在 MySQL 源码中,就是一个类本质是用来进行可见性判断的。 即当我们某个事务执行快照读的时候对该记录创建一个 Read View 读视图把它比作条件,用来判断当前事务能够看到哪个版本的数据既可能是当前最新的数据也有可能是该行记录的 undo log 里面的某个版本的数据。 7.5 RR与RC的本质区别
Read View生成时机的不同从而造成RC,RR级别下快照读的结果的不同 在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来 此后在调用快照读的时候还是使用的是同一个Read View所以只要当前事务在其他事务提交更新之前使用过快照读那么之后的快照读使用的都是同一个Read View所以对之后的修改不可见 即RR级别下快照读生成Read View时Read View会记录此时所有其他活动事务的快照这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见 在RC级别下的事务中每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因 总之在RC隔离级别下是每个快照读都会生成并获取最新的Read View而在RR隔离级别下则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。 正是RC每次快照读都会形成Read View所以RC才会有不可重复读问题。 总结 以上就是关于MySQL中事务的相关概念包含什么是事务为什么存在事务以及如何在MySQL中对事务进行操作并且介绍了事务实现的机制以及每一种策略的实现底层是如何做到的希望能够对大家有所帮助。