多条 SQL 语句,要么所有执行成功,要么所有执行失败。sql
数据库事务必须同时知足 4 个特性 ( ACID )。数据库
特性 | 说明 |
---|---|
原子性 Atomic | 表示组成一个事务的屡次数据库操做是一个不可分割的原子单元,只有全部的操做都执行成功,才提交整个事务 。 事务中的任何一次数据库操做失败,已经执行操做都必须回滚,让数据库返回到操做前的状态 。 |
一致性 Consistency | 事务操做后,数据库所处的状态和它的业务规则是一致的 。好比 A 帐户转帐到 B 帐户,无论操做是否异常, A 帐户与 B 帐户的总额是不变的。 |
隔离性 Isolation | 在并发操做数据时,不一样的事务拥有各自的数据空间,它们的操做既可能地不对对方产生干扰。数据库规定了多种事务隔离级别,不一样的隔离级别对应不一样的干扰程度 。 隔离级别越高,数据一致性越好,但并发性越差。 |
持久性 Durability | 一旦事务提交成功,事务中全部的数据都必须被持久化到数据库中 。 即便在提交事务后数据库发生崩溃,那么当数据库重启时,也必须保证可以根据日志恢复数据 。 |
在这些事务特性中,数据的 “ 一致性 ” 是最终目标, 其余特性都是为了达到这个目标而采起的措施或要求。bash
数据库管理系统采用数据库锁来保证事物的隔离性,当多个事务试图对相同的数据执行操做时,只有持有锁的事务才能真正操做数据。并发
Oracle 采用了数据版本机制,在回滚阶段为数据的每一种变化都保留了一个版本,修改数据不会影响读取数据 。oracle
数据库中的相同数据,可能同时被多个事务所访问。因此,若是没有采起必要的隔离措施,就会致使各类并发问题,从而破坏数据的完整性 。性能
并发问题能够归结为 5 类,包括 3 类数据读问题(脏读 、 不可重复度 、 幻读)和 2 类数据更新问题(第一类丢失更新和第二类丢失更新)。url
A 事务读取了 B 事务还没有提交的更改数据,并在此数据的基础上进行操做 。 若是此时 B 事务回滚,那么 A 事务以前读到的数据就是脏数据。spa
时间序列 | 事务 A | 事务 B |
---|---|---|
1 | 开始事务 | 开始事务 |
2 | - | 查询帐户余额(100 元) |
3 | - | 取出 50 元 |
4 | 查询帐户余额(50 元)【脏读】 | - |
5 | - | 回滚事务(帐户余额:100 元) |
6 | 存入 100 元 | - |
7 | 提交事务(帐户余额:150 元) | - |
这里由于发生脏读,致使帐户损失了 50 元(事务 A 存款 100 元,事务 B 无影响,再加上原来的帐户余额 100 元,最后的帐户余额应该是 200 元才是)。日志
不可重复读指的是事务在不一样的时间点,读取到的数据不一样。code
时间序列 | 事务 A | 事务 B |
---|---|---|
1 | 开始事务 | 开始事务 |
2 | - | 查询帐户余额(100 元) |
3 | 查询帐户余额(100 元) | - |
4 | - | 取款 10 元 |
5 | - | 提交事务(帐户余额:90 元) |
6 | 查询帐户余额(90 元) | - |
在时间序列 6,与在时间序列 3 时查询到的余额不一样,发生不可重复读现象。
幻象读通常发生在计算统计数据的事务中 。 A 事务读取了 B 事务提交的新增数据,这时 A 事务将出现幻象读的问题 。
假设在同一个事务中,两次统计名某银行支行全部帐户的总金额,在两次统计过程当中,恰好新增了一个存款帐户 。那么,这两次统计的总金额确定会不一致 。
时间序列 | 事务 A | 事务 B |
---|---|---|
1 | 开始事务 | 开始事务 |
2 | 统计(总金额:10 w) | - |
3 | - | 新增存款帐户(金额:1 w) |
4 | - | 提交事务(总金额:11 w) |
5 | 统计(总金额:11 w)幻读 | - |
比较 | 不可重复读 | 幻读 |
---|---|---|
读取对象 | 读到其它事务已经提交的修改或删除数据。 | 读到其它事务已经提交的新增数据。 |
采起措施 | 对所要操做的数据添加行级锁,避免这些数据发生变化。 | 对所要操做的数据所在表添加表级锁,即将整张表锁定(在 Oracle 中,是以多版本数据的方式实现的)。 |
A 事务回滚时,把 B 事务中已经提交的更新数据给覆盖咯 。
时间序列 | 事务 A | 事务 B |
---|---|---|
1 | 开始事务 | 开始事务 |
2 | 查询帐户余额(100 元) | - |
3 | - | 查询帐户余额(100 元) |
4 | - | 取款 10 元 |
5 | - | 提交事务(帐户余额:90 元) |
6 | 存入 10 元 | - |
7 | 提交事务(帐户余额:110 元) | - |
这个问题影响很大。这个例子中,帐户余额应该仍是 100 元(取款 10 元,存入 10 元,实际对帐户无影响),但由于存在第一类丢失更新,致使银行损失 10 元。若是事务 A 先提交,那么帐户将损失 10 元。
A 事务提交后覆盖了 B 事务已经提交的数据,致使 B 事务所作操做丢失。
时间序列 | 事务 A | 事务 B |
---|---|---|
1 | 开始事务 | 开始事务 |
2 | - | 查询帐户余额:100 元 |
3 | 查询帐户余额:100 元 | - |
4 | - | 取款 10 元 |
5 | - | 提交事务(帐户余额:90 元) |
6 | 存款 10 元 | - |
7 | 提交事务(帐户余额:110 元) | - |
上述示例,直接致使银行损失 10 元。若是 A 事务先提交,那么将致使帐户损失 10 元。
分类方式 | 类别 |
---|---|
锁定对象 | 表锁定(整张表)、行锁定(特定行) |
并发事务锁定关系 | 共享锁定(运行其它的共享锁定,但防止独占锁定)、独占锁定(防止任何锁定) |
oracle 数据库中常见的锁定:
锁定 | 说明 | 防止 | 容许 |
---|---|---|---|
行共享锁定 | 可经过 select for update 语句隐式得到该锁定,或者经过 LOCK TABLE IN ROW SHARE MODE 语句显式获取 。 |
表独占锁定 | 行共享锁定、行独占锁定、表共享行独占锁定 |
行独占锁定 | 可经过 insert、update、delete 语句隐式获取,或者经过 LOCK TABLE IN ROW EXCLUSIVE MODE 语句显式获取 。 |
行或表共享锁定、行或表独占锁定 | - |
表共享锁定 | 可经过 LOCK TABLE IN SHARE MODE 语句显式获取。该锁定可让会话具备对表事务级的一致性访问,由于其余会话在用户提交或者回滚该事务并释放对该表的锁定以前,不能更改这张表 。 |
表共享行独占锁定、表独占锁定 | 行共享锁定、表共享锁定 |
表共享行独占锁定 | 可经过 LOCK TABLE IN SHARE ROW EXCLUSIVE MODE 语句显式获取。 |
表共享行独占锁定、行独占锁定、表独占锁定 | 其它行的共享锁定 |
表独占锁定 | 可经过 LOCK TABLE IN EXCLUSIVE MODE 显式获取。 |
全部锁定 | - |
上式表中的防止与容许列都是针对其它会话而言的。
由于直接使用锁比较麻烦,因此数据库为咱们设置了事务的隔离级别,这些级别实现了自动锁机制 。 设置好事务的隔离级别后,数据库就会分析事务中的 SQL 语句,而后自动为事务所操做的数据加上适合的锁 。 并且,数据库还会维护这些锁,当一个资源上的锁数目太多时,就会自动升级,从而提升系统的运行性能。这些过程对咱们来讲是彻底透明的。
ANSI/ISO SQL 92 定义了 4 个等级的隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻读 | 第一类丢失更新 | 第二类丢失更新 |
---|---|---|---|---|---|
READ UNCOMMITTED | 容许 | 容许 | 容许 | 不容许 | 容许 |
READ COMMITTED | 不容许 | 容许 | 容许 | 不容许 | 容许 |
REPEATABLE_READ | 不容许 | 不容许 | 容许 | 不容许 | 不容许 |
SERIALIZABLE | 不容许 | 不容许 | 不容许 | 不容许 | 不容许 |
隔离级别与并发性是对立的,READ UNCOMMITTED 并发性最高,而 SERIALIZABLE 的并发性最低。
由于 Oracle 经过多版本机制,完全解决了脏读问题,因此它的 READ COMMITTED 已经达到 SQL 92 定义的 REPEATABLE_READ 标准。
SQL 92 推荐使用的隔离级别是:REPEATABLE_READ。
咱们能够经过 Connection 的 getMetaData()
方法获取 DatabaseMetaData 对象,而后经过该对象的 supportsTransactions()
、supportsTransactionIsolationLevel(int level)
方法查看底层数据库的事务支持状况 。
Connection 在默认状况下是自动提交的,也就是说,每一条执行的 SQL 都对应一个事务。为了可以将多条 SQL 放在一个事务中执行,咱们能够经过 Connection 的 setAutoCommit(false)
来关闭 Connection 的自动提交机制,还能够经过 Connection 的 setTransactionIsolation()
来设置事务的隔离级别, Connection 中定义了 SQL 92 标准中的 4 个事务隔离级别常量 。
Connection connection = null;
try {
String url = "xxx";
//获取数据库链接
connection = DriverManager.getConnection(url);
//关闭自动提交机制
connection.setAutoCommit(false);
//设置事务隔离级别
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
Statement statement = connection.createStatement();
String sql = "xxx";
statement.execute(sql);
//提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//回滚事务
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
复制代码
JDBC2.0 中事务只有提交与回滚操做 。在 JDBC3.0 中(Java1.4+)引入了保存点( SavePoint 接口)。保存点能够把事务分割为多个阶段,这样咱们就能够根据业务要求,来指定须要回滚到的特定保存点啦O(∩_∩)O~
咱们能够经过 DatabaseMetaData 的 supportsSavepoints()
方法验证所链接的数据库是否支持保存点特性 。
Statement statement = connection.createStatement();
String sql1 = "xxx";
statement.execute(sql1);
//设置保存点
Savepoint savepoint=connection.setSavepoint();
String sql2 = "xxx";
statement.execute(sql2);
//回退到保存点
connection.rollback(savepoint);
复制代码
若是事务提交了上段代码, 那么 sql1 语句将有效,而 sql2 语句由于在保存点以后,因此被回滚咯。