为了账号安全,请及时绑定邮箱和手机立即绑定

数据库事务隔离级别详解及在PostgreSQL中的实现

在软件行业中,有时我们会遇到这样的情况:当我们尝试提交事务时,结果却不像预期的那样,而且在不同的数据库服务器上,它们们的反应方式似乎各不相同。今天我们就来聊聊事务隔离级别,它们是什么,那么为什么它们在数据库理论中很重要,它们为何如此重要。

I. 4个层次的现象:

要理解隔离级别的事务,我们首先需要了解一些问题或异常行为。在数据库系统中,当多个事务并发执行时,尤其是在隔离级别维护不当的情况下,会遇到各种意外或不期望的行为。简单来说,这就是多个事务同时运行时可能出现的问题。

按照Postgresql的文档,我们把现象分为四个层次

a. 脏读

一个事务读到了另一个尚未提交的并发事务写的数据。

这就是脏读的一个例子:

  • 交易 A 修改了一些数据但未提交(数据存储在共享缓存中 — 了解更多请参阅文档
  • 交易 B 在交易 A 提交之前读取了修改后的数据。
  • 如果 交易 A 回滚,交易 B 读取的数据将失效,导致不一致。
b. 一次性读取(一次读)

一个事务再次读取它之前读取的数据,发现该数据已被其他事务修改过。

当不可重复读取发生时:

  • 交易 A 从数据库读了一个值。
  • 交易 B 修改(或删除)了该相同的值并提交了更改。
  • 交易 A 再次读取该值,看到的是更新后的值或者根本没有值。

例如:

    -- 事务A--读取数据  
    BEGIN; -- 开始事务  
    SELECT balance FROM accounts WHERE id = 1;  -- 会返回100  

    -- 事务B--修改数据并提交事务  
    BEGIN;  
    UPDATE accounts SET balance = 200 WHERE id = 1;  
    COMMIT;  

    -- 事务A--再次读取数据  
    SELECT balance FROM accounts WHERE id = 1;  -- 现在会返回200
c) 幻读。

一个事务再次执行查询,该查询返回满足特定条件的一组行,发现由于另一个刚提交的事务,满足条件的行集已发生变化。

在一个具体的场景中,发生了幻读现象。

  • 事务 A 执行一个查询,根据某个条件检索一组行。
  • 事务 B 执行插入、更新或删除操作,这些操作影响了事务 A 查询条件匹配的行,并提交了更改。
  • 事务 A 重新执行相同的查询,并发现返回的行不同。

例子:

    BEGIN;  
    -- 返回2行:Alice, Bob  
    SELECT * FROM employees WHERE salary = 100;    

    BEGIN;  
    INSERT INTO employees (id, name, salary) VALUES (3, 'Charlie', 100);  
    COMMIT;  

    -- 现在返回3行:Alice, Bob, Charlie  
    SELECT * FROM employees WHERE salary = 100;  
d.: 序列化异常

成功提交的交易组结果与逐一按所有可能顺序执行这些交易的结果不一致。

进行多个事务时,可能会出现序列异常。

  • 对同一数据进行重叠操作(读取、写入或修改)。
  • 依赖彼此的中间状态。
  • 引入它们之间行动的循环依赖关系,从而导致无法通过任何顺序执行来重现的结果。

比如:

    SELECT balance FROM accounts WHERE id = 1;  -- 读取到 100  
    UPDATE accounts SET balance = balance - 50 WHERE id = 1;  -- 账户1余额减少50  

    SELECT balance FROM accounts WHERE id = 2;  -- 读取到 200  
    UPDATE accounts SET balance = balance - 50 WHERE id = 2;  -- 账户2余额减少50  

    UPDATE accounts SET balance = balance + 50 WHERE id = 2;  -- 账户2余额增加50  

    UPDATE accounts SET balance = balance + 50 WHERE id = 1;  -- 账户1余额增加50
II. 四种隔离级别的事务处理

在Postgresql中设置隔离事务级别:

    将事务隔离级别设置为 <LEVEL>(可以是读取未提交、读取已提交、可重复读、可串行化之一);
    LEVEL 可以为 [READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE]

将上面的分号改为句号:

    将事务隔离级别设置为 <LEVEL>(可以是读取未提交、读取已提交、可重复读、可串行化之一)。
    LEVEL 可以为 [READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE]

PostgreSQL 的读未提交隔离级别表现类似于读已提交模式。这是因为这是将标准的隔离级别映射到 PostgreSQL 的多版本并发存取架构的唯一合理方式。(PostgreSQL文档)

a. 读已提交隔离

这是PostgreSQL的默认模式,在这种模式下,服务器基本上只能读取已提交到数据库中的数据。当一个SELECT查询开始时,它会从数据库中创建一个快照,并在整个查询过程中使用这个快照,直到查询完成提交或回调之前。不仅SELECT查询,其他查询也以这种方式查找目标行。但是,UPDATE和DELETE操作不允许并发执行。如果两个UPDATE/DELETE操作同时发生,第一个UPDATE/DELETE操作提交后,第二个才能执行。

可以这样表示:

从Postgres官网有一个挺有趣的例子。

例如,我们有两个这样的并发事务。

    _________  
    | id | hit|  
    |1   | 10 |  
    |2   | 11 |  
    -----------
    BEGIN;  
    (1) 将website表中的hits字段的值加1;  
    -- 在另一个会话中运行:    
    (2) 删除website表中hits等于10的记录;  
    COMMIT;

结果一行也没删掉,当 DELETE 事务发现 hits = 10 的时候,它匹配上了 ID = 1,但随后需要等待 UPDATE 事务提交,因为此时那个事务被锁定了。等 UPDATE 事务提交后,id 为 1 的网站现在点击数变成了 11,不再符合之前的条件,所以最终还是没行可删。

可重复读隔离

可重复读隔离级别只能看到在事务开始之前提交的数据;它既不会看到未提交的数据,也不会看到在事务执行期间其他事务提交的数据。(但是,每个查询可以看到自己事务中先前更新的效果,即使这些更新还未提交。)

与“读已提交”隔离级别相比,如果有并发的更新或删除操作,第二个事务会收到 ERROR: could not serialize access due to concurrent update 错误,并重试直到不再有冲突为止。可重复读隔离级别确保了每个事务看到的都是数据库的一致且稳定的视图。

c. 可序列化隔离水平

[Serializable(可串行化)]隔离级别提供最严格的事务隔离。此级别为所有已提交的事务模拟串行事务执行;就像它们是按顺序执行的一样,而不是并发执行。实际上,这个隔离级别的工作方式与可重复读完全相同,只是它还监控可能导致并发执行的串行事务行为不符合所有可能的串行(一次一个)执行顺序的条件。这种监控不会引入额外的阻塞,但会增加一定的监控开销。如果检测到可能导致 serialization anomaly(串行化异常)的条件,则会触发 serialization failure(串行化失败)。

比如:

    _______________________  
    | id   |  balance     |  
    | 1    | 100         |  
    | 2    | 200         |  
    -----------------------  
    -- 交易 A  
    BEGIN;  
    SELECT SUM(balance) FROM accounts;  
    UPDATE accounts SET balance = 50 WHERE account_id = 1;  
    -- 

    -- 交易 B   
    BEGIN;  
    SELECT SUM(balance) FROM accounts;  
    UPDATE accounts SET balance = 150 WHERE account_id = 2;  
    --
  • 交易 A 读取初始总余额。

  • 交易 B 启动但不能读取或更新记录,直到这个交易 A 提交之前

可序列化的隔离级别适合需要严格一致性和正确性的场景,但它以牺牲性能和并发性为代价。它确保数据库的行为如同所有事务都是按顺序执行过程的一样,使其成为最安全但也是最严格的隔离级别。

III. 结论篇

事务隔离级别对于确保数据库系统中的数据的一致性和完整性至关重要,特别是在并发环境中。每个隔离级别在性能数据准确性之间找到平衡,因此,根据应用程序的需求选择合适的级别非常重要。

  1. 读未提交:提供最高性能但牺牲了数据完整性。适用于优先考虑速度而不是准确性的情况,例如日志记录或非关键性分析。
  2. 读已提交:对于大多数应用程序来说是一个实际的平衡点,防止未提交的数据读取,同时允许一些并发异常情况。它通常是数据库的默认隔离级别,并且适用于通用场景。
  3. 可重复读:通过防止未提交的数据读取和非重复读取来提供更强的一致性。然而,它并不能完全消除幻读,因此对于需要对同一数据进行一致读取的用例(如库存管理或财务计算)来说是最适合的。
  4. 可串行化:最严格的隔离级别,确保完全的事务隔离。它防止所有并发问题的出现,但会带来显著的性能影响。这种级别适用于数据准确性不容有任何妥协的关键系统,如银行或库存管理系统。

理解每个隔离级别的行为和权衡让开发人员和数据库管理员能够根据他们的具体需求优化事务管理,确保系统的可靠性和性能得到保障。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消