关于数据库的事务处理
事务
事物的概念
-
事务指逻辑上的一组操作,组成这操作的个单元要么全部成功,要么全部失败. { 例如:a——b转帐,对应于如下两条sql语句
update account set money=money-520 where name=‘a’; update account set money=money+520 where name=‘b’;
}
管理事务
- 数据库默认一条sql语句独占一个事务意义不大.
手动控制事务
- 事务中的sql在执行时,并没有真正修改数据库中的数据
- 提交事务,将整个事务对数据库的影响一起发生 commit;
- 回滚事务,将这个事务对数据库的影响取消掉 rollback;
在JDBC中控制事务!!!
- 在数据库中开启事务
conn.setAutoCommit(false);
- 关闭自动连接后,conn将不会帮我们提交事务,在这个连接上执行的所有sql语句将处在同一事务中,需要我们是手动的进行提交或回滚
conn.commit();
- 提交事务
conn.rollback();
- 回滚事务 也可以设置回滚点回滚部分事务。
Savepoint sp = conn.setSavepoint();
conn.rollback(sp);
- 注意,回到回滚点后,回滚点之前的代码虽然没被回滚但是也没提交呢,如果想起作用还要做commit 操作.
事务的四大特性
事务的四大特性是事务本身具有的特点.
原子性(Atomicity)
- 原子性是指事务可以是一个不可分割的单位,事务中的操作要么都发生,要么都不发生.(注意回滚点)
一致性(Consistency)
- 事务前后数据的完整性必须保持一致.
隔离性(Isolation)
- 事务的隔离性是指多个用户并发访问数据库是,一个用户的事务不能被其他用户的事务干扰,多个并发事务之间数据要相互隔离.
持久性(Durability)
- 持久性是指一个事务一旦被提交,他对数据库中数据的改变就是永久性的,接下来及时数据库发生故障也不应该对其有任何影响.
隔离性
数据苦苦隔离分析
数据库的隔离性的问题本质就是多线程并发的安全性问题.
可以利用锁来解决多线程并发安全问题,但是如果用了锁,必然会造成程序的性能大大的下降.对于数据库这种高并发要求的程序来说是不可接受的. 来具体分析下隔离性产生的细节:
- 如果两个线程并发修改,必然产生多线程并发安全问题,必须隔离开
- 如果两个线程并发查询,必然没有问题,就不需要隔离
- 如果一个线程修改,一个线程查询,在不同的场景下有个能有问题,有可能没有问题.
隔离性可能造成的问题
查询当前数据库的隔离级别:
select @@tx_isolation;
修改当前回话的数据库隔离级别:
set session transaction isolation level read uncommitted;
脏读:
一个事务读取到另一个事务未提交的数据
----------------------------
a(杨志) 0
b(买刀者) 1000
----------------------------
b(买刀者):
start transaction;
update account set money=money-100 where name=b;
update account set money=money+100 where name=a;
-----------------------------
a(杨志):
start transaction;
select * from account;
a 100
b 900
commit;
-----------------------------
b(买刀者):
rollback;
-----------------------------
a(杨志):
start transaction;
select * from account;
a 0
b 1000
commit;
-----------------------------
不可重复读:
一个事务多次读取数据库中的同一条记录,多次查询的结果不同(一个事务读取到另一个事务已经提交的数据)
------------------------------
活期 定期 固定资产
a 1000 1000 1000
------------------------------
w:
start transaction;
select 活期 from account where name='a'; --- 活期存款:1000W元
select 定期 from account where name = 'a'; --- 定期存款:1000W元
select 固定 from account where name = 'a'; --- 固定资产:1000W元
---------------------------
a:
start transaction;
update account set 活期=活期-1000 where name= 'a';
commit;
w: ---------------------------
select 活期+定期+固定 from account where name='a'; ---总资产:2000w元
虚读(幻读)
有可能出现,有可能不出现:一个事务多次查询整表数据,多次查询时,由于有其他事务增删数据, 造成的查询结果不同(一个事务读取到另一个事务已经提交的数据)
------------------------------
a 1000
b 2000
------------------------------
z:
start transaction;
select sum(money) from account; --- 总存款3000w元
select count(*) from account; --- 总账户数2个
-----------------
c:
start transaction;
insert into account values ('c',3000);
commit;
z: -----------------
select avg(money) from account; --- 平均每个账户:2000w元
数据库的隔离级别
四大隔离级别
数据库在设计的时候防止的问题越多性能月底,放置的问题越少,安全性越差.
到底该访问那些问题有数据库使用者根据具体的业务场景来决定,所以数据库的设计者并没有把防止哪类问题写死,而是提供了如下选择:
- read uncommitted; -- 不做任何隔离,可能造成脏读 不可重复读 虚读(幻读)问题
- read committed; -- 可以防止脏读,但是不能防止不可重复度 虚读(幻读)问题
- repeatable read; -- 可以防止脏读 不可重复度,但是不能防止 虚读(幻读)问题
- serializable; -- 可以防止所有隔离性的问题,但是数据库就被设计为了串行化的数据库,性能很低
从安全性上考虑:
Serializable > Repeatable Read > Read Committed > Read uncommitted
从性能上考虑:
Read uncommitted > Read committed > Repeatable Read > Serializable
我们作为数据库的使用者,综合考虑安全性和性能,从四大隔离级别中选择一个在可以防止想要防止的问题的隔离级别中性能最高的一个. 其中Serializable性能太低用的不多,Read uncommitted安全性太低用的也不多,我们通常从Repeatable Read和Read committed中选择一个. 如果需要防止不可重复读选择Repeatable Read,如果不需要防止选择Read committed
mysql数据库默认的隔离级别就是Repeatable Read Oracle数据库默认的隔离级别是Read committed
数据库中的锁:
共享锁
共享锁和共享锁可以共存,共享锁和排他锁不能共存.在非Serializable隔离级别下做查询不加任何锁,在Serializable隔离级别下做查询加共享锁. 案例演示:打开两个mysql客户端,将隔离级别都设置为Serializable级别,
set session transaction isolation level
serializable;--设置后查询加了共享锁
分别在两个客户端中查询:
start transaction;
select * from account;--都能查询出数据,说明共享锁可以共存。
排他锁
排他锁和共享锁不能共存,排他锁和排他锁也不能共存,在任何隔离级别下做增删改都加排他锁. 在共享锁的基础上,在其中一个客户端执行修改操作,将一个客户端的共享锁升级为排他锁: 两个客户端都执行:
start transaction;
select * from account;
一个客户端执行:
update account set money = 900;
发现执行在等待,当另外一个客户端提交commit或者回滚rollback之后,修改才能成功。
另外一个客户端执行:
rollback/commit;
可能的死锁
mysql可以自动检测到死锁,错误退出一方执行另一方 在1.8.1基础上: 两个客户端都执行:
start transaction;
select * from account;
一个客户端执行:
update account set money = 900;
另外一个客户端执行:
update account set money = 800;
发现彼此等待,直到一方报错结束,死锁才结束。
更新丢失问题的产生
两个并发的事务基于同一个查询结果进行修改,后提交的事务忽略了先提交的事务对数据库的影响,造成了先提交的事务对数据库的影响丢失,这个过程就叫做更新丢失.
更新丢失解决方案:
将数据库设置为Serializable隔离级别,但是我们一般不会将数据库设置为Serializable 那么在非Serializable下又如何解决更新丢失? 可以使用乐观锁、悲观锁。 乐观锁和悲观锁并不是数据库中真实存在的锁,而是两种解决方案的名字。
悲观锁:
在查询时,手动的加排他锁,从而在查询时就排除可能的更新丢失。
select paystate from orders where id = 6 for update;
乐观锁:
在表中设计版本version字段,在进行修改时修改时,要求根据具体版本进行修改,并将版本字段+1,如果更新失败,说明更新丢失,需要重新进行更新。 两种解决方案各有优缺点,如果查询多修改少,用乐观锁.如果修改多于查询,用悲观锁。