说说数据库事务

多条 SQL 语句,要么全部执行成功,要么全部执行失败。

1 特性

数据库事务必须同时满足 4 个特性 ( ACID )。

特性 说明
原子性 Atomic 表示组成一个事务的多次数据库操作是一个不可分割的原子单元,只有所有的操作都执行成功,才提交整个事务 。 事务中的任何一次数据库操作失败,已经执行操作都必须回滚,让数据库返回到操作前的状态 。
一致性 Consistency 事务操作后,数据库所处的状态和它的业务规则是一致的 。比如 A 账户转账到 B 账户,不管操作是否异常, A 账户与 B 账户的总额是不变的。
隔离性 Isolation 在并发操作数据时,不同的事务拥有各自的数据空间,它们的操作既可能地不对对方产生干扰。数据库规定了多种事务隔离级别,不同的隔离级别对应不同的干扰程度 。 隔离级别越高,数据一致性越好,但并发性越差。
持久性 Durability 一旦事务提交成功,事务中所有的数据都必须被持久化到数据库中 。 即使在提交事务后数据库发生崩溃,那么当数据库重启时,也必须保证能够根据日志恢复数据 。

在这些事务特性中,数据的 “ 一致性 ” 是最终目标, 其他特性都是为了达到这个目标而采取的措施或要求。

数据库管理系统采用数据库锁来保证事物的隔离性,当多个事务试图对相同的数据执行操作时,只有持有锁的事务才能真正操作数据。

Oracle 采用了数据版本机制,在回滚阶段为数据的每一种变化都保留了一个版本,修改数据不会影响读取数据 。

2 并发问题

数据库中的相同数据,可能同时被多个事务所访问。所以,如果没有采取必要的隔离措施,就会导致各种并发问题,从而破坏数据的完整性 。

并发问题可以归结为 5 类,包括 3 类数据读问题(脏读 、 不可重复度 、 幻读)和 2 类数据更新问题(第一类丢失更新和第二类丢失更新)。

2.1 脏读(dirty read)

A 事务读取了 B 事务尚未提交的更改数据,并在此数据的基础上进行操作 。 如果此时 B 事务回滚,那么 A 事务之前读到的数据就是脏数据。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 - 查询账户余额(100 元)
3 - 取出 50 元
4 查询账户余额(50 元)【脏读】 -
5 - 回滚事务(账户余额:100 元)
6 存入 100 元 -
7 提交事务(账户余额:150 元) -

这里因为发生脏读,导致账户损失了 50 元(事务 A 存款 100 元,事务 B 无影响,再加上原来的账户余额 100 元,最后的账户余额应该是 200 元才是)。

2.2 不可重复读(unrepeatable read)

不可重复读指的是事务在不同的时间点,读取到的数据不同。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 - 查询账户余额(100 元)
3 查询账户余额(100 元) -
4 - 取款 10 元
5 - 提交事务(账户余额:90 元)
6 查询账户余额(90 元) -

在时间序列 6,与在时间序列 3 时查询到的余额不同,发生不可重复读现象。

2.3 幻读(phantom read)

幻象读一般发生在计算统计数据的事务中 。 A 事务读取了 B 事务提交的新增数据,这时 A 事务将出现幻象读的问题 。

假设在同一个事务中,两次统计名某银行支行所有账户的总金额,在两次统计过程中,刚好新增了一个存款账户 。那么,这两次统计的总金额肯定会不一致 。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 统计(总金额:10 w) -
3 - 新增存款账户(金额:1 w)
4 - 提交事务(总金额:11 w)
5 统计(总金额:11 w)幻读 -

2.4 不可重复读与幻读比较

比较 不可重复读 幻读
读取对象 读到其它事务已经提交的修改或删除数据。 读到其它事务已经提交的新增数据。
采取措施 对所要操作的数据添加级锁,避免这些数据发生变化。 对所要操作的数据所在表添加级锁,即将整张表锁定(在 Oracle 中,是以多版本数据的方式实现的)。

2.5 第一类丢失更新

A 事务回滚时,把 B 事务中已经提交的更新数据给覆盖咯 。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 查询账户余额(100 元) -
3 - 查询账户余额(100 元)
4 - 取款 10 元
5 - 提交事务(账户余额:90 元)
6 存入 10 元 -
7 回滚事务(账户余额:110 元) -

这个问题影响很大。这个例子中,账户余额应该还是 100 元(取款 10 元,存入 10 元,实际对账户无影响),但因为存在第一类丢失更新,导致银行损失 10 元。如果事务 A 先提交,那么账户将损失 10 元。

2.6 第二类丢失更新

A 事务提交后覆盖了 B 事务已经提交的数据,导致 B 事务所做操作丢失。

时间序列 事务 A 事务 B
1 开始事务 开始事务
2 - 查询账户余额:100 元
3 查询账户余额:100 元 -
4 - 取款 10 元
5 - 提交事务(账户余额:90 元)
6 存款 10 元 -
7 提交事务(账户余额:110 元) -

上述示例,直接导致银行损失 10 元。如果 A 事务先提交,那么将导致账户损失 10 元。

3 锁机制

分类方式 类别
锁定对象 表锁定(整张表)、行锁定(特定行)
并发事务锁定关系 共享锁定(运行其它的共享锁定,但防止独占锁定)、独占锁定(防止任何锁定)

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 显式获取。 所有锁定 -

上式表中的防止与允许列都是针对其它会话而言的。

4 事务的隔离级别

因为直接使用锁比较麻烦,所以数据库为我们设置了事务的隔离级别,这些级别实现了自动锁机制 。 设置好事务的隔离级别后,数据库就会分析事务中的 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。

5 JDBC 事务

我们可以通过 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 语句因为在保存点之后,所以被回滚咯。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容

  • 一、事务 1、事务四要素:ACID 对于事务,我之前的理解是很粗糙的,不就是为了保证操作的原子性么?一般订单系统或...
    张伟科阅读 1,283评论 0 5
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,912评论 2 89
  • 宝贝睡着了,手摸体温正常了,突然有空,真的很难得,珍惜并记录一下。 这个国庆过得有点槽,把所有出行计划打乱,宝贝从...
    厦门嘟嘟阅读 295评论 0 0
  • 对于个人发展来说,假如有两个岗位摆在大家面前: A:大公司,业务专员,薪酬较低(譬如年收入10万)。 B:小公司,...
    会元vip阅读 1,016评论 0 0
  • 因为长久不运动,每日的晨跑对我来说是要空腹吃下的第一只青蛙。虽然已经是易效能践行第六天,但我大腿和小腿的肌肉酸,胀...
    阳光滕君阅读 378评论 0 0