4.11 事务、并发和锁

4.11.1 什么是ACID

对于一组相关操作,通常需要其全部成功或全部失败,事务有4大特性:

  1. 原子性(Atomicity):事务必须以一个整体单元的形式进行工作,对于其数据的修改,要么全都执行,要么全都不执行。如果只执行事务中多个操作的前半部分就出现错误,那么必须回滚所有的操作,让数据在逻辑上回滚到原先的状态

  2. 一致性(Consistency):在事务完成时,必须使所有的数据都保持在一致状态

  3. 隔离性(Isolation):事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务是不会查看中间状态的数据的

  4. 持久性(Durability):事务完成之后,它对于系统的影响是永久性的。即使今后出现致命的系统故障(如机器重启、断电),数据也将一直保持

4.11.2 DDL事务

  1. 在PostgreSQL中,可以将DDL存放在事务中,解决创建出错的麻烦

4.11.3 事务的使用方法

  1. PostgreSQL默认自动提交时开启的状态

  2. 命令关闭自动提交

    \set AUTOCOMMIT off

4.11.4 SAVEPOINT

  1. 暂存点,第一部分执行成功后,可以创建一个暂存点,后续部分执行失败,则回滚到暂存点,而非全部回滚

  2. 执行SQL

    begin;
    
    // 第一部分SQL
    sql
    
    // 设置暂存点
    savepoint 暂存点名称;
    
    // 执行其余部分SQL
    
    
    // 如果失败,回滚到暂存点:
    rollback to SAVEPOINT 暂存点名称;
    
    
    // 执行成功,提交
    commit;

4.11.5 事务隔离级别

  1. 事务隔离级别有一下4种:

    1. READ UNCOMMITTED:读未提交。

    2. READ COMMITTED:读已提交。

    3. REPEATABLE READ:重复读。

    4. SERIALIZABLE:串行化

  2. 对于并发事务,我们不希望发生不一致的情况,这类情况的级别从高到低排序如下

    1. 脏读:一个事务读取了另一个未提交事务写入的数据。这是我们最不希望发生的,因为如果发生了脏读,则在并发控制上,应用程序会变得很复杂。

    2. 不可重复读:指一个事务重新读取前面读取过的数据时,发现该数据已经被另一个已提交事务修改了。在大多数情况下,这还是可以接受的,只是在少数情况下会出现问题。

    3. 幻读:一个事务开始后,需要根据数据库中现有的数据做一些更新,于是重新执行一个查询,返回一套符合查询条件的行,这时发现这些行因为其他最近提交的事务而发生了改变,此时现有的事务如果再进行下去的话就可能会导致数据在逻辑上的一些错误。

4.11.6 两阶段提交

  1. 两阶段提交协议有如下5个步骤

    1. 应用程序先调用各台数据库做一些操作,但不提交事务。然后应用程序调用事务协调器(该协调器可能也是由应用自己实现的)中的提交方法。

    2. 事务协调器将联络事务中涉及的每台数据库,并通知它们准备提交事务,这是第一阶段的开始。PostgreSQL中一般是调用PREPARE TRANSACTION命令。

    3. 各台数据库接收到PREPARE TRANSACTION命令后,如果要返回成功,则数据库必须将自己置于如下状态:确保后续能在被要求提交事务的时候提交事务,或后续能在被要求回滚事务的时候回滚事务。所以PostgreSQL会将已准备好提交的信息写入持久存储区中。如果数据库无法完成此事务,它会直接返回失败给事务协调器。

    4. 事务协调器接收所有数据库的响应。

    5. 在第二阶段,如果任何一个数据库在第一阶段返回失败,则事务协调器将会发一个回滚命令“ROLLBACK PREPARED”给各台数据库。如果所有数据库的响应都是成功的,则向各台数据库发送COMMIT PREPARED命令,通知各台数据库事务成功

4.11.7 死锁及防范

  1. 死锁是指两个或两个以上的事务在执行过程中相互持有对方期待的锁,若没有其他机制,它们都将无法进行下去

  2. 死锁的发生必须具备以下4个必要条件

    1. 互斥条件:指事务对所分配到的资源加了排它锁,即在一段时间内只能由一个事务加锁占用。如果此时还有其他进程请求排它锁,则请求者只能等待,直至持有排它锁的事务释放排它锁。

    2. 请求和保持条件:指事务已经至少持有了一把排它锁,但又提出了新的排它锁请求,而该资源上的排它锁已被其他事务占有,此时请求被阻塞,但同时它对自己已获得的排它锁又持有不放。

    3. 不剥夺条件:指事务已获得的锁在未使用完之前不能被其他进程剥夺,只能在使用完时由自己释放。

    4. 环路等待条件:指在发生死锁时,必然存在一个事务—资源的环形链,即事务集合{T0,T1,T2,…,Tn}中的T0正在等待T1持有的排它锁;P1正在等待P2持有的排它锁,……,Pn正在等待已被P0持有的排它锁。

4.11.8 表级锁命令LOCK TABLE

  1. 语法:

    LOCK [ TABLE ] [ ONLY ] 表名 [, ...] [ IN lockmode MODE ] [ NOWAIT ]

    备注:参数解析

    lockmode:就是前面介绍的几种表级锁模式,即ACCESS SHARE、ROW SHARE、ROW EXCLUSIVE、SHARE UPDATE EXCLUSIVE、SHARE、SHARE ROW EXCLUSIVE、EXCLUSIVE、ACCESS EXCLUSIVE。

    NOWAIT:如果没有NOWAIT这个关键字,当无法获得锁时会一直等待,而如果加了NOWAIT关键字,在无法立即获取该锁时,此命令会立即退出并且报错。

4.11.9 行级锁命令

  1. 显式的行级锁命令是由SELECT命令后面加如下子句来构成的

  2. 语法:

    SELECT .....    FOR { UPDATE | SHARE } [ OF table_name [, ...] ] [ NOWAIT ] [...] ]

4.11.11 锁的查看

  1. 可通过查询系统视图“pg_locks”来查看

最后更新于