explicit-locking.zh.md 14.9 KB
Newer Older
李少辉-开发者's avatar
李少辉-开发者 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
## 13.3.显式锁定

[13.3.1. 桌面锁](explicit-locking.html#LOCKING-TABLES)

[13.3.2. 行级锁](explicit-locking.html#LOCKING-ROWS)

[13.3.3. 页面级锁](explicit-locking.html#LOCKING-PAGES)

[13.3.4. 僵局](explicit-locking.html#LOCKING-DEADLOCKS)

[13.3.5. 顾问锁](explicit-locking.html#ADVISORY-LOCKS)

[](<>)

PostgreSQL提供了各种锁模式来控制对表中数据的并发访问。在MVCC不能提供所需行为的情况下,这些模式可用于应用程序控制的锁定。此外,大多数PostgreSQL命令会自动获取适当模式的锁,以确保在命令执行时不会以不兼容的方式删除或修改引用的表。(例如,`截断`无法安全地与同一表上的其他操作同时执行,因此它会获得`访问独占`锁定桌子以强制执行。)

要检查数据库服务器中当前未完成的锁的列表,请使用[`pg_锁`](view-pg-locks.html)系统视图。有关监控锁管理器子系统状态的更多信息,请参阅[第28章](monitoring.html).

### 13.3.1.桌面锁

[](<>)

下面的列表显示了可用的锁定模式以及PostgreSQL自动使用它们的上下文。您还可以使用命令显式获取这些锁中的任何一个[](sql-lock.html).记住,所有这些锁模式都是表级锁,即使名称中包含单词“row”;锁定模式的名称是历史记录。在某种程度上,这些名称反映了每个锁模式的典型用法——但语义都是相同的。一种锁模式和另一种锁模式之间唯一的真正区别是它们之间冲突的锁模式集(请参见[表13.2](explicit-locking.html#TABLE-LOCK-COMPATIBILITY)).两个事务不能同时在同一个表上持有冲突模式的锁。(然而,交易从不与自身发生冲突。例如,它可能获得`访问独占`锁定并稍后获取`访问共享`锁在同一张桌子上。)非冲突锁模式可以由多个事务同时持有。请特别注意,某些锁定模式是自冲突的(例如`访问独占`锁一次不能由多个事务持有),而其他事务不会自相矛盾(例如`访问共享`锁可以由多个事务持有)。

**表级锁定模式**

`访问共享`

`访问独占`仅限锁定模式。

这个`选择`命令在引用的表上获取此模式的锁。一般来说,任何查询*读到*一个表,如果不修改它,将获得此锁定模式。

`行共享`

`独家``访问独占`锁定模式。

这个`选择更新``选择共享`命令在目标表上获取此模式的锁(除了`访问共享`锁定已引用但未选中的任何其他表`更新/分享`).

`排他`

`共有`, `共享行独占`, `独家``访问独占`锁定模式。

命令`使现代化`, `删去``插入`在目标表上获取此锁定模式(除了`访问共享`锁定任何其他引用的表)。通常情况下,该锁定模式将由以下命令获取:*修改数据*在桌子上。

`共享更新独占`

`共享更新独占`, `共有`, `共享行独占`, `独家``访问独占`锁定模式。此模式保护表不受并发架构更改和`真空`跑。

获得`真空`(没有`满的`), `分析`, `同时创建索引`, `创建统计数据`, `评论`, `同时重新编制索引`,而且是肯定的[`改变索引`](sql-alterindex.html)[`改变桌子`](sql-altertable.html)变体(有关详细信息,请参阅这些命令的文档)。

`共有`

`排他`, `共享更新独占`, `共享行独占`, `独家``访问独占`锁定模式。此模式保护表不受并发数据更改的影响。

获得`创建索引`(没有`同时`).

`共享行独占`

`排他`, `共享更新独占`, `共有`, `共享行独占`, `独家``访问独占`锁定模式。此模式保护表不受并发数据更改的影响,并且是自排他的,因此一次只能有一个会话保存它。

获得`创建触发器`还有一些形式的[`改变桌子`](sql-altertable.html).

`独家`

`行共享`, `排他`, `共享更新独占`, `共有`, `共享行独占`, `独家``访问独占`锁定模式。此模式只允许并发`访问共享`锁,也就是说,只有从表中读取数据才能与持有该锁模式的事务并行进行。

获得`同时刷新物化视图`.

`访问独占`

与所有模式的锁冲突(`访问共享`, `行共享`, `排他`, `共享更新独占`, `共有`, `共享行独占`, `独家``访问独占`).此模式保证持有人是以任何方式访问表的唯一交易。

被政府收购`升降台`, `截断`, `重新索引`, `簇`, `真空满``刷新物化视图`(没有`同时`)命令。多种形式的`改变索引``改变桌子`也可以在这个级别获得一个锁。这也是默认的锁定模式`锁桌`不显式指定模式的语句。

### 提示

只有一个`访问独占`锁块a`选择`(没有`更新/分享`)声明。

一旦获得,锁通常会一直保持到交易结束。但是,如果在建立保存点后获得了锁,则如果保存点回滚到,锁将立即释放。这符合以下原则:`回降`取消保存点之后命令的所有效果。PL/pgSQL异常块中获取的锁也是如此:从该块中的错误转义将释放在该块中获取的锁。

**表13.2。冲突锁模式**

| 请求的锁定模式 | 现有锁定模式 |  |  |  |  |  |  |  |
| ------- | :----: | --- | --- | --- | --- | --- | --- | --- |
| `访问共享` | `行共享` | `排不包括。` | `共享更新不包括。` | `共有` | `共享行不包括。` | `不包括。` | `访问不包括。` |  |
| `访问共享` |  |  |  |  |  |  |  | 十、 |
| `行共享` |  |  |  |  |  |  | 十、 | 十、 |
| `排不包括。` |  |  |  |  | 十、 | 十、 | 十、 | 十、 |
| `共享更新不包括。` |  |  |  | 十、 | 十、 | 十、 | 十、 | 十、 |
| `共有` |  |  | 十、 | 十、 |  | 十、 | 十、 | 十、 |
| `共享行不包括。` |  |  | 十、 | 十、 | 十、 | 十、 | 十、 | 十、 |
| `不包括。` |  | 十、 | 十、 | 十、 | 十、 | X | X | X |
| `ACCESS EXCL.` | X | X | X | X | X | X | X | X |

### 13.3.2. Row-Level Locks

In addition to table-level locks, there are row-level locks, which are listed as below with the contexts in which they are used automatically by PostgreSQL. See[Table 13.3](explicit-locking.html#ROW-LOCK-COMPATIBILITY)for a complete table of row-level lock conflicts. Note that a transaction can hold conflicting locks on the same row, even in different subtransactions; but other than that, two transactions can never hold conflicting locks on the same row. Row-level locks do not affect data querying; they block only*writers and lockers*to the same row. Row-level locks are released at transaction end or during savepoint rollback, just like table-level locks.

**Row-Level Lock Modes**

`FOR UPDATE`

`FOR UPDATE`causes the rows retrieved by the`SELECT`statement to be locked as though for update. This prevents them from being locked, modified or deleted by other transactions until the current transaction ends. That is, other transactions that attempt`UPDATE`,`DELETE`,`SELECT FOR UPDATE`, `选择无密钥更新`, `选择共享``选择密钥共享`在当前事务结束之前,这些行中的任何一行都将被阻止;相反地`选择更新`将等待在同一行上运行任何这些命令的并发事务,然后锁定并返回更新的行(如果行已删除,则不返回行)。在一分钟之内`可重复读取``可序列化`但是,如果要锁定的行在事务启动后发生了更改,则会抛出一个错误。有关进一步讨论,请参阅[第13.4节](applevel-consistency.html).

这个`更新`锁定模式也可由任何`删去`一排,还有一个`使现代化`这会修改某些列的值。当前,考虑用于`使现代化`case是那些具有唯一的索引,可以在外键中使用的索引(因此不考虑部分索引和表达式索引),但这在将来可能会发生变化。

`无需密钥更新`

行为类似于`更新`,但获取的锁较弱:此锁不会阻止`选择密钥共享`试图在同一行上获取锁的命令。该锁定模式也可由任何用户获取`使现代化`这并不意味着`更新`

`分享`

行为类似于`无需密钥更新`,但它会在每个检索到的行上获取共享锁,而不是独占锁。共享锁会阻止其他事务执行`使现代化`, `删去`, `选择更新``选择无密钥更新`在这些行上,但这并不阻止它们执行`选择共享``选择密钥共享`.

`关键份额`

行为类似于`分享`,除了锁较弱外:`选择更新`被阻止了,但没有`选择无密钥更新`.密钥共享锁阻止其他事务执行`删去`或任何`使现代化`这会更改键值,但不会更改其他键值`使现代化`它也不能阻止`选择无密钥更新`, `选择共享``选择密钥共享`.

PostgreSQL不记得有关内存中修改行的任何信息,因此一次锁定的行数没有限制。但是,锁定一行可能会导致磁盘写入,例如:。,`选择更新`修改选定行以将其标记为锁定,因此将导致磁盘写入。

**表13.3。行级锁冲突**

| 请求的锁定模式 | 当前锁定模式 |  |  |  |
| ------- | ------ | --- | --- | --- |
| 关键份额 | 分享 | 无需密钥更新 | 更新 |  |
| 关键份额 |  |  |  | 十、 |
| 分享 |  |  | 十、 | 十、 |
| 无需密钥更新 |  | 十、 | 十、 | 十、 |
| 更新 | 十、 | 十、 | 十、 | 十、 |

### 13.3.3.页面级锁

除了表锁和行锁之外,页级共享/排他锁还用于控制对共享缓冲池中表页的读/写访问。这些锁在获取或更新行后立即释放。应用程序开发人员通常不需要关心页面级锁,但为了完整性,这里提到了它们。

### 13.3.4.僵局

[](<>)

使用显式锁定可以增加*僵局*,其中两个(或更多)事务各自持有对方想要的锁。例如,如果事务1获取了表A上的独占锁,然后尝试获取表B上的独占锁,而事务2已经独占锁定了表B,现在需要表A上的独占锁,那么两个事务都不能继续。PostgreSQL会自动检测死锁情况,并通过中止其中一个相关事务来解决死锁,允许其他事务完成。(准确地说,哪一笔交易将被中止很难预测,也不应该依赖。)

请注意,死锁也可能是行级锁的结果(因此,即使不使用显式锁,死锁也可能发生)。考虑两个并发事务修改一个表的情况。第一个事务执行:

```
UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111;
```

这将在具有指定帐号的行上获取行级锁。然后,执行第二个事务:

```
UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;
```

第一个`更新`语句成功获取指定行的行级别锁,因此成功更新该行。然而,第二个`更新`语句发现它试图更新的行已经被锁定,因此它等待获取锁的事务完成。事务二现在正在等待事务1完成,然后再继续执行。现在,事务一执行:

```
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;
```

事务一试图在指定的行上获取行级别的锁,但不能:事务二已经持有这样的锁。所以它等待事务2完成。因此,事务一在事务2上被阻塞,事务二在事务一上被阻止:死锁条件。PostgreSQL将检测到这种情况并中止其中一个事务。

对于死锁的最好防御通常是确保所有使用数据库的应用程序都以一致的顺序获取多个对象上的锁,从而避免死锁。在上面的示例中,如果两个事务都以相同的顺序更新了行,则不会发生死锁。还应该确保在事务中获取的对象上获得的第一个锁是该对象所需的最严格的模式。如果事先验证这一点不可行,那么可以通过重试由于死锁而中止的事务来实时处理死锁。

只要未检测到死锁情况,寻求表级或行级锁的事务将无限期地等待释放冲突的锁。这意味着应用程序不应该长时间(例如,在等待用户输入的同时)保持事务打开。

### 13.3.5.咨询锁

[](<>)[](<>)

PostgreSQL提供了一种创建具有应用程序定义含义的锁的方法。这些被称为*咨询锁*,因为系统没有强制使用它们——正确使用它们取决于应用程序。对于MVCC模型来说,咨询锁对于锁定策略非常有用。例如,咨询锁的一个常用方法是模拟所谓“平面文件”数据管理系统典型的悲观锁定策略。虽然存储在表中的标志可以用于相同的目的,但是咨询锁的速度更快,避免了表bloat,并且在会话结束时服务器会自动清理。

在PostgreSQL中获取咨询锁有两种方法:会话级或事务级。一旦在会话级别获得,咨询锁将一直保持到显式释放或会话结束为止。与标准锁请求不同,会话级咨询锁请求不遵守事务语义:在回滚之后,在随后回滚的事务期间获取的锁仍然会保持不变,同样,即使调用事务在以后失败,解锁也会有效。锁可以通过其拥有的进程多次获得;对于每个已完成的锁请求,在实际释放锁之前,必须有相应的解锁请求。另一方面,事务级锁请求的行为更像常规的锁请求:它们在事务结束时自动释放,并且没有显式的解锁操作。对于短期使用咨询锁,这种行为通常比会话级行为更方便。对于相同的咨询锁标识符,会话级别和事务级锁请求将以预期的方式相互阻止。如果会话已经持有给定的咨询锁,则即使其他会话正在等待锁,它的其他请求也将始终成功;无论现有锁保持和新请求处于会话级别还是事务级别,此语句都是真的。

与PostgreSQL中的所有锁一样,任何会话当前持有的一个完整的咨询锁列表可以在[`pg_锁`](view-pg-locks.html)系统视图。

建议锁和常规锁都存储在共享内存池中,其大小由配置变量定义[最大值\_锁\_每\_交易](runtime-config-locks.html#GUC-MAX-LOCKS-PER-TRANSACTION)[最大值\_连接](runtime-config-connection.html#GUC-MAX-CONNECTIONS)。必须注意不要耗尽此内存,否则服务器将无法授予任何锁。这对服务器可授予的建议锁的数量施加了上限,通常为数十到几十万,具体取决于服务器的配置方式。

在某些情况下,使用建议锁定方法,尤其是在涉及显式排序和`限度`子句中,由于SQL表达式的求值顺序,必须小心控制获取的锁。例如:

```
SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- danger!
SELECT pg_advisory_lock(q.id) FROM
(
  SELECT id FROM foo WHERE id > 12345 LIMIT 100
) q; -- ok
```

在上面的查询中,第二种形式是危险的,因为`限度`不保证在执行锁定功能之前应用。这可能会导致获取一些应用程序不期望的锁,因此无法释放(直到会话结束)。从应用程序的角度来看,这样的锁将是悬挂的,尽管在应用程序中仍然可以看到`pg_锁`.

中介绍了用于操作建议锁的功能[第9.27.10节](functions-admin.html#FUNCTIONS-ADVISORY-LOCKS).