1. 什么是事务sql
在数据库中事务是工做的逻辑单元,一个事务是由一个或多个完成一组的相关行为的SQL语句组成,经过事务机制确保这一组SQL语句所做的操做要么都成功执行,完成整个工做单元操做,要么一个也不执行。数据库
如:网上转账就是典型的要用事务来处理,用以保证数据的一致性。缓存
2. 事务特性服务器
SQL92标准定义了数据库事务的四个特色:并发
一组SQL语句操做要成为事务,数据库管理系统必须保证这组操做的原子性(Atomicity)、一致性(consistency)、隔离性(Isolation)和持久性(Durability),这就是ACID特性。oracle
3. 数据异常分布式
由于Oracle中支持多个事务并发执行,因此会出现下面的数据异常。性能
3.1 脏读spa
当一个事务修改数据时,另外一事务读取了该数据,可是第一个事务因为某种缘由取消对数据修改,使数据返回了原状态,这是第二个事务读取的数据与数据库中数据不一致,这就叫脏读。日志
如:事务T1修改了一条数据,可是还未提交,事务T2刚好读取到了这条修改后了的数据,此时T1将事务回滚,这个时候T2读取到的数据就是脏数据。
3.2 不可重复读
是指一个事务读取数据库中的数据后,另外一个事务则更新了数据,当第一个事务再次读取其中的数据时,就会发现数据已经发生了改变,这就是不可重复读取。不可重复读取所致使的结果就是一个事务先后两次读取的数据不相同。
如:事务T1读取一行记录,紧接着事务T2修改了T1刚刚读取的记录,而后T1再次查询,发现与第一次读取的记录不一样。
3.3 幻读
若是一个事务基于某个条件读取数据后,另外一个事务则更新了同一个表中的数据,这时第一个事务再次读取数据时,根据搜索的条件返回了不一样的行,这就是幻读。
如:事务T1读取一条指定where条件的语句,返回结果集。此时事务T2插入一行新记录,刚好知足T1的where条件。而后T1使用相同的条件再次查询,结果集中能够看到T2插入的记录,这条新纪录就是幻读。
事务中遇到的这些异常与事务的隔离性设置有关,事务的隔离性设置越多,异常就出现的越少,但并发效果就越低,事务的隔离性设置越少,异常出现的越多,并发效果越高。
4. 事务隔离级别
针对读取数据时可能产生的不一致现象,在SQL92标准中定义了4个事务的隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted(读未提交) | 是 | 是 | 是 |
Read committed(读已提交) | 否 | 是 | 是 |
Repeatable read(可重复读) | 否 | 否 | 是 |
Serializable(串行读) | 否 | 否 | 否 |
Oracle默认的隔离级别是read committed。
Oracle支持上述四种隔离级别中的两种:read committed 和serializable。除此以外,Oralce中还定义Read only和Read write隔离级别。
Read only:事务中不能有任何修改数据库中数据的操做语句,是Serializable的一个子集。
Read write:它是默认设置,该选项表示在事务中能够有访问语句、修改语句,但不常用。
设置隔离级别
设置一个事务的隔离级别:
注意:这些语句是互斥的,不能同时设置两个或两个以上的选项。
设置单个会话的隔离级别:
5. 事务控制命令
5.1 提交事务
在执行使用COMMIT
语句能够提交事务,当执行了COMMIT语句后,会确认事务的变化,结束事务,删除保存点,释放锁。当使用COMMIT语句结束事务以后,其余会话将能够查看到事务变化后的新数据。
5.2 回滚事务
保存点(savepoint):是事务中的一点,用于取消部分事务,当结束事务时,会自动的删除该事务所定义的全部保存点。当执行ROLLBACK时,经过指定保存点能够回退到指定的点。
设置保存点:
sql> Savepoint a;
删除保存点:
sql> Release Savepoint a;
回滚部分事务:
sql> Rollback To a;
回滚所有事务:
sql> Rollback;
6. 数据库锁
数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的状况。若对并发操做不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。
在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其余的事务不能对它读取和修改;加了共享锁的数据对象能够被其余事务读取,但不能修改。
6.1 锁分类
根据保护对象的不一样,Oracle数据库锁可分为:
在Oracle中最主要的锁是DML锁,DML锁的目的在于保证并发状况下的数据完整性。在Oracle数据库中,DML锁主要包括TM锁和TX锁,其中TM锁称为表级锁,TX锁称为事务锁或行级锁。
锁出如今数据共享的场合,用来保证数据的一致性。当多个会话同时修改一个表时,须要对数据进行相应的锁定。
锁有“共享锁”、“排它锁”,“共享排它锁”等多种类型,并且每种类型又有“行级锁” (一次锁住一条记录),“页级锁” (一次锁住一页,即数据库中存储记录的最小可分配单元),“表级锁” (锁住整个表)。
6.2 共享锁(S锁)
可经过lock table in share mode命令添加该S锁。在该锁定模式下,不容许任何用户更新表。可是容许其余用户发出select …from for update命令对表添加RS锁。
6.3 排他锁(X锁)
可经过lock table in exclusive mode命令添加X锁。在该锁定模式下,其余用户不能对表进行任何的DML和DDL操做,该表上只能进行查询。
6.4 行级共享锁(RS锁)
一般是经过select … from for update语句添加的,同时该方法也是咱们用来手工锁定某些记录的主要方法。好比,当咱们在查询某些记录的过程当中,不但愿其余用户对查询的记录进行更新操做,则能够发出这样的语句。当数据使用完毕之后,直接发出rollback命令将锁定解除。当表上添加了RS锁定之后,不容许其余事务对相同的表添加排他锁,可是容许其余的事务经过DML语句或lock命令锁定相同表里的其余数据行。
6.5 行级排他锁(RX锁)
当进行DML操做时会自动在被更新的表上添加RX锁,或者也能够经过执行lock命令显式的在表上添加RX锁。在该锁定模式下,容许其余的事务经过DML语句修改相同表里的其余数据行,或经过lock命令对相同表添加RX锁定,可是不容许其余事务对相同的表添加排他锁(X锁)。
6.6 共享行级排他锁(SRX锁)
经过lock table in share row exclusive mode命令添加SRX锁。该锁定模式比行级排他锁和共享锁的级别都要高,这时不能对相同的表进行DML操做,也不能添加共享锁。
上述几种锁模式中,RS锁是限制最少的锁,X锁是限制最多的锁。它们的兼容关系以下:
基本上全部的锁均可以由Oracle内部自动建立和释放,可是其中的DDL和DML锁是能够经过命令进行管理的,命令语法:
LOCK table_name IN [row share][row exclusive][share][share row exclusive][exclusive] MODE [NOWAIT];
下图列出产生锁定模式的SQL语句:
当程序对所作的修改进行提交(Commit)或回滚(Rollback)后,锁住的资源便会获得释放,从而容许其余用户进行操做。若是两个事务,分别锁定一部分数据,而都在等待对方释放锁才能完成事务操做,这种状况下就会发生死锁
7. 数据库事务实现机制
几乎全部的数据库管理系统中,事务管理的机制都是经过使用日志文件来实现的,咱们来简单介绍一下日志的工做方式。
当用户执行一条修改数据库的DML语句时,DBMS自动在日志文件中写一条记录,显示被这条语句影响的每一条记录的两个副本。一个副本显示变化前的记录,另外一个副本显示变化后的记录。当日志写完以后,DBMS才实际对磁盘中的记录进行修改。
若是用户随后执行COMMIT语句,事务结束也被记录在事务日志中。若是用户执行ROLLBACK语句,DBMS检查日志,找出自事务开始以来被修改的记录“之前”的样子,而后使用这些信息恢复它们之前的状态,有效地撤销事务期间对数据库所作的修改。
若是系统出错,系统操做员一般经过运行DBMS提供的特殊恢复程序来复原数据库。恢复程序检查到事务日志末尾,查找故障以前没有被提交的事务。恢复程序回滚没有彻底完成的事务,以便仅有被提交的事务反映到数据库中,而故障中正处理的事务被回滚。
事务日志的使用明显增长了更新数据库的开销。在实际中,主流商用DBMS产品使用的日志技术比上述描述的方案更复杂,用以减少这种开销。此外,事务日志一般被存储在高速磁盘驱动器中,不一样于存储数据库的磁盘,以减少磁盘访问竞争。某些我的计算机DBMS产品容许关闭事务日志性能,以提升DBMS的性能。
8. 示例
银行转账的例子是最经典的事务示例:
用户把钱从一个银行帐号转帐至另外一个银行帐号,须要将资金从一个银行帐号中取出,而后再存入另外一个银行帐号中。理想来讲,这两次操做都应该成功。可是,若是有错误发生,则两次操做都应该失败,不然的话,操做以后其中一个帐号中的金额将会是错误的,整个操做过程应该是原子性的,两个操做都是一个原子事务操做的一部分。
示例:
-- 从帐户一贯帐户二转帐 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;