codetalks-new / notes

About me
4 stars 0 forks source link

Postgresql 中的事务,隔离级别与锁 #18

Open codetalks-new opened 5 years ago

codetalks-new commented 5 years ago

事务

事务基础

  1. 在 PostgreSQL 中每一个操作都是一个事务.
  2. 证明: now() 函数总是返回当前事务的时间. (同一select 中的 now(),返回都是相同的时间),同一事务中的也是.
  3. begin 语句标记开始多语句事务.
  4. end 及 commit 语句用来提交事务.
  5. rollback 和 abort 都可以用来回滚事务.

事务中的错误处理

  1. 事务中间有错误,后面的执行操作都将被忽略, 此时就算执行的是 end,commit 也是 执行 rollback 操作.

事务的保存点

在上面事务逻辑中,事务中出错之后会导致所有执行都回滚. 如果要允许部分成功.则需要使用保存点.

  1. 创建保存点 savepoint <savepoint_name>
  2. 回滚到某一个保存点 rollback to savepoint <savepoint_name>
  3. 释放一个保存点 release savepoint <savepoint_name>.
  4. 事务结束之后,保存点会自动销毁. PSQL 支持创建成千上万个保存点.

事务性 DDL

对于 DDL 支持事务是 PSQL 的独有强大特性. 一般的 DDL 命令都支持事务.(drop database,create tablespace,drop tablespace 操作除外)

事务隔离级别

先看一个例子. image

左边的事务受其他提交的事务的影响,两次返回的和不一样.这就是因为 PSQL 默认的事务的隔离级别是读提交. 要想左边的事务,一直读到的是同样的值可以采用可重复读的隔离级别. 示例如下: image

  1. 可重复读的使用场景: 报表统计类,希望一开始读取的数据和后面读取的操作一致.
  2. 可重复读本质是 PSQL 冻结了它的数据快照.
  3. 可重复读并不比读提交性能低下.

PSQL 支持的隔离级别

  1. read committed 读提交. (默认隔离级别).
  2. repeatable read 可重复读. (冻结数据快照)
  3. read uncommited 读未提交.(PSQL 将其映射成 read committed,只是标准中有这一级别)
  4. serializable 可序列化(可串行化,Serializable Snapshot Isolation (SSI)). 除非对数据库引擎有很深理解 ,否则一般不建议使用.

隔离级别对应常见的一些问题.

  1. 读未提交(read uncommited) 可能导致脏读 ( Dirty Read). 不过在 PSQL不存在了.
  2. 读提交 (,可能造成不可重复读,或幻读)

不可重复读 VS 幻读

所以可以这么理解,一般另一个事务中的 update 会可能导致不可重复读 (update 修改了数据.) 而 insertdelete 语句可能导致幻读(insert,delete 修改了数据集). 不过 updatedelete 也会生成不可重复读,比如一个统计语句的查询数据结果就会受到 insert,delete 语句的影响.

默认隔离级别读提交的数据可见性规则

如下图,删除语句的执行结果为空. 因为.

  1. 当 delete 语句开始执行时,id 为2的账号满足余额为 250 的要求.其他不满足.
  2. 但是左边会话的事务比 delete 先执行. 由于并发版本控制, delete 语句被阻塞.
  3. 等到 delete 真正开始执行时,左边会话的事务已经提交了, delete 对之前已经命中的行重新进行确认是否满足匹配条件,但是此时已经不匹配, delete 执行会将忽略. delete 也不会再去查询新的匹配条件的语句.

PSQL 多版本并发控制的要读提交是在每一个命令开始时,就创建一个数据的新的快照(包含此刻所有已经提交的事务的数据更新).当前事务的后续语句能看到其他情况下并发提交的事务的变更.

具体来说, delete 开始执行时,收集了匹配的行,但是同时也知道其他正在进行中的事务会影响到匹配的行, delete 将其放到 可能会变更的行的列表. 当其他事务都提交了之后, delete 会重新评估可能会变更列表里的行.如果对应已经被删除或者已经不匹配则忽略. 也就是说 delete 最终看到的是提交之后的数据版本.

对于 select for updateselect for share,锁定和返回给用户的数据也是更新后的版本.

image

并发场景下的数据一致性,乐观锁,悲观锁与 MVCC

乐观锁 (OCC,Optimistic Concurrency Control,乐观并发控制)

它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。 因为乐观锁比较适用于读多写少的情况.

乐观并发控制的事务包括3个阶段:

悲观锁 (PCC, Pessimistic Concurrency Control,悲观并发控制)

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

适用写多读少的情况.

多版本并发控制 (MVCC,Multiversion Concurrency Control)

PSQL 中的并发控制采用 (MVCC), MVCC 的一大优点就是读写互相不阻塞.

MVCC意图解决读写锁造成的多个、长时间的读操作饿死写操作问题。每个事务读到的数据项都是一个历史快照(snapshot)并依赖于实现的隔离级别。写操作不覆盖已有数据项,而是创建一个新的版本,直至所在操作提交时才变为可见。快照隔离使得事物看到它启动时的数据状态。

MVCC使用时间戳 (TS), 或“自动增量的事务ID”实现“事务一致性”。MVCC可以确保每个事务(T)通常不必“读等待”数据库对象(P)。这通过对象有多个版本,每个版本有创建时间戳 与废止时间戳 (WTS)做到的。

MVCC 可以无锁实现(基于时间戳),也可以使用锁, 或者混合使用锁和时间戳.

默认的更新锁

PSQL 默认会锁定被 UPDATE 影响的行. 如下,两个会话的事务同时修改同一行,另一个事务将进入等待. image

参考

1 Mastering PostgreSQL 9.6

  1. PostgreSQL rocks, except when it blocks: Understanding locks