上一篇:11【事务处理】
下一篇:13【触发器】
并发访问下事务产生的问题:
当同时有多个用户在访问同一张表中的记录,每个用户在访问的时候都是一个单独的事务。
事务在操作时的理想状态是:事务之间不应该相互影响,实际应用的时候会引发下面三种问题。应该尽量避免这些问题的发生。通过数据库本身的功能去避免,设置不同的隔离级别。
级别 | 名字 | 隔离级别 | 脏读 | 不可重复读 | 幻读 | 数据库默认隔离级别 |
---|---|---|---|---|---|---|
1 | 读未提交 | read uncommitted | 是 | 是 | 是 | |
2 | 读已提交 | read committed | 否 | 是 | 是 | Oracle和SQL Server |
3 | 可重复读 | repeatable read | 否 | 否 | 是/否 | MySQL |
4 | 串行化 | serializable | 否 | 否 | 否 |
1)Read uncommitted
(读未提交): 简称RU隔离级别,所有事务中的并发访问问题都会发生,可以读取到其他事务没有提交的数据
2)Read committed
(读已提交):简称RC隔离级别,会引发不可重复读和幻读的问题,读取的永远是其他事务提交的数据
3)Repeatable read
(可重复读):简称RR隔离级别,会引发幻读的问题,一次事务读取到的同一行数据,永远是一样
4)Serializable
(串行化): 可以避免所有事务产生的并发访问的问题 效率及其低下
查询全局事务隔离级别
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql>
设置全局事务隔离级别
set global transaction isolation level 四种隔离; -- 服务器只要不关闭一直有效
修改隔离级别后需要重启会话
在并发情况下,一个事务读取到另一个事务没有提交的数据,这个数据称之为脏数据,此次读取也称为脏读。
只有read uncommitted
(读未提交)的隔离级别才会引发脏读。
read committed
(读已提交):mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
将数据还原:
session-01 | session-02 |
---|---|
begin; | |
begin; | |
select * from account where name=‘a’; | |
update account set money=money-200 where name=‘a’; | |
select * from account where name=‘a’; | |
rollback; |
观察变化:
将全局的隔离级别进行提升
set global transaction isolation level read committed;
再次执行:
session-01 | session-02 |
---|---|
begin; | |
begin; | |
select * from account where name=‘a’; | |
update account set money=money-200 where name=‘a’; | |
select * from account where name=‘a’; | |
rollback; |
观察变化:
概念: 在同一个事务中的多次查询应该出现相同的结果,两次读取不能出现不同的结果。
脏读是读取前一事务未提交的脏数据,不可重复读是重复读取了前一事务已提交的数据,但2次读取的结果不同。
应用场景:比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,两次输出结果却不一致,导致文件和屏幕中的结果不同,银行工作人员就不知道以哪个为准了。
1). 将数据进行恢复,并关闭窗口重新登录。
update account set money=1000;
session-01 | session-02 |
---|---|
begin; | |
begin; | |
select * from account where name=‘a’; | |
update account set money=money-200 where name=‘a’; | |
commit; | |
select * from account where name=‘a’; |
观察变化:
两次查询输出的结果不同,到底哪次是对的?
1)将数据进行恢复
update account set money=1000;
-- 设置隔离级别为repeatable read
set global transaction isolation level repeatable read;
记得要重启窗口
session-01 | session-02 |
---|---|
begin; | |
begin; | |
select * from account where name=‘a’; | |
update account set money=money-200 where name=‘a’; | |
commit; | |
select * from account where name=‘a’; |
观察变化:
结论:为了保存多次查询数据一致,必须使用repeatable read
隔离级别
概念:一次事务多次读取到的条数不一致而引发的问题;
在InnoDB(暂时理解是MySQL)中幻读在很多地方都得到了解决,但在一些特殊的情况下,还是会引发幻读问题;
为什么有的情况下能解决,有的情况下解决不了?因为一次事务多次读取到的条数不一致会导致有很多情况发生!
还原数据:
update account set money=1000;
-- 设置隔离级别为repeatable read
set global transaction isolation level repeatable read;
记得重启客户端
session-01 | session-02 |
---|---|
begin; | |
begin; | |
select * from account; | |
insert into account values(3,‘c’,1000); | |
commit; | |
select * from account; |
观察变化:
幻读问题得到解决
还原数据
案例:
session-01 | session-02 |
---|---|
begin; | |
begin; | |
select sum(money) from account; | |
insert into account values(3,‘c’,1000); | |
commit; | |
select sum(money) from account; |
观察变化:
还原数据
session-01 | session-02 |
---|---|
begin; | |
begin; | |
select count(id) from account; | |
insert into account values(3,‘c’,1000); | |
commit; | |
select count(id) from account; | |
update account set money=0; | |
select count(id) from account; |
观察变化:
还原数据
session-01 | session-02 |
---|---|
begin; | |
begin; | |
select * from account; | |
select * from account; | |
insert into account values(3,“c”,1000); | |
commit; | |
select * from account; | |
insert into account values(3,“c”,1000); |
观察变化:
Tips:严格意义来说,上述案例是MySQL的快照机制导致的,不能算幻读;关于幻读我们理解概念就行,即:两次读取到的条数不一致!这就是幻读
想要彻底的解决幻读,那么我们必须再把隔离级别调高,数据库的最高隔离级别为串行化(serializable)
串行化相当于锁表操作,即一个事务如果操作了某张表(增加、删除、修改),那么就不允许其他任何事务操作此表,也不允许查询,等第一个事务提交或者回滚之后才可以操作,这样做效率及其低下,因此一般不会采用serializable
隔离级别
1)开启一个银行窗口
-- 还原数据
truncate account;
INSERT INTO account (name, money) VALUES ('a', 1000), ('b', 1000);
set global transaction isolation level serializable; -- 设置隔离级别为串行化
2)执行案例:
session-01 | session-02 |
---|---|
begin; | |
begin; | |
update account set money=money-500 where name=‘a’; | |
select * from account; | |
在串行化隔离级别中,相当于锁表的操作,在一个事务对表进行任何的insert/update/delete等操作时,其他事务均不可对其进行操作;在读写上是串行的,并发能力极差;