引入事务的目的: 事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,那么所有修改都不保存。
Innodb中事务完全符合ACID特性: - 原子性(Atomicity):一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性. - 一致性 Consistency: 事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态 - 隔离性 Isolation :事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。 - 持久性 Durability:一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。--即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态
在事务操作中,要么都做修改,要么都不做。Innodb默认的事务隔离级别REPEATABLE READ完全遵循和满足事务的ACID性。
锁就是防止其他事务访问指定资源的手段。锁是实现并发控制的主要方法,是多个用户能够同时操纵同一个数据库中的数据而不发生数据不一致现象的重要保障。 一般来说,锁可以防止脏读、不可重复读和幻读。
1.脏读(Dirty Read)——一个事务读取到了另外一个事务没有提交的数据。
详细解释:当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
事务T1:更新一条数据 -->事务T2:读取事务T1更新的记录 事务T1:调用commit进行提交 此时事务T2读取到的数据是保存在数据库内存中的数据,称为脏数据,这个过程称为脏读。
脏读发生在一个事务A读取了被另一个事务B修改,但是还未提交的数据。假如B回退,则事务A读取的是无效的数据。这跟不可重复读类似,但是第二个事务不需要执行提交。
解决脏读问题:修改时加排他锁,直到事务提交后才释放,读取时加共享锁,读取完释放事务1读取数据时加上共享锁后(这样在事务1读取数据的过程中,其他事务就不会修改该数据),不允许任何事务操作该数据,只能读取,之后1如果有更新操作,那么会转换为排他锁,其他事务更无权参与进来读写,这样就防止了脏读问题。但是当事务1读取数据过程中,有可能其他事务也读取了该数据,读取完毕后共享锁释放,此时事务1修改数据,修改完毕提交事务,其他事务再次读取数据时候发现数据不一致,就会出现不可重复读问题,所以这样不能够避免不可重复读问题。
2.幻读(Phantom)——同一事务中,用同样的操作读取两次,得到的记录数不相同。
详细解释:幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。
事务T1:查询表中所有记录 -->事务T2:插入一条记录 -->事务T2:调用commit进行提交 事务T1:再次查询表中所有记录
此时事务T1两次查询到的记录是不一样的,称为幻读。
注意:幻读重点在新增或删除。
幻读发生在当两个完全相同的查询执行时,第二次查询所返回的结果集跟第一个查询不相同。
发生的情况:没有范围锁。
如何避免:实行序列化隔离模式,在任何一个低级别的隔离中都可能会发生。
解决幻读问题:采用的是范围锁RangeS RangeS_S模式,锁定检索范围为只读,这样就避免了幻读问题。
3.不可重复读(Nonrepeatable Read)——在同一事务中,两次读取同一数据,得到内容不同。
事务T1:查询一条记录 -->事务T2:更新事务T1查询的记录 -->事务T2:调用commit进行提交 事务T1:再次查询上次的记录
此时事务T1对同一数据查询了两次,可得到的内容不同,称为不可重复读。
注意:不可重复读重点在修改。
在基于锁的并行控制方法中,如果在执行select时不添加读锁,就会发生不可重复读问题。
在多版本并行控制机制中,当一个遇到提交冲突的事务需要回退但却被释放时,会发生不可重复读问题。
有两个策略可以防止这个问题的发生:
(1) 推迟事务2的执行,直至事务1提交或者回退。这种策略在使用锁时应用。
(2) 而在多版本并行控制中,事务2可以被先提交,而事务1继续执行在旧版本的数据上。当事务1终于尝试提交时,数据库会检验它的结果是否和事务1、事务2顺序执行时一样。如果是,则事务1提交成功;如果不是,事务1会被回退。
解决不可重复读问题:读取数据时加共享锁,写数据时加排他锁,都是事务提交才释放锁。读取时候不允许其他事物修改该数据,不管数据在事务过程中读取多少次,数据都是一致的,避免了不可重复读问题。 4.丢失更新(Lost Update)
事务T1读取了数据,并执行了一些操作,然后更新数据。事务T2也做相同的事,则T1和T2更新数据时可能会覆盖对方的更新,从而引起错误。
5.处理以上隔离级别的问题,采用如下方法:
事务隔离五种级别: (1)TRANSACTION_NONE 不使用事务。 (2)TRANSACTION_READ_UNCOMMITTED 允许脏读。 (3)TRANSACTION_READ_COMMITTED 防止脏读,最常用的隔离级别,并且是大多数数据库的默认隔离级别。 (4)TRANSACTION_REPEATABLE_READ 可以防止脏读和不可重复读。 (5)TRANSACTION_SERIALIZABLE 可以防止脏读,不可重复读取和幻读,(事务串行化)会降低数据库的效率。
以上的五个事务隔离级别都是在Connection接口中定义的静态常量,使用setTransactionIsolation(int level) 方法可以设置事务隔离级别。
如:con.setTransactionIsolation(Connection.REPEATABLE_READ)。
注意:事务的隔离级别受数据库的限制,不同的数据库支持的的隔离级别不一定相同。
隔离性(又称:并发控制、可串行化、锁,事务提交前对其他事物都不可见,通常用锁来实现)在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同,分别是:未授权读取,授权读取,可重复读取和串行化
事务隔离级别 | -- |
---|---|
读未提交(Read Uncommitted) | 该隔离级别允许脏读取,其隔离级别最低;比如事务A和事务B同时进行,事务A在整个执行阶段,会将某数据的值从1开始一直加到10,然后进行事务提交,此时,事务B能够看到这个数据项在事务A操作过程中的所有中间值(如1变成2,2变成3等),而对这一系列的中间值的读取就是未授权读取。会引发脏读、不可重复读、虚读,但是避免更新丢失,可以通过“排他写锁”实现。 |
授权读取也称为已提交读(Read Commited) | 授权读取只允许获取已经提交的数据。比如事务A和事务B同时进行,事务A进行+1操作,此时,事务B无法看到这个数据项在事务A操作过程中的所有中间值,只能看到最终的10。另外,如果说有一个事务C,和事务A进行非常类似的操作,只是事务C是将数据项从10加到20,此时事务B也同样可以读取到20,即授权读取允许不可重复读取。 |
可重复读(Repeatable Read) mysql默认级别 | 就是保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻是一致的,因此该事务级别禁止不可重复读取和脏读取,但是有可能出现幻影数据。所谓幻影数据,就是指同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果。在上面的例子中,可重复读取隔离级别能够保证事务B在第一次事务操作过程中,始终对数据项读取到1,但是在下一次事务操作中,即使事务B(注意,事务名字虽然相同,但是指的是另一个事务操作)采用同样的查询方式,就可能读取到10或20; |
串行化 | 是最严格的事务隔离级别,它要求所有事务被串行执行,即事务只能一个接一个的进行处理,不能并发执行。 |
读未提交(Read Uncommitted)案例:
create table account(
id int auto_increment primary key,
name varchar(30),
money decimal(10,2)
) ;
insert into account values (1,'A',1500);
--A窗口
set transaction isolation level read uncommitted; --设置A用户的数据库隔离级别为Read uncommitted(读未提交)
start transaction;--开启事务
select * from account;--查询A账户中现有的钱,然后转到B窗口进行操作
select * from account; --发现a多了100元,这是A读到了B未提交的数据(脏读)
--B窗口
start transaction; -- 开启事务
update account set money=money+100 where name='A'; --不要提交,转到A窗口查询
授权读取(Read Committed),也称为读提交,会引发不可重复读和虚读,但避免脏读,可以通过“瞬间共享读锁”和“排他写锁”实现。 读取数据的事务允许其他事务继续访问该数据,但是未提交的写事务会禁止其他事务访问该行。
--A窗口
set transaction isolation level read committed; --设置A用户的数据库隔离级别为Read uncommitted(读未提交)
start transaction;--开启事务
select * from account;--查询A账户中现有的钱,然后转到B窗口进行操作
select * from account; --发现a多了100元,这是A读到了别的事务提交的数据,两次读取a账户读到的是不同的结果(不可重复读)
--B窗口
start transaction; -- 开启事务
update account set money=money+100 where name='A'; --这时A窗口查询多少次数据都是B 更新之前的数据。一直到B commit。(脏读是B commit之前A窗口就能读到)
commit; -- 转到A窗口查询
可重复读(Repeatable Read )mysql默认级别: 不会造成不可重复读取和脏读,但是有时可能出现幻读数据和虚读。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
--A窗口
set transaction isolation level repeatable read ;
start transaction;--开启事务
select * from account;--然后转到B窗口进行操作
select * from account; --(可能发现记录增加,读取到了B事务插入的数据(虚读)
--B窗口
start transaction; -- 开启事务
update account set money=money+100 where name='A';
insert into account(name,money) values('gg',10000);
commit; -- 转到A窗口查询
序列化Serializable :提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
--A窗口
set transaction isolation level Serializable;
start transaction;--开启事务
select * from account;--然后转到B窗口进行操作
--B窗口
start transaction; -- 开启事务
insert into account(name,money) values('gg',10000);--发现不能插入。只能等A窗口结束事务才能插入
补充:
参考资料:https://www.cnblogs.com/sushu-yaya/p/6944287.html 参考资料:https://www.jianshu.com/p/fc8a654f2205