数据库事务
事务特性
原子性(Atomicity):
一个事务包含多个操作,这些操作要么全部执行,要么全都不执行。实现事务的原子性,要支持回滚操作,在某个操作失败后,回滚到事务执行之前的状态。
一致性(Consistency):
事务的一致性指的是在一个事务执行之前和执行之后的数据必须在数据库的其他事务中处于一致性状态,即读的时候能读到所有已经提交的最新数据,提交写操作能让所有其他事务能读到最新数据,而事务在中间过程的操作数据,外部不可以读取。
其概念和
多线程的可见性
相似,和
分布式数据分区的一致性
相同。
事务可以不同程度的一致性:
强一致性:读操作可以立即读到提交的更新操作。
弱一致性:提交的更新操作,不一定立即会被读操作读到,此种情况会存在一个不一致窗口,指的是读操作可以读到最新值的一段时间。
最终一致性:是弱一致性的特例。事务更新一份数据,最终一致性保证在没有其他事务更新同样的值的话,最终所有的事务都会读到之前事务更新的最新值。如果没有错误发生,不一致窗口的大小依赖于:通信延迟,系统负载等。
单调一致性:如果一个进程已经读到一个值,那么后续不会读到更早的值。
会话一致性:保证客户端和服务器交互的会话过程中,读操作可以读到更新操作后的最新值。
隔离性(Isolation):
由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。说人话就是,一个事务所修改的数据在未提交之前,别的事务不应该可以修改,多个事务对同一个数据的删改操作时,必须保证同一时间只有一个事务在操作(只要没提交,都算在操作)。
再说直白一点就是,操作之前要加锁,操作之后释放锁。
事务查看数据时数据所处的状态,到底是另一个事务执行之前的状态还是中间某个状态,相互之间存在什么影响,是可以通过隔离级别的设置来控制的。
事务可以不同程度的隔离性(隔离级别):
读未提交、读已提交、可重复读、可串行化。
下文会有事务隔离级别详细说明,这里不做过多的解释。
持久性(Durability):
事务结束后,事务处理的结果必须能够得到固化,不可回滚,即写入数据库文件中即使机器宕机数据也不会丢失,它对于系统的影响是永久性的。
事务并发问题
脏读( Dirty Reads):一个事务读取到其他事务未提交的数据。
如,事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,如果事务A提交失败,事务B读到的就是脏数据。
不可重复读(Non- Repeatable Reads):在同一个事务中,对于同一份数据读取到的结果不一致。
如,事务A中执行了两个查询,但在事务A的执行过程中,事务B对要查询的数据进行了修改并提交,此时会造成事务A前面的查询是事务B修改之前的数据,事务A后面的查询是事务B修改之后的数据。
不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的数据加行锁,这会导致锁竞争加剧,影响性能。另一种方法是通过MVCC可以在无锁的情况下,避免不可重复读。
幻读( Phantom Reads):在同一个事务中,同一个查询多次返回的结果不一致。
和不可重复度类似,区别在于幻读的关注的是整个表(增删数据),不可重复度关注的是具体的数据(修改数据)。
幻读是由于并发事务增删数据导致的,这个不能像不可重复读通过加行锁解决,因为对于新增的数据根本无法加行锁。需要将事务串行化,或锁表,才能避免幻读。
第一类更新丢失( Lost Update 1):撤销一个事务的时候,把其它事务已提交的更新数据覆盖了。这是完全没有事务隔离级别造成的。
第二类更新丢失( Lost Update 2):它和不可重复读本质上是同一类并发问题,通常将它看成不可重复读的特例。当两个或多个事务查询相同的记录,然后各自基于查询的结果更新记录时会造成后提交的覆盖先提交的情况。加写锁就能解决此类问题。
事务隔离级别
(1)read uncommitted 读未提交
最低的隔离级别,一个事务可以读到另一个事务未提交的结果,该级别下所有的并发事务问题都会发生。
(2)read committed 读已提交
任何事务只可以读取其他事务已提交的数据,可以解决脏读问题。
(3)repeatable read 可重复读
在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。该级别可以解决脏读、不可重复读,但存在可能将未提交的记录查询出来,而出现幻读问题。
(4)serializable 可串行化
事务串行化执行(强制排序后按顺序执行),隔离级别最高,该级别下所有的并发事务问题都不会发生,但会导致大量超时现象和锁竞争。
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进
行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读
”和“幻读”并不敏感,可能更关心数据并发访问的能力。
事务隔离级别引发事务并发问题
事务隔离级别 | 脏读 | 不可重复读 | 幻读 | 一类丢失 | 二类丢失 | 总结 |
---|---|---|---|---|---|---|
读未提交(read uncommitted) | 是 | 是 | 是 | 是 | 是 | (无锁)最低级别,不保证任何并发性数据问题 |
读已提交(read committed) | 否 | 是 | 是 | 否 | 是 | (行锁+读锁)会出现不可重复读和幻读。 |
可重复读(repeatable read) | 否 | 否 | 是 | 否 | 否 | (行锁+写锁)会出现幻读,mysql默认的数据库隔离级别 |
可串行化(serializable) | 否 | 否 | 否 | 否 | 否 | (表锁)最高级别,所有并发性数据问题都不会出现 |
MySQL事务隔离级别
mysql InnoDB支持事务四大隔离级别,默认隔离级别是
repeatable read
。
查看当前数据库的事务隔离级别:
5.x:
show variables like 'tx_ isolation';
或者
select @@tx_isolation;
8.x:
show variables like 'transaction_isolation';
或者
select @@transaction_isolation;
修改数据隔离级别:
sql修改:
set session transaction isolation level serializable;#设置当前连接的事务隔离级别为串行化,一共四种,"read uncommitted","read committed","repeatable read","serializable"。
set global transaction isolation level serializable;#设置全局的事务隔离级别为串行化,5.x的global变量在数据库重启后会丢失,想不丢失必须在配置文件中进行配置,8.x的global变量会永久保存,数据库重启后依然生效。
配置(my.cnf或my.ini)修改:
[mysqld]
transaction-isolation = REPEATABLE-READ
Oracle事务隔离级别
oracle数据库支持read committed和serializable,默认隔离级别是read committed。
查询和设置事务隔离级别:
-为了查询事务隔离级别,首先创建一个事务
declare trans_id varchar2(100);
begin trans_id := dbms_transaction.local_transaction_id( true );end;
--查看事务隔离级别
select s.sid, s.serial#,case bitand(t.flag, power(2, 28)) when 0 then 'read committed' else 'serializable' end isolation_level
from v$transaction t join v$session s on t.addr = s.taddr and s.sid = sys_context('USERENV', 'SID');
set transaction isolation level read committed;--设置系统当前隔离级别
set transaction isolation level serializable;--设置系统当前隔离级别
alter session set transaction isolation level read committed;--设置当前会话隔离级别
alter session set transaction isolation serializable;--设置当前会话隔离级别
--设置oracle事务是否只读(与本文无关,记在这里为了作者自己看)
set transaction read only;--设置事务为只读,read only是oracle自己的事务隔离级别
set transaction read write;--设置事务为读写
Spring事务传播方式