>

浅析Oracle中的事务,数据库设计与事务处理

- 编辑:大富豪棋牌游戏下载 -

浅析Oracle中的事务,数据库设计与事务处理

事务(transaction)其实就是一序列数据库命令,他们可以组合在一起当做一个单一逻辑单元

1. 什么是事务

在一个事务被标记为完成之前(commited),任何改变可以被回滚(rolled back)

在数据库中事务是工作的逻辑单元,一个事务是由一个或多个完成一组的相关行为的SQL语句组成,通过事务机制确保这一组SQL语句所作的操作要么都成功执行,完成整个工作单元操作,要么一个也不执行。

例子:转账,它可以包括(减少发送钱账户的金额量,增加收款人的金额量,给发送方和接收方都增加交易记录)

如:网上转帐就是典型的要用事务来处理,用以保证数据的一致性。

如果连接被打断,或者说一些操作失败了,所有的操作都需要被回滚

2. 事务特性

→ Total failure is better than partial failure

SQL92标准定义了数据库事务的四个特点:

 

  • 原子性(Atomicity):一个事务里面所有包含的SQL语句是一个执行整体,不可分割,要么都做,要么都不做。
  • 一致性(Consistency):事务开始时,数据库中的数据是一致的,事务结束时,数据库的数据也应该是一致的。
  • 隔离性(Isolation):是指数据库允许多个并发事务同时对其中的数据进行读写和修改的能力,隔离性可以防止事务的并发执行时,由于他们的操作命令交叉执行而导致的数据不一致状态。
  • 持久性 (Durability) : 是指当事务结束后,它对数据库中的影响是永久的,即便系统遇到故障的情况下,数据也不会丢失。

每一个事务的终止信号都是通过commit或者roll back来传达的,没有commit,我们做出的改变不会被保存

一组SQL语句操作要成为事务,数据库管理系统必须保证这组操作的原子性(Atomicity)、一致性(consistency)、隔离性(Isolation)和持久性(Durability),这就是ACID特性。

但其实Sesssions都是有自动提交功能(auto-commit)的,也就是任何命令都能被马上提交,不需要roll back和commit,这种auto-commit方式都是被默认的(set sutocommit = 0 关闭autocommit)

3. 数据异常

→ START TRANSACTION or BEGIN

因为Oracle中支持多个事务并发执行,所以会出现下面的数据异常。

BEGIN;

3.1 脏读

UPDATE accounts SET balance = balance - 100.00 WHERE acc_nr = 254231426;

当一个事务修改数据时,另一事务读取了该数据,但是第一个事务由于某种原因取消对数据修改,使数据返回了原状态,这是第二个事务读取的数据与数据库中数据不一致,这就叫脏读。

UPDATE accounts SET balance = balance 100.00 WHERE acc_nr = 676535301;

如:事务T1修改了一条数据,但是还未提交,事务T2恰好读取到了这条修改后了的数据,此时T1将事务回滚,这个时候T2读取到的数据就是脏数据。

INSERT INTO transfers (acc_nr, amount, reference) VALUES (254231426, -100.00, ’Dentist’);

3.2 不可重复读

INSERT INTO transfers (acc_nr, amount, reference) VALUES (676535301, 100.00, ’John Smith’);

是指一个事务读取数据库中的数据后,另一个事务则更新了数据,当第一个事务再次读取其中的数据时,就会发现数据已经发生了改变,这就是不可重复读取。不可重复读取所导致的结果就是一个事务前后两次读取的数据不相同。

COMMIT;

如:事务T1读取一行记录,紧接着事务T2修改了T1刚刚读取的记录,然后T1再次查询,发现与第一次读取的记录不同。

 

3.3 幻读

同时事务:

如果一个事务基于某个条件读取数据后,另一个事务则更新了同一个表中的数据,这时第一个事务再次读取数据时,根据搜索的条件返回了不同的行,这就是幻读。

复杂情况:多种同时事务

如:事务T1读取一条指定where条件的语句,返回结果集。此时事务T2插入一行新记录,恰好满足T1的where条件。然后T1使用相同的条件再次查询,结果集中可以看到T2插入的记录,这条新纪录就是幻读。

情况1:按顺序执行事务

事务中遇到的这些异常与事务的隔离性设置有关,事务的隔离性设置越多,异常就出现的越少,但并发效果就越低,事务的隔离性设置越少,异常出现的越多,并发效果越高。

  很简单去实行,在一些情况下工作的很好

4. 事务隔离级别

  但是有时过慢了:

针对读取数据时可能产生的不一致现象,在SQL92标准中定义了4个事务的隔离级别:

    CPU可以保持空闲当I/O转换过程中,反之亦然

隔离级别 脏读 不可重复读 幻读
Read uncommitted(读未提交)
Read committed(读已提交)
Repeatable read(可重复读)
Serializable(串行读)

    慢的语句可能会阻碍快的那个

Oracle默认的隔离级别是read committed。

    事务可能需要去等待用户输入

Oracle支持上述四种隔离级别中的两种:read committed 和serializable。除此之外,Oralce中还定义Read only和Read write隔离级别。

情况2:事务的交错执行

Read only:事务中不能有任何修改数据库中数据的操作语句,是Serializable的一个子集。

  等待时间一半都减少特别明显了

Read write:它是默认设置,该选项表示在事务中可以有访问语句、修改语句,但不经常使用。

  在单命令事务中也能进行工作(分裂成多种更小的操作)

设置隔离级别

  但是难以正确实施进行

设置一个事务的隔离级别:

  这就是DBMS所做的事

  • SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
  • SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
  • SET TRANSACTION READ ONLY;
  • SET TRANSACTION READ WRITE;

 

注意:这些语句是互斥的,不能同时设置两个或两个以上的选项。

ACID规则:

设置单个会话的隔离级别:

ACID规则是摆正多个并行事务能被可靠处理的一组方法策略

  • ALTER SESSION SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
  • ALTER SESSION SET TRANSACTION ISOLATION SERIALIZABLE;

Atomicity:原子性   Consistency:一致性   Isolation:交易之间不被相互作用     Durability:持久性

还可以通过JDBC来设置事务的级别:

复杂的是隔离或可串行化的:确保并行执行相当于对某个顺序的顺序执行。

Connection对象中的方法 void setTransactionIsolation(int level) 可以设置事务级别。其中级别用数字来表示,

这就是并发控制的全部内容。

TRANSACTION_READ_UNCOMMITTED = 1;
TRANSACTION_READ_COMMITTED   = 2;
TRANSACTION_REPEATABLE_READ  = 4;
TRANSACTION_SERIALIZABLE     = 8;

注意:为了保持DB编程的可管理性,DBMS确保串行化是至关重要的,想象一下担心其他查询会妨碍运行的任何查询!

查看当前的事务方法 int getTransactionIsolation() 。

 

5. 事务控制命令

并发控制:

5.1 提交事务

查询被分成许多较小的操作。

在执行使用COMMIT语句可以提交事务,当执行了COMMIT语句后,会确认事务的变化,结束事务,删除保存点,释放锁。当使用COMMIT语句结束事务之后,其他会话将可以查看到事务变化后的新数据。

允许对这些操作进行交错。

5.2 回滚事务

启用并发性

保存点(savepoint):是事务中的一点,用于取消部分事务,当结束事务时,会自动的删除该事务所定义的所有保存点。当执行ROLLBACK时,通过指定保存点可以回退到指定的点。

 

设置保存点:

但如果使用相同的数据,则会出现问题。

sql> Savepoint a;

不能更新(Lost Update)

删除保存点:

不好识别(Dirty Reads)

sql> Release Savepoint a;

不能一致识别(Inconsistent Reads)

回滚部分事务:

 

sql> Rollback To a;

并发控制的目的:

回滚全部事务:

允许通用的并发,但禁止危险的相互作用。

sql> Rollback;

 

6. 数据库锁

Lost update:

数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。

图片 1

在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改;加了共享锁的数据对象可以被其他事务读取,但不能修改。

假设有人花了50,并得到了2000,两个事务是同时发生的

6.1 锁分类

语句则被分成很多带顺序的低级操作:

根据保护对象的不同,Oracle数据库锁可分为:

图片 2

  • DML lock(data locks,数据锁):用于保护数据的完整性。
  • DDL lock(dictionary locks,字典锁):用于保护数据库对象的结构(例如表、视图、索引的结构定义)。
  • Internal locks 和latches(内部锁与闩):保护内部数据库结构。
  • Distributed locks(分布式锁):用于OPS(并行服务器)中。
  • PCM locks(并行高速缓存管理锁):用于OPS(并行服务器)中。

下面是交错操作:

在Oracle中最主要的锁是DML锁,DML锁的目的在于保证并发情况下的数据完整性。在Oracle数据库中,DML锁主要包括TM锁和TX锁,其中TM锁称为表级锁,TX锁称为事务锁或行级锁。

图片 3

锁出现在数据共享的场合,用来保证数据的一致性。当多个会话同时修改一个表时,需要对数据进行相应的锁定。

这种方式下在账户里增加2000的效率丢失了

锁有“共享锁”、“排它锁”,“共享排它锁”等多种类型,而且每种类型又有“行级锁” (一次锁住一条记录),“页级锁” (一次锁住一页,即数据库中存储记录的最小可分配单元),“表级锁” (锁住整个表)。

 

6.2 共享锁(S锁)

Dirty Reads

可通过lock table in share mode命令添加该S锁。在该锁定模式下,不允许任何用户更新表。但是允许其他用户发出select …from for update命令对表添加RS锁。

当事务读取由另一事务修改的值时,会发生Dirty Reads,但修改不是最终的。

6.3 排他锁(X锁)

图片 4

可通过lock table in exclusive mode命令添加X锁。在该锁定模式下,其他用户不能对表进行任何的DML和DDL操作,该表上只能进行查询。

交错操作如下:

6.4 行级共享锁(RS锁)

图片 5

通常是通过select … from for update语句添加的,同时该方法也是我们用来手工锁定某些记录的主要方法。比如,当我们在查询某些记录的过程中,不希望其他用户对查询的记录进行更新操作,则可以发出这样的语句。当数据使用完毕以后,直接发出rollback命令将锁定解除。当表上添加了RS锁定以后,不允许其他事务对相同的表添加排他锁,但是允许其他的事务通过DML语句或lock命令锁定相同表里的其他数据行。

返回余额0,即便已经被回滚了

6.5 行级排他锁(RX锁)

 

当进行DML操作时会自动在被更新的表上添加RX锁,或者也可以通过执行lock命令显式的在表上添加RX锁。在该锁定模式下,允许其他的事务通过DML语句修改相同表里的其他数据行,或通过lock命令对相同表添加RX锁定,但是不允许其他事务对相同的表添加排他锁(X锁)。

Inconsistent Reads

6.6 共享行级排他锁(SRX锁)

不一致(不可重复)的读取 发生在在另一事务之间改变时读值多次

通过lock table in share row exclusive mode命令添加SRX锁。该锁定模式比行级排他锁和共享锁的级别都要高,这时不能对相同的表进行DML操作,也不能添加共享锁。

交错操作如下:

上述几种锁模式中,RS锁是限制最少的锁,X锁是限制最多的锁。它们的兼容关系如下:

图片 6

图片 7

最大余额的账户寻找不到

基本上所有的锁都可以由Oracle内部自动创建和释放,但是其中的DDL和DML锁是可以通过命令进行管理的,命令语法:

 

LOCK table_name IN 
    [row share][row exclusive][share][share row exclusive][exclusive] MODE 
[NOWAIT];

读/写锁

下图列出产生锁定模式的SQL语句:

目标:

图片 8

防止对同一数据的并发访问

当程序对所做的修改进行提交(Commit)或回滚(Rollback)后,锁住的资源便会得到释放,从而允许其他用户进行操作。如果两个事务,分别锁定一部分数据,而都在等待对方释放锁才能完成事务操作,这种情况下就会发生死锁

但要尽量减少对性能的影响

7. 数据库事务实现机制

只在必要时限制以防止问题

几乎所有的数据库管理系统中,事务管理的机制都是通过使用日志文件来实现的,我们来简单介绍一下日志的工作方式。

 

当用户执行一条修改数据库的DML语句时,DBMS自动在日志文件中写一条记录,显示被这条语句影响的每一条记录的两个副本。一个副本显示变化前的记录,另一个副本显示变化后的记录。当日志写完之后,DBMS才实际对磁盘中的记录进行修改。

区分读写操作/访问

如果用户随后执行COMMIT语句,事务结束也被记录在事务日志中。如果用户执行ROLLBACK语句,DBMS检查日志,找出自事务开始以来被修改的记录“以前”的样子,然后使用这些信息恢复它们以前的状态,有效地撤销事务期间对数据库所做的修改。

交错读取操作是无害的。

如果系统出错,系统操作员通常通过运行DBMS提供的特殊恢复程序来复原数据库。恢复程序检查到事务日志末尾,查找故障之前没有被提交的事务。恢复程序回滚没有完全完成的事务,以便仅有被提交的事务反映到数据库中,而故障中正处理的事务被回滚。

交错写入操作会引起问题。

事务日志的使用明显增加了更新数据库的开销。在实际中,主流商用DBMS产品使用的日志技术比上述描述的方案更复杂,用以减小这种开销。此外,事务日志通常被存储在高速磁盘驱动器中,不同于存储数据库的磁盘,以减小磁盘访问竞争。某些个人计算机DBMS产品允许关闭事务日志性能,以提高DBMS的性能。

交错读写操作也会引起问题。

8. 示例

 

银行转帐的例子是最经典的事务示例:

两种锁:

用户把钱从一个银行账号转账至另一个银行账号,需要将资金从一个银行账号中取出,然后再存入另一个银行账号中。理想来说,这两次操作都应该成功。但是,如果有错误发生,则两次操作都应该失败,否则的话,操作之后其中一个账号中的金额将会是错误的,整个操作过程应该是原子性的,两个操作都是一个原子事务操作的一部分。

读锁(又名共享锁)

示例:

写锁(又名专用锁)

图片 9

可以有多个读锁或单个写锁

-- 从账户一向账户二转账
DECLARE
  v_money NUMBER(8, 2); -- 转账金额
  v_balance account.balance%TYPE; -- 账户余额
BEGIN
  v_money := &转账金额; -- 输入转账金额
  -- 从账户一减钱  
  UPDATE account SET balance = balance - v_money WHERE id=&转出账户
  RETURNING balance INTO v_balance;
  IF SQL%NOTFOUND THEN
    RAISE_APPLICATION_ERROR(-20001, '没有该账户:'||&转出账户);
  END IF;
  IF v_balance < 0 THEN
    RAISE_APPLICATION_ERROR(-20002, '账户余额不足');
  END IF;

  -- 向账户二加钱
  UPDATE account SET balance = balance   v_money WHERE id=&转入账户;
  IF SQL%NOTFOUND THEN
    RAISE_APPLICATION_ERROR(-20001, '没有该账户:'||&转入账户);
  END IF;

  -- 如果没有异常,则提交事务
  COMMIT;
  DBMS_OUTPUT.PUT_LINE('转账成功');

  EXCEPTION
    WHEN OTHERS THEN 
      ROLLBACK; -- 出现异常则回滚事务
      DBMS_OUTPUT.PUT_LINE('转账失败:');
      DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;

 

图片 10

在事务访问数据之前,它必须获取锁,如果不可能,稍后再尝试

 

锁定可以在不同级别下发生:数据库级,表级,页面级别(最常见),行级

原文连接:Oracle数据库之事务

 

数据库级别:只需要单个锁,但不并发

行级:大并发,但需要的锁太多

表和页级锁提供了一个折衷方案

 

多查询事务随时间获取锁,在需要时获得很容易。

但是什么时候释放它们呢?

越快越好并发,但有问题

那么,如果我们释放一个锁,然后又需要它会发生什么?

那么,如果我们释放一个锁,然后需要一个不同的锁会发生什么?

图片 11

二相锁定协议

成长阶段:获取锁(无锁释放)  收缩阶段:释放锁(没有获得的锁)也就是在事务结束时释放所有锁

防止有问题的操作交错

图片 12

两阶段锁定(带表级锁):

图片 13

死锁(Deadlocks)

两个事务可能互相等待释放锁。

  因为两者都在等待,没有一个释放他们的锁。

  也可以发生两个以上的事务,这种僵局被称为僵局。

  两相锁定不能防止死锁

图片 14

死锁可以用不同的方式处理:

死锁预防:

  如果发生僵局,就采取行动

  可能不必要中止交易

死锁检测:

   如果发生死锁,只能采取行动

  用于检测死锁的开销

额外的问题:饥饿

为了解决死锁,事务需要中止(如果可能的话重新开始)

相同的事务可能会一再中止(它“饿死”了)。

解决方案:总是中止上次提交的事务

  所有的事务都是时间戳的。

  当中止并重新启动时,保留原始时间戳。

 

死锁预防:

等待-死亡方案:

如果T1需要一个T2持有的锁:

(等待)如果T1大于T2,等待锁定被释放。

(死亡)如果T1较小,中止T1并稍后重启。

 

伤口等待方案:如果T1需要一个T2持有的锁:

(受伤)如果T1大于T2,则中止T2并重新启动。

(等待)如果T1较小,等待锁定被释放。

 

两种方案的共同点:

死锁被解决的了

年轻的过程中止了——没有饥饿

即使没有死锁,事务也可能中止

 

死锁检测:

目标:只有死锁发生时才会中止

 

构造和维护等待图

每个事务都是一个节点。

如果Ti在等待Tj持有的锁,则从TI到TJ的有向边。

如果图包含任何循环,则为死锁。如果检测到循环,则中止一个事务以中断循环 例如:最年轻的交易——没有饥饿。

维护等待图和检测周期是昂贵的

最适合于很少并发事务的数据库,或者当重新启动事务是有问题的(例如中间结果已经返回)

 

死锁解析由DBMS处理,但并非所有的实现都能防止饥饿。,频繁死锁减速处理(DBMS可能需要一些帮助)

谨慎地构造交易可以减少僵局:

  提前锁定(在事务开始时),减少锁定操作交错的机会

  以固定顺序获取所有事务的锁,防止死锁,但对于大的代码库是困难的

在每种情况下,都必须声明所需的锁。

DBMS提供用于获取/释放锁的工具

 

DBMSs还允许禁用/减弱锁定,防止/减少死锁,加快查询速度,但能制造出不可复制的讨厌的bugs

在大多数情况下,这是个坏方法!


频繁类型的死锁:先读取数据,然后更新

图片 15

许多DBMS提供更新锁来处理这个问题。

只允许读取数据

不能有多个更新锁(或更新 写锁),防止上述类型的死锁

允许单个更新锁 多个读锁,不阻止读写锁定

必须由用户明确声明,例如

图片 16

 

 

数据库恢复

当事务中止时,必须进行更改。

事务被终止可能由于

  用户发出的显式回滚

  死锁、超时或错误

  数据库管理系统崩溃

在DBMS崩溃的情况下,我们需要确保

  所有未提交的事务都会回滚。

  所有已提交事务的更改被保存。

更新通常只在内存中执行,更快,但在DBMS崩溃期间丢失

 

事务日志

所有数据修改都记录在事务日志中。

  存储“前后”和“后”值

  允许改变颠倒

  必须在数据更改之前发生日志记录。(日志没有变化是可以的,没有日志的更改是坏的)

元数据也存储在事务日志中。

  事务的开始/结束(提交或回滚)

  检查点(从内存保存到磁盘的数据),可以识别哪些更新丢失了

当DBMS崩溃时,重新启动它

  读取事务日志

  标识未提交的事务并将它们滚回。

  识别并重新应用已提交事务的丢失更新

dB以一致状态(原子性)开始。

所有承诺的事务持续(持久性)

 

Summary

事务将数据库命令分组为单元

ACID确保DB相互作用是可靠的

交错操作对性能至关重要。

  为了保证隔离,需要进行并发控制

  锁定数据会导致死锁

  解决僵局的协议(DBMS),

  最小化死锁的技术(用户)

使用事务日志的数据库恢

本文由关于我们发布,转载请注明来源:浅析Oracle中的事务,数据库设计与事务处理