一 、事务
一个事务中的一系列的处理操作要么全部成功,要么全部失败。在数据库操作中,一项事务(Transaction)是由一条或多条操作数据库的SQL语句组成的一个不可分割的工作单元。
事务的处理结果有两种:
1)当事务中的所有步骤全部成功执行时,事务提交,成功;
2)如果其中任何一个步骤失败,该事务都将发生回滚操作,撤销已执行的所有操作。
二、事务四大特性(ACID)
2.1 原子性(Atomic)
表示将事务中所进行的操作捆绑成一个不可分割的单元,即对事务所进行的数据修改等操作,要么全部执行,要么全不执行;如果失败,就回滚到事务开始前的状态。
2.2 一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
2.3 隔离性(Isolation)
指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
2.4 持久性(Durability)
持久性也称永久性(permanence),持久性就是指如果事务一旦被提交,数据库中数据的改变就是永久性的,即使断电或者宕机的情况下,也不会丢失提交的事务操作。
三 、隔离性详解以及事务的并发问题
说明:隔离性是指,多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔离。
事务隔离性的重要性:事务的隔离性主要是从提升数据库的数据处理速度,即并发角度考虑的;换句话说,事务隔离性和整体数据库的性能/并发执行,有直接决定性作用。
一个思维模式:想要理解一个知识点,就假设如果没有该点会有什么发生;事务隔离性也是,如果事务中没有隔离性这个概念,会发生点啥事?
在实际应用中,数据库中性能的好坏标准之一就是数据能被尽可能多的用户共同访问,当多个用户同时处理同一数据时,可能就会出现一些事务的并发问题,导致如下四种情况出现:
3.1)脏读
指一个事务读取到另一个事务未提交的数据。
3.2)幻读
指一个事务执行两次查询,但第二次查询的结果包含了第一次查询中未出现的数据。
举例:事务1读取一个表期间,事务2对表做了delete/update/insert操作并提交,事务1再次读取表时,此时读取到事务2操作的记录,两次操作结果不一致。
3.3)不可重复读
指一个事务对同一行数据重复读取两次,但得到的结果不同。
举例:事务1读取表的一条数据期间,事务2更新了该条记录并提交,事务1再次读取该表该条记录时,发现和第一次内容不一致。
这里,有朋友会问,不可重复读和幻读这不一样吗?
答案为:不一样。有啥区别?
幻读和不可重复读都是读取了另一条已经提交的事务;但不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
不可重复读和幻读是初学者不易分清的概念;简单来说,解决不可重复读的方法是大家常说的加行锁,解决幻读方式是加表锁。
3.4)丢失更新
指两个事务同时更新一行数据,后提交(或撤销)的事务将之前事务提交的数据覆盖了。
注意:丢失更新可分为两类,分别为第一类丢失更新和第二类丢失更新。
第一类丢失更新:两个事务同时操作同一个数据时,当第一个事务撤销时,把已经提交的第二个事务的更新数据覆盖了,第二个事务就造成了数据丢失。
第二类丢失更新:当两个事务同时操作同一个数据时,第一个事务将修改结果成功提交后,对第二个事务已经提交的修改结果进行了覆盖,对第二个事务造成了数据丢失。
可以看出,上述问题,均是在并发情况下发生的,并发度越高,上述出现的情况也普遍。
如何解决事务的并发问题怎么办?
为了避免上述事务并发问题的出现,标准 SQL 规范定义了四种事务隔离级别,不同的隔离级别对事务的处理有所不同。这四种事务的隔离级别如下:
四 、数据库事务的四种隔离级别
mysql数据库测试,首先创建一个表accoun,然后往表中插入两条数据,插入后结果如下:
为了说明问题,我们打开两个控制台分别进行登录来模拟两个用户(暂且成为用户A和用户B吧),并设置当前MySQL会话的事务隔离级别。
1)Read Uncommitted(读取未提交数据)
一个事务在执行过程中,既读取其他事务未提交的数据,又可以读取本事务未提交的修改数据。一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。此隔离级别可防止丢失更新。
缺点:
能读取到其他事务修改未提交的数据,会产生“脏读”
读未提交是并发最高,但一致性也最差的隔离级别。
具体用户A的操作如下:
set session transaction isolation level read uncommitted;
start transaction;
select * from account;
用户B的操作如下:
set session transaction isolation level read uncommitted;
start transaction;
update account set account=account+200 where id = 1;
随后我们在A用户中查询数据,结果如下:
结论:
我们将事务隔离级别设置为read uncommitted,即便是事务没有commit,但是我们仍然能读到未提交的数据,这是所有隔离级别中最低的一种。
那么这么做有什么问题吗?那就是我们在一个事务中可以随随便便读取到其他事务未提交的数据,这还是比较麻烦的,我们叫脏读。我不知道这个名字是怎么起的,为了增强大家的印象,可以这么想,这个事务好轻浮啊,饥渴到连别人没提交的东西都等不及,真脏,呸!
实际上我们的数据改变了吗?
答案是否定的,因为只有事务commit后才会更新到数据库。
2)Read Committed(读已提交)---大多数数据库默认的隔离级别
优点:此隔离级别可有效防止脏读
在该隔离级别下,不允许2个未提交的事务之间并行执行,但它允许在一个事务执行的过程中,另外一个事务得到执行并提交。读取数据的一个事务不会禁止其他写事务,读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
缺点:此隔离级别会产生不可重复读问题。
该方式是oracle数据库默认的隔离级别,事务提交需手动进行。
注意,在互联网大数据量,高并发量的场景下,几乎不会使用上述两种隔离级别。
在用户A所在的会话中我们执行下面操作:
set session transaction isolation level read committed;
start transaction;
update account set account=account-200 where id=1;
在B用户所在的会话中查询:
select * from account;
结果如下:
我们会发现数据并没有变,还是1000。
接着在会话A中我们将事务提交:
commit;
在会话B中查询结果如下:
结论:
当我们将当前会话的隔离级别设置为read committed的时候,当前会话只能读取到其他事务提交的数据,未提交的数据读不到。
那么这么做有什么问题吗?那就是我们在会话B同一个事务中,读取到两次不同的结果。这就造成了不可重复读,就是两次读取的结果不同。这种现象叫不可重复读。
3)Repeatable Read(可重复读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。
但是不能解决幻读,为啥?看个场景
在可重复读取隔离级别下,因为只是对一个事务写操作的行加了行锁,但依旧允许别的事务在该表其他行插入和删除数据,于是就会出现,在事务1执行的过程中,如果先后两次select出符合某个条件的行,如果在这两次select之间另一个事务得到了执行,insert或delete了某些行,就会出现先后两次select出来的符合同一个条件的结果不一样,第一次select好像出现了幻觉一样,因此,这个问题也被成为幻读。要想解决幻读问题,需要将数据库的隔离级别设置为串行化。
4)Serializable(可串行化)
这是最高的隔离级别,它强制事务都是串行执行的,使之不可能相互冲突,从而解决幻读问题。换言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
介绍完四种隔离级别,这里小结下:
总结:
1.四种隔离级别最高的是 Serializable 级别,最低的是 Read uncommitted 级别,当然级别越高,执行效率就越低。像 Serializable 这样的级别,就是以 锁表 的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读) ;
2.在MySQL数据库中(只有InnoDB支持事务),支持上面四种隔离级别,默认的为Repeatable read (可重复读) ;而在 Oracle数据库 中,只支持Serializable (串行化) 级别和 Read committed (读已提交) 这两种级别,其中默认的为 Read committed(读已提交) 级别;
3.通常来说,事务的隔离级别越高,越能保证数据库的完整性和一致性,但相对来说,隔离级别越高,对并发性能的影响也越大。因此,Oracle通常将数据库的隔离级别设置为 Read Committed,即读已提交数据,它既能防止脏读,又能有较好的并发性能。虽然这种隔离级别会导致不可重复读、幻读和第二类丢失更新这些并发问题,但可通过在应用程序中采用悲观锁和乐观锁加以控制;
五、各种数据库的默认隔离级别
5.1MySQL
mysql默认的事务处理级别是'REPEATABLE-READ',也就是可重复读
1.查看当前会话隔离级别
select @@tx_isolation;
2.查看系统当前隔离级别
select @@global.tx_isolation;
3.设置当前会话隔离级别
set session transaction isolatin level repeatable read;
4.设置系统当前隔离级别
set global transaction isolation level repeatable read;
5.2Oracle
oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别。
默认系统事务隔离级别是READ COMMITTED,也就是读已提交
1.查看系统默认事务隔离级别,也是当前会话隔离级别
--首先创建一个事务
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 AS isolation_level
FROM v$transaction t
JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID');
5.3SQL Server
默认系统事务隔离级别是read committed,也就是读已提交
1.查看系统当前隔离级别
DBCC USEROPTIONS
isolation level 这一项的 Value 既是当前的隔离级别设置值
2.设置系统当前隔离级别
SET TRANSACTION ISOLATION LEVEL Read UnCommitted;
其中Read UnCommitted为需要设置的值