3、PostgreSQL 事务

事务是数据库系统的基础概念。

事务本质上有四个特点 —— ACID:

  1. Atomicity 原子性
    原子性体现在对于一个事务来讲,必须要以一个整体单元的形式进行工作。对于数据的修改,要么全部执行,要么全部不执行。出错必须回滚到先前的状态。

  2. Consistency 一致性
    一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。

一致性有下面特点:

  • 如果一个操作触发辅助操作(级联,触发器),这些也必须成功,否则交易失败。
  • 如果系统是由多个节点组成,一致性规定所有的变化必须传播到所有节点(多主复制)。如果从站节点是异步更新,那么我们打破一致性规则,系统成为“最终一致性”。
  • 一个事务是数据状态的切换,因此,如果事务是并发多个,系统也必须如同串行事务一样操作。
  • 在现实中,事务系统遭遇并发请求时,这种串行化是有成本的, Amdahl法则描述如下:它是描述序列串行执行和并发之间的关系。“一个程序在并行计算情况下使用多个处理器所能提升的速度是由这个程序中串行执行部分的时间决定的。” 大多数数据库管理系统选择(默认情况下)是放宽一致性,以达到更好的并发性。
  1. Isolation 隔离性
    事务是并发控制机制,他们交错使用时也能提供一致性。隔离让我们隐藏来自外部世界未提交的状态变化,一个失败的事务不应该破坏系统的状态。隔离是通过用悲观或乐观锁机制实现的。

  2. Durability 持久性
    一个成功的事务将永久性地改变系统的状态,所以在它结束之前,所有导致状态的变化都记录在一个持久的事务日志中。如果我们的系统突然受到系统崩溃或断电,那么所有未完成已提交的事务可能会重新执行。

SQL标准规定了四个隔离级别:

  1. READ_UNCOMMITTED 读未提交
  2. READ_COMMITTED 读已提交
  3. REPETABLE_READ 可重复读
  4. SERIALIZABLE 可序列化
隔离级别 脏读 不可重复读 幻读 序列化异常
READ_UNCOMMITTED 允许 (PG中不允许) 允许 允许 允许
READ_COMMITTED 不允许 允许 允许 允许
REPETABLE_READ 不允许 不允许 允许 (PG中不允许) 允许
SERIALIZABLE 不允许 不允许 不允许 不允许
  • 脏读
    一个事务读取了另一个并行未提交事务写入的数据。

  • 不可重复读
    一个事务重新读取之前读取过的数据,发现该数据已经被另一个事务(在初始读之后提交)修改。

  • 幻读
    一个事务重新执行一个返回符合一个搜索条件的行集合的查询, 发现满足条件的行集合因为另一个最近提交的事务而发生了改变。

  • 序列化异常
    成功提交一组事务的结果与这些事务所有可能的串行执行结果都不一致。

PostgreSQL 中的事务

PostgreSQL事务的隔离级别目前有4种,分别是:读未提交,读已提交,可重复读,串行化。在PostgreSQL里,你可以请求四种可能的事务隔离级别中的任意一种。但是在内部, 实际上只有三种独立的隔离级别,分别对应读已提交,可重复读和可串行化。如果你选择了读未提交的级别, 实际上你用的是读已提交,在重复读的PostgreSQL执行时,幻读是不可能的, 所以实际的隔离级别可能比你选择的更严格。这是因为把标准隔离级别映射到 PostgreSQL 的多版本并发控制架构的唯一合理的方法。

SQL 标准允许更严格的行为:四种隔离级别只定义了哪种现像不能发生,但是没有定义哪种现像必须发生。某些PostgreSQL数据类型和函数关于事务的行为有特殊的规则。特别是,对一个序列的修改(以及用serial声明的一列的计数器)是立刻对所有其他事务可见的,并且在作出该修改的事务中断时也不会被回滚。

默认情况下 PostgreSQL会将每一个SQL语句都作为一个事务来执行。如果我们没有发出BEGIN命令,则每个独立的语句都会被加上一个隐式的BEGIN以及(如果成功)COMMIT来包围它。一组被BEGIN和COMMIT包围的语句也被称为一个事务块。

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
COMMIT;

也可以利用保存点来以更细的粒度来控制一个事务中的语句。保存点允许我们有选择性地放弃事务的一部分而提交剩下的部分。在使用SAVEPOINT定义一个保存点后,我们可以在必要时利用ROLLBACK TO回滚到该保存点。该事务中位于保存点和回滚点之间的数据库修改都会被放弃,但是早于该保存点的修改则会被保存。

在回滚到保存点之后,它的定义依然存在,因此我们可以多次回滚到它。反过来,如果确定不再需要回滚到特定的保存点,它可以被释放以便系统释放一些资源。记住不管是释放保存点还是回滚到保存点都会释放定义在该保存点之前的所有其他保存点。

所有这些都发生在一个事务块内,因此这些对于其他数据库会话都不可见。当提交整个事务块时,被提交的动作将作为一个单元变得对其他会话可见,而被回滚的动作则永远不会变得可见。

BEGIN;
UPDATE accounts SET balance = balance - 100.00
    WHERE name = 'Alice';
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Bob';
-- oops ... forget that and use Wally's account
ROLLBACK TO my_savepoint;
UPDATE accounts SET balance = balance + 100.00
    WHERE name = 'Wally';
COMMIT;

查看数据库事务隔离级别

SELECT name, setting FROM pg_settings WHERE name ='default_transaction_isolation';
或
SELECT current_setting('default_transaction_isolation');

设置全局事务隔离级别

法一:

修改 postgresql.conf 文件中的 default_transaction_isolation

法二:

alter system set default_transaction_isolation to 'REPEATABLE READ';

查看当前会话事务隔离级别

show transaction_isolation
或
SELECT current_setting('transaction_isolation');

设置当前会话事务隔离级别

SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

设置当前事务的事务隔离级别

START TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
或
BEGIN ISOLATION LEVEL READ UNCOMMITTED READ WRITE;

推荐阅读更多精彩内容