MySQL 基础 事务

什么是mysql的事务

  MySQL 事务主要用于处理操作量大,复杂度高的数据。简单的说,事务就是一连串的DML的sql语句组合在一起,所以语句执行成功才算成功,如果有语句执行失败,执行就不成功 。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

  注意:mysql只有Innodb存储引擎才支持事务。DML之的是数据库管理语句:insert, update, delete语句。

事务的特性

原子性(Atomicity):一个事务的所有操作,要么全部都完成,要么全部都不完成,不会再在中间的环节结束。如果其中一个地方发生了错误,会回滚到开始的地方,即该事务的语句一个都没有执行。

一致性(Consistency):在事务开始之前和事务结束之后,数据库的完整性没有被破坏,即修改的数据完全符合预期,例如从某个表减去一些数据,并将减去的数据加到另一表,这整个过程不会出现其他多余的数据,也不会突然减少数据。

隔离性(lsolation):数据库可以多个事务并行执行,相互之间不影响,隔离性可以放在多个事务并发执行时由于交叉执行而导致的数据不一致的问题。而隔离性的隔离效果是有等级的。在接下来回对隔离级别产生的影响和解决方法进行详述。

永久性(Durability):事务处理后,对数据的修改是永久的,即便系统故障也不会丢失。

事务的简单使用:

若要进行事务处理,必须先把MySQL的提交方式更改为手动提交,而不是自动提交。自动提交是指:MySQL的每一次语句执行就进行提交,而手动提交就是,我们可以同时执行多个语句然后一起提交。

查看提交方式:

设置提交方式:

然后我们就可以进行事务的实例训练了:

实例1:commit方式提交

mysql> select * from student;  // 事务开始前的数据
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 老汪   |   25 |
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
+----+--------+------+
4 rows in set (0.00 sec)

mysql> start transaction;  // 开始事务
Query OK, 0 rows affected (0.00 sec)

mysql> insert into student values(5, '老个', 22);  // 数据操作
Query OK, 1 row affected (0.00 sec)

mysql> commit;      // 事务接受,也就是提交
Query OK, 0 rows affected (0.60 sec)

mysql> select * from student;  // 事务介绍后的数据
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 老汪   |   25 |
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
|  5 | 老个   |   22 |
+----+--------+------+
5 rows in set (0.00 sec)

从上面看来和普通的方式没有什么区别,接下来是rollback方式进行提交:

实例2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into student values(6, '情感', 22);
Query OK, 1 row affected (0.00 sec)

mysql> rollback;  // 事务回滚
Query OK, 0 rows affected (0.00 sec)

mysql> select * from student;  // 查询发现数据不变,没有插入
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 老汪   |   25 |
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
|  5 | 老个   |   22 |
+----+--------+------+
5 rows in set (0.00 sec)

从上面两个简单的事务可以看出:rollback提交是会将所有事务中的执行失效,回到事务开始前的状态。

注意:假如使用commit的方式提交,若事务中的sql语句有一句出错了,其他的没错,commit 提交后,除了出错的语句,其他的语句一样有效。

例如3:

mysql> select * from student;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 老汪   |   25 |
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
|  5 | 老个   |   22 |
+----+--------+------+
5 rows in set (0.00 sec)

mysql>
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into student values(7,'老人', 22);
Query OK, 1 row affected (0.00 sec)

mysql> insert into student values('情感', 22);  // 插入错语句
ERROR 1136 (21S01): Column count doesn't match value count at row 1
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from student; // 事务中的语句仍然成功
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 老汪   |   25 |
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
|  5 | 老个   |   22 |
|  7 | 老人   |   22 |
+----+--------+------+
6 rows in set (0.00 sec)

注意:如果不在进行事务处理,需要把手动提交给关闭改为自动提交。

隔离性的隔离级别和可能出现的问题

隔离的级别:

读取未提交(read-uncommited):
    事务A为提交的数据,事务B可以读取到,会导致脏数据
    离级别最低:这一级别一般是理论上存在,数据库的一般级别都会高于该级别。
读取已提交:事务A和事务B,事务A提交的数据,事务B可以读取到。可以避免脏数据的产生。但是会导致
'不可重复读取的问题'
可重复读取:事务A和事务B,事务A提交之后的数据,事务B读取不到,事务B是可重复读取数据,这种隔离级别高于读已提交,换句话说
  ,对方提交之后的数据,我还是读取不到,这种隔离级别可以避免“不可重复读取”,达到可重复读取,比如1点和2点读到数据是同一个, MySQL默认级别,虽然可以达到可重复读取,但是会导致“幻像读”
串行化:事务A和事务B,事务A在操作数据库时,事务B只能排队等待,这种隔离级别很少使用,吞吐量太低,用户体验差,这种级别可以避免“幻像读”,每一次读取的都是数据库中真实存在数据,事务A与事务B串行,而不并发

导致的问题解释:

  1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

  3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

  小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

 

事务隔离级别 脏读 不可重复读 幻读
读取未提交(read-uncommitted)
读取已提交(不可重复读取)(read-committed)
可重复读(repeatable-read)(默认)
串行化(serializable)

接下来就为大家演示一下什么是脏数据,不可重复读和幻读:

首先要对隔离级别进行设置,默认的是可重复读取:   这个数据可以在配置文件进行配置:

查看方式:

mysql> show variables like '%isolation%';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 1 warning (0.00 sec)

对隔离级别进行设置:

mysql> set session transaction isolation level read uncommitted;   // 对当前的事务进行设置,而不是全局  set global transaction isolation level read uncommitted; 这是全局设置
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like '%isolation%';
+-----------------------+------------------+
| Variable_name         | Value            |
+-----------------------+------------------+
| transaction_isolation | READ-UNCOMMITTED |
+-----------------------+------------------+
1 row in set, 1 warning (0.00 sec)

 

出现问题一,脏数据:

第一个客户端进行事务操作,但不提交:事务A

mysql> show variables like '%isolation%';
+-----------------------+------------------+
| Variable_name         | Value            |
+-----------------------+------------------+
| transaction_isolation | READ-UNCOMMITTED |
+-----------------------+------------------+
1 row in set, 1 warning (0.00 sec)

mysql>
mysql>
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)

mysql>
mysql>
mysql>
mysql> begin; // 事务开始
Query OK, 0 rows affected (0.00 sec)

mysql> select * from student;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 老汪   |   25 |
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
|  5 | 老个   |   22 |
|  7 | 老人   |   22 |
|  8 | 老杨   |   22 |
+----+--------+------+
7 rows in set (0.00 sec)

mysql> insert into student values(9, 'wallace', 33);  // 插入数据,但未提交
Query OK, 1 row affected (0.00 sec)

第二个客户端进行数据查询:客户端B

mysql> use school;
Database changed
mysql> select * from student;
+----+---------+------+
| id | name    | age  |
+----+---------+------+
|  1 | 老汪    |   25 |
|  2 | peilin  |   20 |
|  3 | 微微    |   21 |
|  4 | 老晴    |   22 |
|  5 | 老个    |   22 |
|  7 | 老人    |   22 |
|  8 | 老杨    |   22 |
|  9 | wallace |   33 |  ===>这条数据是事务A的数据,但A并未提交,B还是查询出来了,这就是脏数据。
+----+---------+------+
8 rows in set (0.00 sec)

出现问题二,不可重复读取:

 一,解决脏读问题:

  客户端A,设置全局为read committed 查询数据

mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from student; // 客户端B在事务跟新前
+----+------------+------+
| id | name       | age  |
+----+------------+------+
|  1 | yangyang   |   25 |
|  2 | peilin     |   20 |
|  3 | 微微       |   21 |
|  4 | 老晴       |   22 |
|  5 | 老个       |   22 |
|  7 | 老人       |   22 |
|  8 | 老杨       |   22 |
|  9 | wallace    |   33 |
| 10 | wace       |   33 |
| 11 | 老五年     |   33 |
| 12 | 老年       |   33 |
| 13 | 老大法师年 |   33 |
| 14 | aaa        |  333 |
+----+------------+------+
13 rows in set (0.00 sec)

mysql> select * from student; // 客户端B在事务跟新为提交之前
+----+------------+------+
| id | name       | age  |
+----+------------+------+
|  1 | yangyang   |   25 |
|  2 | peilin     |   20 |
|  3 | 微微       |   21 |
|  4 | 老晴       |   22 |
|  5 | 老个       |   22 |
|  7 | 老人       |   22 |
|  8 | 老杨       |   22 |
|  9 | wallace    |   33 |
| 10 | wace       |   33 |
| 11 | 老五年     |   33 |
| 12 | 老年       |   33 |
| 13 | 老大法师年 |   33 |
| 14 | aaa        |  333 |
+----+------------+------+
13 rows in set (0.00 sec)

客户端B:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update student set name='aixs' where id=1; // 更新B,但在A中未查询到脏数据。
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from student;
+----+------------+------+
| id | name       | age  |
+----+------------+------+
|  1 | aixs       |   25 |
|  2 | peilin     |   20 |
|  3 | 微微       |   21 |
|  4 | 老晴       |   22 |
|  5 | 老个       |   22 |
|  7 | 老人       |   22 |
|  8 | 老杨       |   22 |
|  9 | wallace    |   33 |
| 10 | wace       |   33 |
| 11 | 老五年     |   33 |
| 12 | 老年       |   33 |
| 13 | 老大法师年 |   33 |
| 14 | aaa        |  333 |
+----+------------+------+
13 rows in set (0.00 sec)

二,不可重复读取:

对B中的事务进行提交:查询客户端A的数据,发现和之前的不一样这就是不可重复读取

mysql> select * from student;
+----+------------+------+
| id | name       | age  |
+----+------------+------+
|  1 | aixs       |   25 |   ====》变更的值
|  2 | peilin     |   20 |
|  3 | 微微       |   21 |
|  4 | 老晴       |   22 |
|  5 | 老个       |   22 |
|  7 | 老人       |   22 |
|  8 | 老杨       |   22 |
|  9 | wallace    |   33 |
| 10 | wace       |   33 |
| 11 | 老五年     |   33 |
| 12 | 老年       |   33 |
| 13 | 老大法师年 |   33 |
| 14 | aaa        |  333 |
+----+------------+------+
13 rows in set (0.00 sec)

出现问题三,幻读:

一,可重复读取: 客户端A对age更新,事务并提交

  mysql> set global transaction isolation level repeatable read;
  Query OK, 0 rows affected (0.00 sec)


mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from student; +----+--------+------+ | id | name | age | +----+--------+------+ | 1 | quert | 20 | | 2 | peilin | 20 | | 3 | 微微 | 21 | | 4 | 老晴 | 22 | +----+--------+------+ 4 rows in set (0.00 sec) mysql> update student set age = age -5 where id =1; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.00 sec) mysql> select * from student; +----+--------+------+ | id | name | age | +----+--------+------+ | 1 | quert | 15 | | 2 | peilin | 20 | | 3 | 微微 | 21 | | 4 | 老晴 | 22 | +----+--------+------+ 4 rows in set (0.00 sec)

客户端B,在事务是否提交时进行数据查询,数据都一致:

mysql> select * from student;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | quert  |   20 |
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
+----+--------+------+
4 rows in set (0.00 sec)

mysql> select * from student;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | quert  |   20 |  ====》提交后数据仍然不变,可重复读取
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
+----+--------+------+
4 rows in set (0.00 sec)

二,幻读

客户端A 进行事务,提交插入数据

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into student values(18, 'aa', 33);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from student;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | quert  |   15 |
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
| 18 | aa     |   33 |
+----+--------+------+

客户端B:但是在事务B中无法查询出新增的数据,就好像什么都没有方式,这就是幻读

mysql> select * from student;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | quert  |   15 |
|  2 | peilin |   20 |
|  3 | 微微   |   21 |
|  4 | 老晴   |   22 |
+----+--------+------+
4 rows in set (0.00 sec)

四,串行化

客户端A,进行事务,但不提交

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account;
+------+--------+---------+
| id   | name   | balance |
+------+--------+---------+
|    1 | lilei  |   10000 |
|    2 | hanmei |   10000 |
|    3 | lucy   |   10000 |
|    4 | lily   |   10000 |
+------+--------+---------+
4 rows in set (0.00 sec)

客户端B,进行事务操作,不能执行,会报错,因为是串行化,A事务必须先结束。B才可以执行。

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into account values(5,'tom',0);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

 

补充:

  1、事务隔离级别为读提交时,写数据只会锁住相应的行

  2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。

  3、事务隔离级别为串行化时,读写数据都会锁住整张表

   4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

   5、MYSQL MVCC实现机制参考链接:https://blog.csdn.net/whoamiyang/article/details/51901888

   6、关于next-key 锁可以参考链接:https://blog.csdn.net/bigtree_3721/article/details/73731377

 

posted @ 2019-02-24 16:47  他山之石·玉  阅读(314)  评论(0编辑  收藏  举报