事务
概述:在实际的开发过程中,一个业务操作如:转账,往往是要多次访问数据库才能完成的。转账是一个用户扣 钱,另一个用户加钱。如果其中有一条 SQL
语句出现异常,这条
SQL
就可能执行失败。事务执行是一个整体,所 有的 SQL
语句都必须执行成功。如果其中有
1
条
SQL
语句出现异常,则所有的
SQL
语句都要回滚,整个业务执行 失败。
模拟银行转账:
创建表
CREATE TABLE account (id INT ( 10 ) AUTO_INCREMENT PRIMARY KEY ,name VARCHAR ( 10 ),balance DOUBLE) engine=innodb default charset=utf8;# 添加数据INSERT INTO account (name, balance) VALUES ( ' 张三 ' , 1000 ), ( ' 李四 ' , 1000 );
假设,张三给李四转账200元
张三账号+200
update account set balance = balance - 200 where name= ' 张三 ' ;
李四账号+200
update account set balance = balance + 200 where name= ' 李四 ' ;
数据正常情况下,完成两条语句不会发生任何问题,但是,假设当张三账号上
-200
元
,
服务器崩溃了。李四的账号并没有+200
元,数据就出现问题了。我们需要保证其中一条
SQL
语句出现问题,整个转账就算失败。只有两条
SQL
都成功了转账才算成功。这个时候就需要用到事务。
MySQL中事物的提交方式
原理:事务开启之后
,
所有的操作都会临时保存到事务日志中
,
事务日志只有在得到
commit
命令才会同步到数据表中,其他任何情况都会清空事务日志(rollback
,断开连接
)
①手动提交
SQL
语句:
功能 | SQL语句 |
---|---|
开启事务 | start transaction; |
提交事务 | commit; |
回滚事务 | rollback; |
②手动提交
手动提交事务
执行过程:开启事务
-->
执行多条
SQL
语句
-->
成功提交事务
(
失败事务的回滚
)
如下图所示:
代码展示:
数据库内容
提交事务步骤:
①使用
DOS
控制台进入
MySQL;
②执行以下
SQL
语句:
1.
开启事务,
2.
张三账号
-200
,
3.
李四账号
+200;
③ 使用
Navicat
查看数据库:发现数据并没有改变
;
④在控制台执行
commit
提交事务
;
⑤使用
Navicat
查看数据库:发现数据改变
.
过程截图:
①
Win+R
进入
cmd
,输入如下命令进入数据库
②开启事务
③选择数据库并执行SQL语句
④查询数据库中数据
⑤通过Navicat工具查看表中数据
上述内容可以发现,两条语句还没有提交,只是执行完成了
⑥提交之后查看数据
⑦通过navicat工具查看表中数据
回滚事务步骤:
①在控制台执行以下
SQL
语句:
1.
开启事务,
2.
张三账号
-200
,李四账号
+200;
②使用
Navicat
查看数据库:发现数据并没有改变
;
③在控制台执行
rollback
回滚事务
;
④在
dos
界面查看数据库:发现数据回滚到开启事务时的状态;使用
Navicat
查看数据库:发现数据没有改变
.
过程截图:
数据库内容
执行过程
navicat结果展示
回滚事务
事务总结
:
如果事务中
SQL
语句没有问题,
commit
提交事务,会对数据库的数据进行改变。 如果事务中
SQL
语句有问题,rollback
回滚事务,会回退到开启事务时的状态。
MySQL
中,默认自动提交事务。
事务回滚点
在某些成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都已经成功,可以在当前成功的位置设置一个回滚点。可以供后续失败操作返回到该位置,而不是返回所有操作,这个点称之为回滚点。
回滚点
SQL
语句
设置回滚点 savepoint 名字
回到回滚点 rollback to 名字
代码演示:
①将数据还原到
1000
,开启事务
②让张三账号减
3
次钱,每次
10
块
③设置回滚点:
savepoint three_times;
④让张三账号减
3
次钱,每次
10
块
⑤回到回滚点:
rollback to three_times;
事务的四大特征(ACID)
特征 | 含义 |
---|---|
原子性(atomicity) |
每个事务都是一个整体,不可再拆分,事务中所有的
SQL
语句要么都执行成功, 要么都失败。
|
一致性(Consistency ) |
事务在执行前数据库的状态与执行后数据库的状态保持一致。
|
隔离性(isolation) |
事务与事务之间不应该相互影响,执行时保持隔离的状态。
|
持久性(
Durability
) |
一旦事务执行成功,对数据库的修改是持久的。就算关机,也是保存下来的
|
事务的隔离级别
事务在操作时的理想状态:所有的事务之间保持隔离,互不影响。因为并发操作,多个用户同时访问同一个数据。可能引发并发访问
的问题:
并发的访问问题 | 含义 |
---|---|
脏读 |
一个事务读取到了另一个事务中尚未提交的数据
|
不可重复读 |
一个事务先后执行同一条
SQL
,但两次读取到的数据不同,就是不可重复读,这是事务 update 时引发的问题
|
幻读 |
在一个事务中明明没有查到主键为
X
的数据,但主键为
X
的数据就是插入不进去,这是insert 或
delete
时引发的问题
|
不可重复读和脏读的区别
脏读可以读到其他事务中未提交的数据,而不可重复读是读取到了其他事务已经提交的数据,但前后两次读取的结果不同。
不可重复读和幻读的区别:
二者描述的侧重点不同,不可重复读描述的侧重点是修改操作,而幻读描述的侧重点是添加或删除操作
MySQL
中隔
离级别可能引发的问题。
在
MySQL
中事务的隔离级别有以下
4
种:
1.
读未提交(
READ UNCOMMITTED
)
2.
读已提交(
READ COMMITTED
)
3.
可重复读(
REPEATABLE READ
)
4.
序列化(SERIALIZABLE)
READ UNCOMMITTED
:
读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
READ COMMITTED
:
读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL
查询中,可能会得到不同的结果,这种现象叫做不可重复读。
REPEATABLE READ
:
可重复读,是
MySQL
的默认事务隔离级别,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读。
SERIALIZABLE
:
序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多
事务隔离级别
| 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted)
| 是 | 是 | 是 |
读已提交(read-committed)
| 否 | 是 | 是 |
可重复读(repeatable-read)
| 否 | 否 | 是 |
串行化(serializable)
| 否 | 否 | 否 |
测试:
查看事务隔离级别:
SELECT @@GLOBAL .tx_isolation , @@tx_isolation;
设置事务隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
读未提交:
删除
account
数据表并重建:
drop table account;CREATE TABLE account (id INT ( 10 ) AUTO_INCREMENT PRIMARY KEY ,name VARCHAR ( 10 ),balance DOUBLE) engine=innodb default charset=utf8;
初始化两条数据:
INSERT INTO account (name, balance) VALUES ( ' 张三 ' , 1000 ), ( ' 李四 ' , 1000 );
两个人的账户各有
1000
人民币,现在模拟这两个用户之间的一个转账操作。