13--mysql事务详解,数据库读现象

一、事务介绍

1、什么是事务:

事务(Transaction),顾名思义就是要做的或所做的事情,数据库事务指的则是作为单个逻辑工作单元执行的一系列操作(SQL语句)。这些操作要么全部执行,要么全部不执行。

2、为什么需要事务

把一系列sql放入一个事务中有两个目的:

1、为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
2、当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。

当一个事务被提交给了DBMS(数据库管理系统),则DBMS需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态(要么全执行,要么全都不执行);同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

但在现实情况下,失败的风险很高。在一个数据库事务的执行过程中,有可能会遇上事务操作失败、数据库系统/操作系统失败,甚至是存储介质失败等情况。这便需要DBMS对一个执行失败的事务执行恢复操作,将其数据库状态恢复到一致状态(数据的一致性得到保证的状态)。为了实现将数据库状态恢复到一致状态的功能,DBMS通常需要维护事务日志以追踪事务中所有影响数据库数据的操作。

事务管理是每个数据库(oracle、mysql、db等)都必须实现的。

3、如何使用事务

# 事务相关的关键字

# 1、开启事务
start transaction;

# 2、回滚(回到事务执行之前的状态)
rollback;

# 3、确认(确认之后就无法回滚了)
commit;

# 总结:
当你想让sql语句同时保证数据的一致性,要么同时成功,要么同时失败,那么就可以考虑使用事务

4、一个成功事务的生命周期

start transaction; #begin 开启事务
sql1  #真正事务处理的时候,是第一条sql语句执行完,才是开启事务的
sql2
sql3
...
commit;
# 若用了begin手动开始编辑事务,编辑完后只要commit手动提交,另一端则能查询到数据

6、一个失败事务的生命周期

start transaction;
sql1
sql2
sql3
...
rollback;
# 若用了begin手动开始编辑事务,编辑完后且只要不commit手动提交,另一端则查询不到数据

二、事务的4个特性

这四个特性通常称为ACID特性

# 1、原子性(Atomicity)
事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。

# 2、一致性(Consistency)
事务应确保数据库的状态从一个一致状态转变为另一个一致状态,例如转账行为中,一个人减了50元,另外一个人就应该加上这50元,而不能是40元。
其他一致状态的含义是数据库中的数据应满足完整性约束,例如字段约束不能为负数,事务执行完毕后的该字段也同样不是负数

# 3、隔离性(Isolation)
多个事务并发执行时,一个事务的执行不应影响其他事务的执行。

# 4、持久性(Durability)
一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

三、事务的3种运行模式

隐式 == 自动

显式 == 手动

1、自动提交事务(隐式开启、隐式提交)

此乃mysql默认的事务运行模式

mysql默认为每条sql开启事务,并且会在本条sql执行完毕后自动执行commit提交

# 0.手动提交
mysql>  commit;

#1.查看自动提交
mysql> show variables like 'autocommit';   
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.01 sec)

2、隐式事务(隐式开启、显式提交)

既然mysql默认是为每条sql都开启了事务并且在该sql运行完毕后会自动提交
那么我只需要将自动提交关闭即可变成“隐式开启、显式提交”

#1.临时关闭
set autocommit =0;
show variables like 'autocommit';  -- 查看

#2.永久关闭
[root@db01 ~]# vim /etc/my.cnf
[mysqld]
autocommit=0

3、显式事务(显式开启、显式提交)

手动开启的事务里默认不会自动提交
所以我们可以将要执行的sql语句放在我们自己手动开启的事务里,如此便是显式开启、显式提交
start transaction;

update test.t1 set id=33 where name = "jack";

commit;

# 注意,重要的事!!!
这种方式在当你使用commit或者rollback后,事务就结束了
再次进入事务状态需要再次start transaction

无论事务是显式开启还是隐式开启,事务会在某些情况下被隐式提交

# 隐式提交触发条件
1.执行事务没有commit时,如果使用了DDL或者DCL会自动提交上一条事务
2.执行事务没有commit时,如果你手动执行begin,会自动提交上一条事务
3.执行事务没有commit时,如果执行锁表(lock tables)或者解锁(unlock tables),会自动提交上一条事务
4.load data infile(导数据)会自动提交上一条事务
5.select for update 加锁
6.在autocommit=1的时候,会自动提交上一条事务

四、事务的保存点

savepoint和虚拟机中的快照类似,用于事务中,没设置一个savepoint就是一个保存点,当事务结束时会自动删除定义的所有保存点,在事务没有结束前可以回退到任意保存点

1、设置保存点savepoint 保存点名字
2、回滚到某个保存点,该保存点之后的操作无效,rollback 某个保存点名
3、取消全部事务,删除所有保存点rollback

# 注意:rollback和commit都会结束掉事务,这之后无法再回退到某个保存点

案例

1、设置保存点

mysql> select * from employee;
+----+-----------+-----+
| id | name      | age |
+----+-----------+-----+
|  1 | egon      |  16 |
|  2 | alex      |  18 |
|  3 | wupeiqi   |  18 |
|  4 | yuanhao   |  18 |
|  5 | liwenzhou |  20 |
+----+-----------+-----+
5 rows in set (0.00 sec)

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

mysql> update employee set name="EGON_NB" where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update employee set name="ALEX_SB" where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update employee set name="WXX" where id=3;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> savepoint one;  -- 保存点one
Query OK, 0 rows affected (0.00 sec)

mysql> select * from employee;
+----+-----------+-----+
| id | name      | age |
+----+-----------+-----+
|  1 | EGON_NB   |  16 |
|  2 | ALEX_SB   |  18 |
|  3 | WXX       |  18 |
|  4 | yuanhao   |  18 |
|  5 | liwenzhou |  20 |
+----+-----------+-----+
5 rows in set (0.00 sec)

mysql> update employee set name="yxx_sb" where id=4;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update employee set name="lxx" where id=5;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> savepoint two;  -- 保存点two
Query OK, 0 rows affected (0.00 sec)

mysql> select * from employee;
+----+---------+-----+
| id | name    | age |
+----+---------+-----+
|  1 | EGON_NB |  16 |
|  2 | ALEX_SB |  18 |
|  3 | WXX     |  18 |
|  4 | yxx_sb  |  18 |
|  5 | lxx     |  20 |
+----+---------+-----+
5 rows in set (0.00 sec)

mysql> insert into employee values(6,"egonxxx",19);
Query OK, 1 row affected (0.00 sec)

mysql> savepoint three;  -- 保存点three
Query OK, 0 rows affected (0.00 sec)

mysql> select * from employee;
+----+---------+-----+
| id | name    | age |
+----+---------+-----+
|  1 | EGON_NB |  16 |
|  2 | ALEX_SB |  18 |
|  3 | WXX     |  18 |
|  4 | yxx_sb  |  18 |
|  5 | lxx     |  20 |
|  6 | egonxxx |  19 |
+----+---------+-----+
6 rows in set (0.00 sec)
mysql> insert into employee values(7,"egon666",20);
Query OK, 1 row affected (0.00 sec)

mysql> savepoint four;  -- 保存点four
Query OK, 0 rows affected (0.00 sec)

mysql> select * from employee;
+----+---------+-----+
| id | name    | age |
+----+---------+-----+
|  1 | EGON_NB |  16 |
|  2 | ALEX_SB |  18 |
|  3 | WXX     |  18 |
|  4 | yxx_sb  |  18 |
|  5 | lxx     |  20 |
|  6 | egonxxx |  19 |
|  7 | egon666 |  20 |
+----+---------+-----+
7 rows in set (0.00 sec)

回退到指定保存点,注意一旦回退到某个保存点,该保存点之后的操作都撤销了包括保存点,例如

mysql> rollback to three;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from employee;
+----+---------+-----+
| id | name    | age |
+----+---------+-----+
|  1 | EGON_NB |  16 |
|  2 | ALEX_SB |  18 |
|  3 | WXX     |  18 |
|  4 | yxx_sb  |  18 |
|  5 | lxx     |  20 |
|  6 | egonxxx |  19 |
+----+---------+-----+
6 rows in set (0.00 sec)

mysql> rollback to four;  -- 保存点four不复存在
ERROR 1305 (42000): SAVEPOINT four does not exist

可以跨越n个保存点

mysql> rollback to one;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from employee;
+----+-----------+-----+
| id | name      | age |
+----+-----------+-----+
|  1 | EGON_NB   |  16 |
|  2 | ALEX_SB   |  18 |
|  3 | WXX       |  18 |
|  4 | yuanhao   |  18 |
|  5 | liwenzhou |  20 |
+----+-----------+-----+
5 rows in set (0.00 sec)

回退所有

mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

mysql> select * from employee;
+----+-----------+-----+
| id | name      | age |
+----+-----------+-----+
|  1 | egon      |  16 |
|  2 | alex      |  18 |
|  3 | wupeiqi   |  18 |
|  4 | yuanhao   |  18 |
|  5 | liwenzhou |  20 |
+----+-----------+-----+
5 rows in set (0.00 sec)

事务的控制语句--总结

begin;(或 start transaction;):显式开始一个新事务,推荐begin   #开启事务
savepoint:分配事务过程中的一个位置,以供将来引用		      #临时存档
commit:永久记录当前事务所做的更改				      #提交事务
rollback:取消当前事务所做的更改				      #回滚
roolback to savepoint:取消在 savepoint 之后执行的更改	      #回到存档点
release savepoint:删除 savepoint 标识符			      #删除临时存档
set autocommit:为当前连接禁用或启用默认 autocommit 模式         #临时开关自动提交

PS:永久开启或关闭autocommit,则在配置文件(my.cnf)插入一行:
autocommit=1	# 开启状态
autocommit=0	# 关闭状态

锁的使用:
事务一对id=3的行加了互斥锁之后,其它事务对id=3行不能加任何锁(写不行,但是可以读)
事务一对id=3的行加了共享锁之后,其它事务对id=3行只能加共享锁,或者不加锁(写不行,但可以读)

五、数据库读现象

数据库管理软件的“读现象”指的是当多个事务并发执行时,在读取数据方面可能碰到的问题,包括有脏读、不可重复读和幻读

1、脏读 (dirty read) ==========> 原因 -- 设置安全级别低导致的

一个事务在执行时修改了某条数据,另一个事务正好也读取了这条数据,并基于这条数据做了其他操作,因为前一个事务还没提交,如果基于修改后的数据进一步处理,就会产生无法挽回的损失。

2、不可重复读取 (non-repeatable read)

同样是两个事务在操作同一数据,如果在事务开始时读了某数据,这时候另一个事务修改了这条数据,等事务再去读这条数据的时候发现已经变了,这就是没办法重复读一条数据。

3、幻像读取 (phantom read)

幻读是不可重复读(Non-repeatable reads)的一种特殊场景:
当事务没有获取范围锁的情况下执行SELECT … WHERE操作有可能会发生“幻影读“。

# 1、当设置事务的安全隔离级别低的的时候,相应的安全级别低,处理效率就比较高
# 2、当设置事务的安全隔离级别高的的时候,相应的安全级别高,处理效率就比较低

Mysql默认使用的数据隔离级别是REPEATABLE READ ,可重复读,允许幻读

解决方案:

其实,脏写、脏读、不可重复读、幻读,都是因为业务系统会多线程并发执行,每个线程可能都会开启一个事务,每个事务都会执行增删改查操作。然后数据库会并发执行多个事务,多个事务可能会并发地对缓存页里的同一批数据进行增删改查操作,于是这个并发增删改查同一批数据的问题,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。

所以这些问题的本质,都是数据库的多事务并发问题,那么为了解决多事务并发带来的脏读、不可重复读、幻读等读等问题,数据库才设计了锁机制事务隔离机制、MVCC 多版本隔离机制,用一整套机制来解决多事务并发问题

六、事务的使用原则

  1. 保持事务短小
  2. 尽量避免事务中rollback
  3. 尽量避免savepoint
  4. 显式声明打开事务
  5. 默认情况下,依赖于悲观锁,为吞吐量要求苛刻的事务考虑乐观锁
  6. 锁的行越少越好,锁的时间越短越好
posted @ 2021-07-16 09:10  小绵  阅读(93)  评论(0编辑  收藏  举报