事务:
事务:事务是指在逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功
例如:A——B转帐,对应于如下两条sql语句
update account set money=money-100 where name=‘a’;
update account set money=money+100 where name=‘b’;
数据库中事务管理机制:
默认管理:数据库是支持默认事务的,但是数据库中的默认事务是一条Sql语句独占一个事务,这ti种方式意义不大,数据库支持自动开启事务的。
自动开启事务的场景:当用户进行操作单条记录的时候。
手动进行事务的控制:
-- 手动开启事务: start transaction ;
开启事务之后,往后的操作都是处于当前的事务中,要么同时完成,要么同时不完全
开启事务之后,sql语句的执行并没有真正的在修改数据库中的数据,还没有执行完成,需要在提交之后才能进行数据的展示,默认的情况是不进行提交的。
-- 提交事务:commit;
-- 回滚事务:rollback;
一旦进行事务的回滚操作,之前的任何操作对数据库中的影响都被取消,回归到之前的数据库形态
JDBC中的事务控制:
-- 当jdbc程序向数据库获得那个一个Connection对象时,默认情况下这个Connection对像会自动向数据库提交在它上面发送的sql语句,如果想要关闭数据库的默认提交方式,让多条sql语句在一个事务中执行
-- 也就是说,jdbc中默认是会自动进行提交数据,一条语句,独占一个事务
--- 关闭默认提交的方式 conn.setAutoCommit(false);
--关闭自动连接后,conn将不会帮我们提交事务,在这个连接上执行的所有sql语句将处在同一事务中,需要我们是手动的进行提交或回滚
conn.commit();
--提交事务
conn.rollback();
--回滚事务
-- 设置事务的回滚点的位置:回滚部分事务,提高对应的效率
SavePoint sp=conn.setSavePoint();
conn.rollback(sp):将事务提交到回滚点,回滚对应点
-- 注意的是:只是回滚到了回滚点,事务还是没有进行提交,如果想要进行提交的话,还是必须进行手动提交
事务的四大特性(ACID):
1. 原子性(Autoitymic):一个事务是不可拆分的工作单位,事务中的操作要么全部发生,要么全部不发生
2. 一致性(Consistency):事务的前后的完整性必须一致(比如:账户的支出等)
3. 隔离性(Isolation):当多个用户并发访问数据库时,一个用户的事务不能被其他事务所干扰,多个并发事务之间的数据要进行相互的隔离
4. 持久性(Durablity):指一个事务一旦被提交,它对数据库中的数据改变是永久性的,接下来数据库发生故障也不应该对其有任何影响。
浅谈隔离性:
数据库的其他三大特性数据库可以帮我们保证,而隔离性我们需要再讨论。
如果我们是数据库的设计者,该如何考虑设计数据库保证数据库的隔离性呢?t
- - 隔离性的问题本质上就是多线程并发安全问题讨论,多线程的意思是多个对象同作ru
- -隔离性的产生细节:
-- 如果两个线程并发修改,必然产生多线程安全问题,必须隔离开
-- 如果两个线程并发查询,必然是没有问题的,不需要进行隔离性的操作
-- 如果一个线程修改,一个线程查询,在不同的场景下可能没有问题,可能有问题
隔离性可能造成的问题:
--- 脏读:一个事务读取到另一个事务未提交的数据,同一个数据库中没有进行提交数据,数据库中并不会做出真正的改变
----------------------------
a 1000
b 1000
----------------------------
a:
start transaction;
update account set money=money-100 where name=a;
update account set money=money+100 where name=b;
-----------------------------
b:
start transaction;
select * from account;
a 900
b 1100
commit;
-----------------------------
a:
rollback;
-----------------------------
b:
start transaction;
select * from account;
a 1000
b 1000
commit;
--- 不可重复读:一个事务多次进行读取数据库中的同一条记录,多次进行查询的结果不一样(一个事务读取到另一个事务已经提交的事务),后面提交的事务会对当前的事务做出改变。
------------------------------
a 1000 1000 1000
------------------------------
b:
start transaction;
select 活期 from account where name='a'; --- 活期存款:1000元
select 定期 from account where name = 'a'; --- 定期存款:1000元
select 固定 from account where name = 'a'; --- 固定资产:1000元
---------------------------
a:
start transaction;
update account set 活期=活期-1000 where name= 'a';
commit;
---------------------------
select 活期+定期+固定 from account where name='a'; ---总资产:2000元
--- 虚读/幻读:有可能不进行出现,是一个事务多次查询整表记录的时候,多次查询时,由于有其他事务的增删数据,造成查询结果不同()一个事务读取到另一个事务已经提交的数据
------------------------------
a 1000
b 2000
------------------------------
d:
start transaction;
select sum(money) from account; --- 总存款3000元
select count(*) from account; --- 总账户数2个
-----------------
c:
start transaction;
insert into account values ('c',3000);
commit;
-----------------
select avg(mone) from account; --- 平均每个账户:2000元
数据库之间的问题都是来源于多个事务之间进行数据的读取不准确性,多线程并发安全问题:
针对数据库的隔离性,设置了四大隔离级别:
Read uncommitted:--- 可读,没有提交,开启事务后,数据库没有做出任何改变
--- 不做任何的隔离,可能造成脏读,不可重复读,虚读/幻读问题
Read Committed:--- 可读,但是数据库中的数据已经被提交
--- 可以防止出现脏读,但是不能防止不可重复读,虚读问题
Repeatable Read:--- 可以重复读取,不可重复读,整表操作---mysql的默认隔离级别
--- 可以防止脏读,不可重复读,但是不能防止虚读/幻读问题
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
操作数据库的隔离级别:
--- 查询数据库的隔离级别 select @@tx_isolation;
--- 修改数据库的隔离级别
set session transaction isolation level xxxx;---->修改当前客户端的隔离级别
set global transaction isolation level xxxx----->新开的客户端的隔离级别被改变
数据库中锁:
共享锁:共享锁和共享锁可以共存,共享锁和排他锁不能共存.在非Serializable隔离级别下做查询不加任何锁,在Serializable隔离级别下做查询加共享锁.
排他锁:排他锁和共享锁不能共存,排他锁和排他锁也不能共存,在任何隔离级别下做增删改都加排他锁.
总结:
MySQL中底层会自动根据锁机制进行工作,排他锁和共享锁底层会自动枷锁,如果存在查询和修改的情况,不需要自己的手动进行加锁和手动释放锁
可能出现的死锁:mysql可以自动检测到死锁,错误退出一方执行另一方
丢失更新问题的出现:
两个并发的事务基于同一个查询结果进行修改,后提交的事务忽略了先提交的事务对数据库的影响,造成了先提交的事务对数据库的影响丢失,这个过程就叫做更新丢失
案例:在线支付案例
案例:修改小红案例
丢失更新解决:
将数据库设置为Serializable隔离级别,但是我们一般不会将数据库设置为Serializable
那么在非Serializable下又如何解决更新丢失?可以使用乐观锁、悲观锁。
乐观锁和悲观锁并不是数据库中真实存在的锁,而是两种解决方案的名字。
(1)悲观锁:
在查询时,手动的加排他锁,从而在查询时就排除可能的更新丢失。
select * from users for update;
(2)乐观锁:
在表中设计版本字段,在进行修改时修改时,要求根据具体版本进行修改,并讲版本字段+1,如果更新失败,说明更新丢失,需要重新进行更新。
两种解决方案各有优缺点,如果查询多修改少,用乐观锁.如果修改多余查询,用悲观锁。