为了确保数据库一致性并支持事务之间适当的隔离级别,数据库服务器使用多种类型的锁。
模式锁
模式锁用于序列化对数据库模式的更改,并确保使用表的事务不受由其它连接发起的模式更改的影响。例如,通过插入新列更改表结构的事务可锁定一个表,这样其它事务就不会受到模式更改的影响。在这种情况下,必须限制其它事务进行访问,以防止出现错误。
行锁
行锁通过允许多用户访问和修改行级别的特定表来确保并发事务之间的一致性。例如,事务可锁定特定的行以防止另一个事务对其进行更改。行锁类有:读(共享)锁定、写(独占)锁定以及意图锁。
表锁
表锁锁定表中的所有行,并在事务更新表时阻止其它事务更新表。表锁类型有:读(共享)锁定、写(独占)锁定以及意图锁。
位置锁
位置锁确保表的顺序扫描或索引扫描内的一致性。事务通常按顺序或使用由索引确定的顺序对行进行扫描。在这两种情况下,都可以将锁放置在扫描位置上。例如,在索引中放置锁可以防止其它事务在索引中插入具有特定值或特定值范围的行。
锁持续时间事务通常会保存锁,直到完成该事务。此行为可防止其它事务进行修改,以免无法回退原始事务。在隔离级别
3
,必须在事务结束之前保存所有锁,以确保事务的可序列化性。当行锁用于实现游标稳定性时,它们不用在事务结束前保存。只要对象行是当前的游标行,就保存这些行锁。在大多数情况下,这段时间要比事务的生存期短。使用
WITH HOLD
打开游标时,可以在连接的生存期内一直保存锁。锁可保持以下时长:
位置
短期锁定,如特定行上用于在隔离级别
1
实现游标稳定性的读锁定。
事务
例如,一直保持到事务结束的行锁、表锁和位置锁。
连接
一直保持到事务结束之后的模式锁,如使用
WITH HOLD
游标时创建的模式锁。
模式锁用于序列化对数据库模式的更改,并确保使用表的事务不受由其它连接发起的模式更改的影响。
例如,共享模式锁可防止
ALTER TABLE
语句在表正被另一连接上打开的游标读取时从该表删除列。
有两类模式锁:共享模式锁和独占模式锁。
当事务直接或间接引用数据库中的表时,会获取共享模式锁。共享模式锁不会互相冲突;任意数量的事务都可以同时在同一表上获取共享模式锁。共享模式锁会一直保持到通过
COMMIT
或
ROLLBACK
完成事务为止。
允许持有共享模式锁的任何连接更改表数据,只要更改不与其它连接冲突。以共享(读取)模式锁定表模式。
通常使用
DDL
语句修改表的模式时,会获取独占模式锁。
ALTER TABLE
语句便是
DDL
语句的一个示例,该语句在对表进行修改之前获取该表的独占模式锁。任何时候只有一个连接可获取表的独占模式锁。锁定该表模式(共享或独占)的所有其它尝试都将阻塞,或失败并产生错误。以隔离级别
0
(最低限制的隔离级别)执行的连接将不能从以独占模式锁定的表中读取行。
只有持有独占表模式锁的连接才能够更改表数据。将表模式锁定为供单个连接独占使用。
行锁可防止更新丢失以及其它类型的事务不一致。
行锁可确保在某个事务完成(通过发出隐式或显式
COMMIT
语句提交更改,或通过
ROLLBACK
语句中止更改)前,由该事务修改的任何行无法被其它事务修改。
有三类行锁:读(共享)锁定、写(独占)锁定以及意图锁。数据库服务器会为每个事务自动获取这些锁。
当事务读取某行时,事务的隔离级别会确定是否获取读锁定。在某一行被放置了读锁定后,任何其它事务都不能在该行上获取写锁定。获取读锁定可确保另一个事务不会修改或删除正在被读取的行。任意数量的事务可以同时在任意一行上获取读锁定,因此读锁定有时也称为共享锁,或非独占锁。
读锁定可保持不同的时间。在隔离级别
2
和
3
上,事务获取的任何读锁定会一直保持到事务通过
COMMIT
或
ROLLBACK
完成为止。这些读锁定称为长期读锁定。
对于以隔离级别
1
执行的事务,数据库服务器在游标所在的行上获取短期读锁定。随着应用程序滚动游标,会释放前面行上的短期读锁定,并在后面的行上获取新的短期读锁定。此技术称作游标稳定性。因为应用程序在当前行上保持读锁定,所以在应用程序离开该行之前,其它事务无法对该行进行更改。如果游标是通过涉及多个表的查询打开的,则可获取多个锁。仅当必须在请求(通常,这些请求是应用程序发出的
FETCH
语句)间保持游标位置时,才获取短期读锁定。例如,处理
SELECT COUNT(*)
查询时不会获取短期读锁定,因为通过此语句打开的游标将永远不会位于特定的基表行上。这种情况下,数据库服务器仅需要保证读取已提交的语义,即该语句处理的行已被其它事务提交。
以隔离级别
0
(读取未提交数据)执行的事务不会获取长期读锁定或短期读锁定,因此也不会与其它事务冲突(除了独占模式锁)。但是,隔离级别
0
事务可能处理其它并发事务所做的未提交的更改。可以使用快照隔离避免处理未提交的更改。
事务在插入、更新或删除行时将获取写锁定。此行为适用于处于所有隔离级别(包括隔离级别
0
和快照隔离级别)的事务。获取了写锁定后,任何其它事务都不能在相同行上获取读锁定、意图锁或写锁定。写锁定也称为独占锁,因为任何时候只能有一个事务可以在某一行上持有独占锁。只要一个事务在一行上持有某种类型的锁定,任何其它事务就不能在同一行上获取写锁定。同样,一个事务获取了写锁定后,其它事务要求锁定该行的请求都将被拒绝。
意图锁(也称为意图更新锁)表示修改特定行的意图。事务进行以下操作时,会获取意图锁:
发出
FETCH FOR UPDATE
语句
发出
SELECT...FOR UPDATE BY LOCK
语句
将
SQL_CONCUR_LOCK
用作
ODBC
应用程序中的并发基础(使用
SQLSetStmtAttr ODBC API
调用的
SQL_ATTR_CONCURRENCY
参数设置)
发出
SELECT...FROM T WITH (UPDLOCK)
语句
意图锁不会与读锁定冲突,因此获取意图锁不会阻塞其它事务读取同一行。但是,意图锁确实会防止其它事务在同一行上获取意图锁或写锁定,这样就保证了在更新前该行无法被任何其它事务所更改。
如果正在使用快照隔离的事务请求意图锁,则仅当该行在数据库中未修改且为所有并发事务公用时,才会获取意图锁。但是,如果该行是快照副本,则不会获取意图锁,因为原始行已被其它事务所修改。快照事务要更新该行的任何尝试都将失败,并会返回快照更新冲突错误。
表锁可在事务更新表时阻止其它事务更新表。
有三类表锁:共享表锁、意图写表锁和独占表锁。事务结束(发生
COMMIT
或
ROLLBACK
)后释放表锁。
表锁不同于模式锁:表锁在表的所有行上放置锁,而模式锁在表的模式上放置锁。
下表显示了哪两个表锁之间会发生冲突:
共享表锁允许多个事务读取基表的数据。拥有基表的共享表锁的事务可以修改此表,前提是其它事务均未持有对所修改行的任意类型的锁。
例如,通过执行
LOCK TABLE...IN SHARED MODE
语句可获取共享表锁。
REFRESH MATERIALIZED VIEW
和
REFRESH TEXT INDEX
语句还支持
WITH SHARE MODE
子句,使用该子句可以在刷新操作发生的同时在基础表上创建共享表锁。
意图写表锁
意图写表锁(也称作意图表锁)会在事务第一次获取行上的写锁定时隐式获取。即在更新、插入或删除行时获取意图表锁。与共享表锁一样,意图表锁会一直保持到通过
COMMIT
或
ROLLBACK
完成事务为止。意图表锁与共享表锁和独占表锁冲突,但不与其它意图表锁冲突。
独占表锁可阻止其它事务修改表的模式或数据,包括插入新数据。与独占模式锁不同,以隔离级别
0
执行的事务仍可读取具有独占表锁的表中的行。一次只能有一个事务在任意表上持有独占锁。独占表锁与所有其它表和行锁冲突。
使用
LOAD TABLE
语句时会隐式获取独占表锁。
可通过使用
LOCK TABLE...IN EXCLUSIVE MODE
语句获取独占表锁。
REFRESH MATERIALIZED VIEW
和
REFRESH TEXT INDEX
语句还提供了
WITH EXCLUSIVE MODE
子句,使用该子句可以在刷新操作发生的同时在基础表上放置独占表锁。
位置锁是一种键范围锁定方式,旨在防止由于幻像或幻像行的存在而导致的异常。
仅当数据库服务器处理以隔离级别
3
运行的事务时,才涉及到位置锁。
以隔离级别
3
运行的事务是可序列化的。隔离级别
3
的事务的行为不应受其它事务的并发更新活动影响。特别是,在隔离级别
3
上,事务不能受引入可影响计算结果的行的
INSERT
或
UPDATE
(幻像)所影响。数据库服务器使用位置锁防止此类更新发生。正是通过此附加锁定来区分隔离级别
2
(可重复读取)和隔离级别
3
。
为了防止创建幻像行,数据库服务器在表的物理扫描中获取位置锁。对于顺序扫描,扫描位置基于当前行的行标识符。对于索引扫描,扫描的位置基于当前行的索引键值(该值可以是唯一的也可以是不唯一的)。通过锁定扫描位置,一个事务可以防止其它事务按照行的顺序执行与特定值范围相关的插入。此行为适用于可更改索引属性值的
INSERT
语句和
UPDATE
语句。扫描位置锁定后,
UPDATE
语句可视为请求对索引条目执行
DELETE
,随后紧跟一个
INSERT
请求。
支持两类位置锁:幻象锁与插入锁。这两类锁都是共享锁,因为任意数量的事务可以在同一行上获取相同类型的锁。但是,幻像锁和防幻像锁互相冲突。
幻像锁(有时也称为防插入锁)放置在扫描位置上以防止其它事务随后创建幻像行。获取了幻像锁后,它可以防止其它事务在表中被放置了防插入锁的行与前一行之间插入新行。幻像锁是长期锁,它会一直保持到事务结束。
只有以隔离级别
3
运行的事务才能获取幻像锁;只有该隔离级别可保证幻像的一致性。
对于索引扫描,通过索引读取的每行上都会获取幻像锁,且会在索引扫描的末尾获取一个额外的幻像锁,以防止在满足条件的索引范围的末尾处向索引插入行。索引扫描上的幻像锁可防止向表中插入新行时创建幻像,也可防止会导致在幻像锁所在位置创建索引条目的索引值的更新。
对于顺序扫描,表中的每行上都会获取幻像锁,以防止任何插入操作变更结果集。隔离级别
3
扫描通常会对数据库并发产生负面影响。虽然一个或多个幻像锁会与插入锁冲突,而一个或多个读锁定会与写锁定冲突,但是幻像锁
/
插入锁和读锁定
/
写锁定之间不存在交互。例如,虽然不能在包含读锁定的行上获取写锁定,但可以在只具有幻像锁的行上获取写锁定。这种灵活的安排为数据库服务器提供了更多选择,但这也意味着数据库服务器必须经常注意在获取幻像锁的同时也应获取读锁定。否则,其它事务可以删除该行。
插入锁(有时也称作防幻像锁)是短期锁,放置在扫描位置上以保留插入行的权利。插入锁仅在插入期间保持;该行正确插入数据库页后,会立即在该行上放置写锁定以确保一致性,然后释放插入锁。一个事务在某一行上获取了插入锁后,其它事务都不能在同一行上获取幻像锁。插入锁是必需的,因为数据库服务器必须预料任何活动连接上的隔离级别
3
扫描操作,而任何新的请求都可能引发隔离级别
3
扫描。当幻像锁和插入锁由同一事务持有时,它们之间不会互相冲突。
Add custom field to F0718 - Post General Journal Entries Fiori App
in
Technology Q&A
Error Logs in SQL SAP Anywhere 17
in
Technology Q&A
SAC Forecast chart feature seems missing
in
Technology Q&A
Deploying App using ABAP CDS Annotations
in
Technology Q&A
Grounding is All You Need
in
Technology Blogs by Members