关于mysql:MYSQL事务详解

53次阅读

共计 7725 个字符,预计需要花费 20 分钟才能阅读完成。

学习一个货色,还是先从其概念开始,第一个问题:首先先看看什么是事务?

数据库事务是拜访并可能操作各种数据项的一个数据库操作序列,这些操作要么全副执行,要么全副不执行。

从这个概念看,事务就是一组 SQL,而且这组 SQL 要么全副执行,要么全副不执行。

那么第二个常见的问题,事务有哪些个性(点)呢?这是一个很常见的考题。

事务的四大个性:A(Atomicity)C(Consistency)I(Isolation)D(Durability),如何了解这四个个性呢?

1. 原子性(Atomicity):事务开始后的所有操作,要么全副做完,要么全副不做,不可能做一半的状况,如果事务执行过程中出错,会回滚到事务开始前的状态,也就是事务是一个不可分割的整体。

2. 一致性(Consistency):事务开始前和完结后,数据库的完整性束缚没有被毁坏。比方 A 向 B 转账,A 扣了钱,B 就肯定会收到,不存在 A 这边减了,B 那边没有加的状况。

3. 隔离性(Isolation):同时产生的事务(并发事务)不应该导致数据库处于不统一的状态中。零碎中每个事务都应该像惟一事务一样执行。任何事务都不应该影响其余事务的存在。如果 A 向 B 转账的同时,A 齐全转移所有的钱,两个事务应该独立进行,在进行转账前要确认好余额。对于隔离性,前面会具体介绍。

4. 持久性(Durability)事务实现后,事务对数据库的所有更新将被保留到数据库(磁盘),不能回滚,也能够了解为:无论 db 或零碎是否故障,数据都会永恒保留在磁盘上,不会丢。

那 Mysql 是否反对事务,Mysql 有多种存储引擎,包含 MyISAM, Innodb, Memory, Merge 等。这其中,Innodb 和 BDB 是反对事务的,MyISAM 不反对事务。

Mysql 如何执行事务呢?
1. 启动事务

mysql> start transaction;
或者
mysql> begin;

2. 执行你心愿在事务中运行的 sql 语句
3. 执行 commit 语句,实现事务并提交数据

示例,假如有一张表 student:

MySQL [test]> select * from student;
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  30 |
|  2 | zhangsan |  28 |
|  3 | lisi     |  28 |
+----+----------+-----+

咱们来执行一个事务,将 zhangsan 的年龄改成 22,具体语句如下:

# 会话(事务)1
MySQL [test]> begin;
Query OK, 0 rows affected (0.01 sec)

MySQL [test]> 
MySQL [test]> 
MySQL [test]> 
MySQL [test]> 
MySQL [test]> update student set age = 22 where id = 2;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MySQL [test]> select * from student;
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  30 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
+----+----------+-----+
3 rows in set (0.01 sec)

此时咱们在会话 1 中没有执行 commit,而后咱们在另一个会话窗口 2 查问下 id= 2 的学生记录,年龄依然是 28(只管在以后事务会话(会话 1)中能查到最新数据 22),因为前一个会话并没有 commit(Mysql 默认的事务隔离级别是可反复读),所以咱们读的依然是事务执行完之前的数据。

# 会话(事务)2
MySQL [test]> select * from student;
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  30 |
|  2 | zhangsan |  28 |
|  3 | lisi     |  28 |
+----+----------+-----+
3 rows in set (0.01 sec)

当我将会话窗口 1 执行 commit 后,此时会话 2 的查问后果如下:

# 会话(事务)1
MySQL [test]> commit;
Query OK, 0 rows affected (0.00 sec)
# 会话(事务)2
MySQL [test]> select * from student;
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  30 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
+----+----------+-----+
3 rows in set (0.01 sec)

以上就是一个事务的执行过程。如果咱们想停止这个事务,不提交的话,能够应用 rollback:

MySQL [test]> begin;
Query OK, 0 rows affected (0.01 sec)

MySQL [test]> update student set age = 32 where id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MySQL [test]> select * from student where id = 1;
+----+-------+-----+
| id | name  | age |
+----+-------+-----+
|  1 | clark |  32 |
+----+-------+-----+
1 row in set (0.01 sec)

MySQL [test]> rollback;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> select * from student where id = 1;
+----+-------+-----+
| id | name  | age |
+----+-------+-----+
|  1 | clark |  30 |
+----+-------+-----+
1 row in set (0.00 sec)

PS:聊聊 autocommit
默认状况下,autocommit 的状态是 ON,这意味着所有独自的语句一旦被执行就会被提交,除非该语句在 begin….commit 语句块中。想要查看 autocommit 值,能够应用:

MySQL [test]> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)

如果 autocommit 的状态为 off,则须要明确收回 commit 语句来提交事务。要禁用 autocommit,能够用上面命令:

MySQL [test]> set autocommit = 0;

PS: DDL 语句,如 create, alter, drop 语句,都是无奈 rollback 的。

聊完事务的概念及事务的用法之后,对于事务,还有一个很常见的问题:事务的隔离级别,这也是个惯例考题。

事务的隔离级别,形容的是两个或多个事务同时产生时,一个事务与其余事务在资源或数据批改方面的隔离水平。事务的隔离级别有 4 个,别离是读未提交(read uncommitted),读已提交(read committed),可反复读(repeatable read),序列化(serializable)。

后面咱们提过 MYSQL 默认的事务隔离级别是:可反复读(repeatable read),如何批改隔离级别呢?能够应用 set @@transaction_isolation=’xxx’;来批改事务的隔离级别。

那接下来我来别离具体说说四个事务隔离级别:

1. 读未提交(read uncommitted):顾名思义,事务能够读取另一个未提交的事务写入的数据,读未提交也被称为脏读(因为它会导致脏读)。

咱们举个例子,

如果老板给你发工资,转的金额本来应该是 2w,然而不小心手抖输错了数字,输成了 2.5w,该钱已打到你的账户,然而事务还没有提交,然而此时,你去查看本人的账户,发现多了 5k 块,窃喜认为涨工资了,老板及时发现不对,马上回滚了事务,将数字改成了 2w,再 commit,你就蒙圈了,刚刚看了还是 2.5w 的,这就呈现了脏读

具体查问举例:

# 会话 1 -- 会话 1 没有 commit
MySQL [test]> select * from student;
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  30 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
+----+----------+-----+
3 rows in set (0.00 sec)

MySQL [test]> begin;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> update student set age = 29 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
# 会话 2
MySQL [test]> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> 
MySQL [test]> 
MySQL [test]> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)

MySQL [test]> select * from student;
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  29 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
+----+----------+-----+
3 rows in set (0.01 sec)

在会话 2 中能看到会话 1 未提交的事务,这种状况就是读未提交。读未提交会呈现脏读(一旦会话 1 事务被 rollback)

2. 读已提交(read committed):以后事务只能读取另一个事务提交的数据,这被称为不可反复读(non-repeatable read), 因为它会导致不可反复读。

具体举例:

# 会话 1
MySQL [test]> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.01 sec)

MySQL [test]> begin;
Query OK, 0 rows affected (0.01 sec)

MySQL [test]> select * from student;
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  33 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
|  0 | wangwu   |  45 |
|  0 | zhaoliu  |  22 |
+----+----------+-----+
5 rows in set (0.01 sec)

MySQL [test]> update student set age = 25 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MySQL [test]> commit;
Query OK, 0 rows affected (0.01 sec)
# 会话 2
MySQL [test]> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> begin;
Query OK, 0 rows affected (0.01 sec)

MySQL [test]> select * from student;  // 会话(事务)1--commit 前 
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  33 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
|  0 | wangwu   |  45 |
|  0 | zhaoliu  |  22 |
+----+----------+-----+
5 rows in set (0.01 sec)

MySQL [test]> select * from student; // 会话(事务)1--commit 后
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  25 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
|  0 | wangwu   |  45 |
|  0 | zhaoliu  |  22 |
+----+----------+-----+
5 rows in set (0.01 sec)

能够留神到,在同一个事务会话 2 中,雷同的 select 语句获取不同的后果,一次 age 等于 30, 一次 age 等于 29。所以呈现不可反复读

3. 可反复读(repeatable read)

一个事务通过第一条语句只能看到雷同的数据,即便另一个事务已提交数据。在同一个事务中,读取通过第一次读取建设快照是统一的。一个例外,一个事务能够读取在同一个事务中更改的数据。

当事务开始并执行第一次读取数据时,将创立读取视图并放弃关上状态,直到事务完结。为了在事务完结之前提供雷同的后果集,InnoDB 应用行版本控制和 UNDO 信息。假如事务 1 抉择了几行,另一个事务删除了这些行并提交了数据。如果事务 1 处于关上状态,它应该可能看到本人在开始时抉择的行。已被删除的行保留在 UNDO 日志空间中以履行事务 1。一旦事务 1 操作实现,那些行便被标记为从 UNDO 日志中删除。这称为多版本并发管制(MVCC)。

具体举例:

# 会话 1
MySQL [test]> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> 
MySQL [test]> 
MySQL [test]> 
MySQL [test]> begin;
Query OK, 0 rows affected (0.00 sec)

MySQL [test]> select * from student;
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  25 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
|  0 | wangwu   |  45 |
|  0 | zhaoliu  |  22 |
+----+----------+-----+
5 rows in set (0.01 sec)

MySQL [test]> update student set age = 27 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MySQL [test]> commit;
Query OK, 0 rows affected (0.00 sec)
# 会话 2
MySQL [test]> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.01 sec)

MySQL [test]> begin;
Query OK, 0 rows affected (0.01 sec)

MySQL [test]> select * from student; // 会话(事务)1 commit 前
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  25 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
|  0 | wangwu   |  45 |
|  0 | zhaoliu  |  22 |
+----+----------+-----+
5 rows in set (0.01 sec)

MySQL [test]> select * from student; // 会话(事务)1 commit 后
+----+----------+-----+
| id | name     | age |
+----+----------+-----+
|  1 | clark    |  25 |
|  2 | zhangsan |  22 |
|  3 | lisi     |  28 |
|  0 | wangwu   |  45 |
|  0 | zhaoliu  |  22 |
+----+----------+-----+
5 rows in set (0.01 sec)

可反复读保障了在一个事务中,无论怎么查,后果都是统一,不受其余事务是否 commit 影响。从上例能够看出,事务 1 提交前后,事务 2 的查问后果都不变。

这就是咱们后面说的:当事务开始并执行第一次读取数据时,将创立读取视图并放弃关上状态,直到事务完结。为了在事务完结之前提供雷同的后果集。

从下面后果看,可反复读会产生幻读。那下面看下来,脏读,不可反复读,幻读有点像。那区别在哪呢?

脏读 是指一个事务 取到了 其余事务没有提交 的数据,
不可反复读 是指一个事务内屡次依据同一个查问条件查问进去的 同一行记录 的值不一样
幻读 是指一个事务内屡次依据同个条件查出来的 记录行数 不一样

大家弄懂了几个的区别吗?如果没懂,能够细细品上述加粗字体

因为可反复读会产生幻读,于是就有第四个事务隔离级别:序列 / 串行化(serializable)

通过把选定的所有行锁起来,序列化能够提供最高级别的隔离。此级别与 REPEATABLE READ 相似,但如果禁用 autocommit,则 InnoDB 会将所有一般 SELECT 语句隐式转换为 SELECT…LOCKIN SHARE MODE;如果启用 autocommit,则 SELECT 就是它本人的事务,并且序列化会期待被锁的行,总是读取最新提交的数据。

串行化,顾名思义,是将所有读写操作齐全串行。
串行化是所有隔离级别中最高的
每次读都须要取得表级共享锁,读写互相都会阻塞
串行化对资源的开销大,对并发反对不好,只在,某些场景下应用。

具体例子就不细列了,能够本人 google

那么下面讲了这么多,当初来总结一下,以下是各个隔离级别的对照表:

隔离级别 脏读 不可反复读 幻读
Read Uncommitted 可能 可能 可能
Read Committed 不可能 可能 可能
Repeatable Read 不可能 不可能 可能
Serializable 不可能 不可能 不可能

正文完
 0