Oracle的数据并发与一致性详解(上)

        今天想了解下oracle中事务与锁的原理,但百度了半天,发现网上介绍的内容要么太短,要么版本太旧,而且抄袭现象严重,所以干脆查官方帮助文档(oracle 11.2),并将其精华整理成中文,供大家一起学习。

       本篇将从数据并发与一致性概念开始,依次介绍事务隔离级别、锁机制、自动锁、手动锁、用户自定义锁的相关内容。

  请尊重作者劳动成果,转载请标明原文链接:

  https://www.cnblogs.com/jpcflyer/p/9164100.html

一、Oracle数据并发与一致性概念

       在以前单用户的数据库环境中,我们根本就不需要关心数据一致性的问题,因为根本就不会有多个用户在同一时间修改同一数据。但在现在的多用户数据库环境中,必须允许同时发生多个事务,而且这些事务可能会访问同一数据,此外,还要保证这些事务的一致性。因此多用户数据库必须提供以下两个基本功能:

  • 数据并发:即允许多用户同时访问同一数据
  • 数据一致性:即每个用户看到的数据都是一致的

        为了描述事务并发运行时的一致性行为,研究人员定义了一种事务隔离模型,称之为serializability(序列化)。这种可串行化事务操作使得它看起来似乎没有其它用户在操作数据。虽然这种序列化机制在一般情况下是可用的,但在并发要求高的场景,它会严重影响系统的吞吐能力。一般情况下,需要在事务隔离级别与性能间作一个取舍。

       Oracle通过使用multiversion consistency model(多版本一致性模型)、以及各种锁和事务来维护数据一致性,下面介绍下相关概念。

1.Multiversion Read Consistency(多版本读一致性)

       多版本指的是同时存在数据的多个版本,意味着oracle有以下两个特性:

  • read-consistent queries(读一致性查询)

       查询返回的数据是已提提交的,在某一时间点是一致的。(注意:oracle中是不会有脏读的,为什么呢?原因是第一条会话插入一条记录不提交,第二个会话再查询时发现这个事务没有commit,从而会找到这个事务的事务槽,事务槽中记录着该行未修改前的值存放在undo的位置,然后把该undo块加载到内存构造出CR块,查询会读取CR块中的值返回给客户。

  • nonblocking queries(非阻塞查询)

       数据的读和写不会相互阻塞。

2.Statement-Level Read Consistency(声明级读一致性)

       Oracle总是强制保证声明级的读一致性,确保查询返回的数据在同一时间点是已提交的(原因已在上面提及)。

3.Transaction-Leval Read Consistency(事务级读一致性)

       Oracle可以提供事务中所有查询的一致性,即事务中每个声明看到的数据都是某一点的数据,这个点指的是事务开始的点。在序列化事务中的查询只能看到自己本事务发生的修改。事务级读一致性产生了可重复读,且不会产生幻影读。

4.Read Consistency and Transaction Table(读一致性和事务表)

       Oracle使用了事务表来确定当数据库开始修改一个块时,是否有未提交的事务,这个事务表也称为interested transaction list(ITL)。事务表中描述了哪个事务有行锁、哪一行包含已提交或未提交的修改。

5.Locking Mechanisms(锁机制)

       一般来说,多用户数据库会使用多种数据锁的形式来解决数据的并发与一致性问题,本文后面会有锁的详细介绍。

6.ANSI/ISO Transaction Isotation Levals(ANSI/ISO事务隔离级别)

       ANSI和ISO都采纳的SQL标准中,定义了四个级别的事务隔离。这些不同级别对事务吞吐量有不同影响。这些隔离级别定义是为了预防两个并发事务会产生的一些现象,这些现象包括:

  • 脏读:一个事务读取了另一个事务没有提交的数据
  • 不可重复读:一个事务重复读取刚才已读过的数据,结果两次数据不一致,在此期间,其它事务对此数据已修改并提交
  • 幻影读:一个事务重复读取满足查询条件的记录数,结果两次数据不一致,在此期间,其它事务插入了符合此查询条件的数据

       SQL标准中根据隔离级别允许发生的现象,定义了四种隔离级别:

       Oracle数据库提供了read committed(默认级别)和serializable两种隔离级别,同时还支持只读模式

二、事务隔离级别

       上面已经提到ANSI的四种事务隔离级别,下面来详细介绍oracle数据库提供的三种事务隔离级别:read committed, serializable,read-only。

1.Read Committed事务隔离级别

       在此级别中,事务中查询到的数据都是在此查询前已经提交的。这种隔离级别避免了读取脏数据。然而数据库并不阻止其它事务修改一个所读取的数据,其它事务可能会在查询执行期间修改。因此 ,一个事务运行同样的查询两次,可能会遇到不可重复读和幻影读。

  • read committed隔离级别中的读一致性

       每个查询都会提供一个一致性的结果集,其中不需要用户做什么(这里的查询也包含像update中where这样的隐式查询)。

  • read committed隔离级别中的写冲突

       在一个read committed事务中,当事务要更改一行,而这行已经被另外一个未提交事务修改了(有时称之为blocking transaction),这里会发生写冲突。此时这个事务会等待blocking transaction结束,并有以下两个选项:

  • 如果blocking transaction回滚,那么waiting transaction会修改之前被locked的行
  • 如果blocking transaction提交然后释放锁,那么waiting transaction会在改变后的数据基础上,进行更新

       下表显示了事务1(可以是read committed或serializable)与事务2(read committed)的典型交互,称之为lost update(丢失更新)。

事务1事务2说明
SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9500
 

 

SQL> UPDATE employees SET salary
= 7000 WHERE last_name = 'Banda';
 

事务1用的是默认隔离级别READ COMMITTED

 
SQL> SET TRANSACTION ISOLATION
LEVEL READ COMMITTED;

 

 
SQL> SELECT last_name, salary
FROM employees WHERE last_name IN
('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9500

事务2通过使用oracle的读一致性得到了事务1更新前的数据

 
SQL> UPDATE employees SET salary =
9900 WHERE last_name = 'Greene';

 

SQL> INSERT INTO employees
(employee_id, last_name, email,
hire_date, job_id) VALUES (210,
'Hintz', 'JHINTZ', SYSDATE,
'SH_CLERK');
 

事务1插入了employee Hintz,但并没有提交

 
SQL> SELECT last_name, salary
FROM employees WHERE last_name IN 
('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9900

事务2看不到事务1未提交的Hintz信息

 
SQL> UPDATE employees SET salary =
6300 WHERE last_name = 'Banda';

-- prompt does not return 

事务2尝试去更新被事务锁住的Banda信息,产生了写冲突,此时事务2要等到事务1结束后再执行

SQL> COMMIT;
 

 

 
1 row updated.
 
SQL>

事务1提交,结束了事务,事务2继续处理

 
SQL> SELECT last_name, salary
FROM employees WHERE last_name IN
('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6300
Greene              9900
Hintz

 

 
COMMIT;

 

SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6300
Greene              9900
Hintz

       关于丢失更新的问题,后面有时间再讨论。

2.Serializable事务隔离级别

        在序列化隔离级别中,事务可以看到的是事务开始时已经提交的或事务自己做的修改。此隔离级别适合下面的场景:

  • 超大数据库并且事务很小,每个事务只更新几行
  • 在两个并发事务修改相同行的几率相对比较低的场景
  • 有较长的事务,但主要是只读事务的时候

       在序列化隔离级别中,读一致性从通常的语句级扩展成整个事务级。事务中读取的任何行,再次读时保证是相同的。序列化事务不会遇到脏读、不可重复读、幻影读的问题。

       Oracle允许序列化事务修改数据,不过如果有其它事务修改,那么这个事物必须在序列化事务开始之前就提交。当一个序列化事务企业修改一行,而该行被别的事务修改,且在序列化事务开始之后才提交,这时候会报ORA-08177:Cannot serialize access for this transaction。此时,应用可以采取以下动作:

  • 提交事务
  • 执行其它不同的语句,也许会回滚到之前的savepoint
  • 回滚整个事务

       下面显示一个序列化事务是如何与其它事务交互的。如果一个序列化任务不去尝试修改其它事务在序列化事务开始后提交的数据,那么serialized access问题可以避免。

事务1事务2事务3
SQL> SELECT last_name, salary
FROM employees WHERE last_name 
IN ('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9500
 

 

SQL> UPDATE employees SET salary
= 7000 WHERE last_name = 'Banda';
 

事务1是默认的 READ COMMITTED

 
SQL> SET TRANSACTION ISOLATION
LEVEL SERIALIZABLE;

 

 
SQL> SELECT last_name, salary
FROM employees WHERE last_name 
IN ('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9500

 

 
SQL> UPDATE employees SET salary =
9900 WHERE last_name = 'Greene';

 

SQL> INSERT INTO employees
(employee_id, last_name, email,
hire_date, job_id) VALUES (210,
'Hintz', 'JHINTZ', SYSDATE,
'SH_CLERK');
 

 

SQL> COMMIT;
 

 

SQL> SELECT last_name, salary
FROM employees WHERE last_name 
IN ('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               7000
Greene              9500
Hintz
SQL> SELECT last_name, salary
FROM employees WHERE last_name IN
('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               6200
Greene              9900

注意:oracle的读一致性使得事务2的前后读取是一致的,即事务1的插入和更新操作对事务2来说是不可见的

 
COMMIT;

 

SQL> SELECT last_name, salary
FROM employees WHERE last_name
IN ('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               7000
Greene              9900
Hintz
SQL> SELECT last_name, salary 
FROM employees WHERE last_name 
IN ('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               7000
Greene              9900
Hintz

 

SQL> UPDATE employees SET salary
= 7100 WHERE last_name = 'Hintz';
 

 

 
SQL> SET TRANSACTION ISOLATION
LEVEL SERIALIZABLE;

 

 
SQL> UPDATE employees SET salary =
7200 WHERE last_name = 'Hintz';

-- prompt does not return

 

SQL> COMMIT;
 

 

 
UPDATE employees SET salary = 7200
WHERE last_name = 'Hintz'
*
ERROR at line 1:
ORA-08177: can't serialize access
for this transaction

报错原因在于事务3的提交是在事务4开始之后,没有满足序列化

 
SQL> ROLLBACK;

事务2回滚以结束事务

 
SQL> SET TRANSACTION ISOLATION
LEVEL SERIALIZABLE;

 

 
SQL> SELECT last_name, salary 
FROM employees WHERE last_name 
IN ('Banda','Greene','Hintz');
 
LAST_NAME         SALARY
------------- ----------
Banda               7100
Greene              9500
Hintz               7100

 

 
SQL> UPDATE employees SET salary =
7200 WHERE last_name = 'Hintz';

1 row updated.

 

 
SQL> COMMIT;

 

3.Read-Only事务隔离级别

       只读隔离级别和序列化隔离级别很像,只是在只读事务中,不允许有修改操作,除非是用sys用户。因此只读事务不会有ORA-08177错误,只读事务在产生一个报告时很有效。

 

——限于精力,今天先写到这里,明天继续更新下篇——锁机制、手动锁、用户自定义锁。

       参考资料:《Oracle官网在线帮助文档

 

原文地址:https://www.cnblogs.com/jpcflyer/p/9164100.html