1.
数据库表锁定原理
1.1 目前的C/S,B/S结构都是多用户访问数据库,每个时间点会有成千上万个user来访问DB,其中也会同时存取同一份数据,会造成数据的不一致性或者读脏数据。
1.2 事务的ACID原则
1.3 锁是关系数据库很重要的一部分, 数据库必须有锁的机制来确保数据的完整和一致性。
1.3.1 SQL Server中可以锁定的资源:
1.3.2 锁的粒度:
1.3.3 锁的升级:锁的升级门限以及锁升级是由系统自动来确定的,不需要用户设置。
1.3.4 锁的类型
(1) 共享锁:共享锁用于所有的只读数据操作。
(2) 修改锁:修改锁在修改操作的初始化阶段用来锁定可能要被修改的资源,这样可以避免使用共享锁造成的死锁现象。
(3) 独占锁:独占锁是为修改数据而保留的。它所锁定的资源,其他事务不能读取也不能修改。独占锁不能和其他锁兼容。
(4) 架构锁:结构锁分为结构修改锁(Sch-M)和结构稳定锁(Sch-S)。执行表定义语言操作时,SQL
Server采用Sch-M锁,编译查询时,SQL Server采用Sch-S锁。
(5) 意向锁:意向锁说明SQL Server有在资源的低层获得共享锁或独占锁的意向。
(6) 批量修改锁:批量复制数据时使用批量修改锁。
1.3.4 SQL Server锁类型
(1) HOLDLOCK: 在该表上保持共享锁,直到整个事务结束,而不是在语句执行完立即释放所添加的锁。
(2) NOLOCK:不添加共享锁和排它锁,当这个选项生效后,可能读到未提交读的数据或“脏数据”,这个选项仅仅应用于SELECT语句。
(3) PAGLOCK:指定添加页锁(否则通常可能添加表锁)。
(4) READCOMMITTED用与运行在提交读隔离级别的事务相同的锁语义执行扫描。默认情况下,SQL
Server 2000 在此隔离级别上操作。
(5) READPAST: 跳过已经加锁的数据行,这个选项将使事务读取数据时跳过那些已经被其他事务锁定的数据行,而不是阻塞直到其他事务释放锁,READPAST仅仅应用于READ
COMMITTED隔离性级别下事务操作中的SELECT语句操作。
(6) READUNCOMMITTED:等同于NOLOCK。
(7) REPEATABLEREAD:设置事务为可重复读隔离性级别。
(8) ROWLOCK:使用行级锁,而不使用粒度更粗的页级锁和表级锁。
(9) SERIALIZABLE:用与运行在可串行读隔离级别的事务相同的锁语义执行扫描。等同于 HOLDLOCK。
(10) TABLOCK:指定使用表级锁,而不是使用行级或页面级的锁,SQL Server在该语句执行完后释放这个锁,而如果同时指定了HOLDLOCK,该锁一直保持到这个事务结束。
(11) TABLOCKX:指定在表上使用排它锁,这个锁可以阻止其他事务读或更新这个表的数据,直到这个语句或整个事务结束。
(12) UPDLOCK :指定在 读表中数据时设置更新 锁(update lock)而不是设置共享锁,该锁一直保持到这个语句或整个事务结束,使用UPDLOCK的作用是允许用户先读取数据(而且不阻塞其他用户读数据),并且保证在后来再更新数据时,这一段时间内这些数据没有被其他用户修改。
2. 如何解除表的锁定,解锁就是要终止锁定的那个链接,或者等待该链接事务释放。
2.1 Activity Monitor
可以通过Wait Type,Blocked By栏位查看到,SPID 54 被SPID 53 阻塞。
可以右键Details查到详细的SQL 语句,或Kill掉这个进程。
2.2 SQL Server提供几个DMV,查看locks:
sys.dm_exec_requests
sys.dm_tran_locks
sys.dm_os_waiting_tasks
sys.dm_tran_database_transactions
(1)select
*
from
sys.dm_tran_locks where
resource_type<>'DATABASE'
--and
resource_database_id=DB_ID()
(2)
SELECT
session_id, blocking_session_id,*
FROM
sys.dm_exec_requests
WHERE
blocking_session_id >
0
(3)
SELECT
request_session_id as
Spid, Coalesce(s.name
+
'.'
+
o.name +
isnull('.'
+
i.name,''),
s2.name +
'.'
+
o2.name,
db.name) AS
Object,
l.resource_type as
Type,
request_mode as
Mode,
request_status as
Status
FROM
sys.dm_tran_locks l
LEFT
JOIN
sys.partitions p
ON
l.resource_associated_entity_id =
p.hobt_id
LEFT
JOIN
sys.indexes i
ON
p.object_id
=
i.object_id
AND
p.index_id =
i.index_id
LEFT
JOIN
sys.objects o
ON
p.object_id
=
o.object_id
LEFT
JOIN
sys.schemas s
ON
o.schema_id =
s.schema_id
LEFT
JOIN
sys.objects o2
ON
l.resource_associated_entity_id =
o2.object_id
LEFT
JOIN
sys.schemas s2
ON
o2.schema_id =
s2.schema_id
LEFT
JOIN
sys.databases db
ON
l.resource_database_id =
db.database_id
WHERE
resource_database_id =
DB_ID()
ORDER
BY
Spid, Object, CASE
l.resource_type
When
'database'
Then
1
when
'object'
then
2
when
'page'
then
3
when
'key'
then
4
Else
5
end
有几个朋友留言建议结合例子来演示一下,上篇已经说过锁的几种类型,可以利用系统动态视图sys.dm_tran_locks查看到,重要的栏位如下:
resource_type:被锁的资源类型(Database, FILE, Object,PAGE,KEY,EXTENT,RID,APPLICATION,METADATA,HOBT,APPOCATION_UNIT)。
request_mode:锁的类型(共享锁,更新锁,排它锁,架构锁等)。
resource_description:资源描述。
request_session_id:Request session ID。
一、下面以AdventureWorks2008为示例数据库做简要的说明,过滤掉一般的数据库的共享锁,作为示例必须要看到锁,所以用WITH(HOLDLOCK)来保持锁。
1. Shared locks (S) 共享锁
USE
AdventureWorks2008
BEGIN
TRAN
select
*
from
Sales.SalesOrderHeader WITH(HOLDLOCK)
where
SalesOrderID='43662'
SELECT
resource_type, request_mode, resource_description,request_session_id,
DB_NAME(resource_database_id)as
resource_database
FROM
sys.dm_tran_locks
WHERE
resource_type <>
'DATABASE'
在事务回滚之前, 查看锁的类型:
其他session对Table只读, 不能更新, 在开一个新的session测试:
select
*
from
Sales.SalesOrderHeader where
SalesOrderID='43662'
go
update
Sales.SalesOrderHeader set
OrderDate=GETDATE()
where
SalesOrderID='43662'
select可以正常执行,update语句一直处于等待状态,等待上面的session释放锁。
2. Update locks (U):更新锁是共享锁和独占锁的组合.用UPDLOCK保持更新锁。
USE
AdventureWorks2008
BEGIN
TRAN
select
*
from
Sales.SalesOrderHeader WITH(UPDLOCK)
where
SalesOrderID='43662'
SELECT
resource_type, request_mode, resource_description,request_session_id,DB_NAME(resource_database_id)as
resource_database
FROM
sys.dm_tran_locks
WHERE
resource_type <>
'DATABASE'
ROLLBACK
TRAN
查看到锁的信息:
3.Exclusive locks (X):独占锁是为了锁定数据被一个session修改的数据, 而不能够被另外的session修改。
只能指定NOLOCK来读取。
USE
AdventureWorks2008
BEGIN
TRAN
update
Sales.SalesOrderHeader set
ShipDate=GETDATE()
where
SalesOrderID='43662'
SELECT
resource_type, request_mode, resource_description,request_session_id,DB_NAME(resource_database_id)as
resource_database--,*
FROM
sys.dm_tran_locks
WHERE
resource_type <>
'DATABASE'
ROLLBACK
TRAN
查看锁:
4.Intent locks (I):意向锁用于建立锁的层次结构,意向锁包含三种类型:意向共享 (IS)、意向排他
(IX) 和意向排他共享 (SIX)。
数据库引擎使用意向锁来保护共享锁(S 锁)或排他锁(X 锁)放置在锁层次结构的底层资源上。 意向锁之所以命名为意向锁,是因为在较低级别锁前可获取它们,因此会通知意向将锁放置在较低级别上。
意向锁有两种用途:
- 防止其他事务以会使较低级别的锁无效的方式修改较高级别资源。
- 提高数据库引擎在较高的粒度级别检测锁冲突的效率。
5. Schema locks (Sch): 架构锁
- Schema stability lock(Sch-S):保持架构稳定性,用在生成执行计划时,不会阻止对数据的访问。
- Schema modification lock (Sch-M):用在DDL操作时.当架构正在被改变时,
阻止对对象数据的访问。
USE
AdventureWorks2008
BEGIN
TRAN
CREATE
TABLE
MyTable (ID INT,
NAME VARCHAR(20),COUNTRY
VARCHAR(15))
SELECT
resource_type, request_mode, resource_description
FROM
sys.dm_tran_locks
WHERE
resource_type <>
'DATABASE'
order
by
request_mode
ROLLBACK
TRAN
6. Bulk Update locks (BU)
数据库引擎在将数据大容量复制到表中时使用了大容量更新 (BU) 锁, 并指定了 TABLOCK
提示或使用 sp_tableoption 设置了 table
lock on bulk load 表选项。大容量更新锁(BU 锁)允许多个线程将数据并发地大容量加载到同一表,同时防止其他不进行大容量加载数据的进程访问该表。
7. Key - Range locks
在使用可序列化事务隔离级别时,对于 Transact-SQL 语句读取的记录集,键范围锁可以隐式保护该记录集中包含的行范围。
键范围锁可防止幻读, 通过保护行之间键的范围, 它还防止对事务访问的记录集进行幻像插入或删除。
二、死锁与死锁解除
1. 死锁
使用或管理数据库都不可避免的涉及到死锁。一旦发生死锁,数据相互等待对方资源的释放,会阻止对数据的访问,
严重会造成DB挂掉。 当资源被锁定,无法被访问时, 可以终止访问DB的那个session来达到解锁的目的(即
Kill掉造成锁的那个进程)。
在两个或多个任务中,如果每个任务锁定了其他任务试图锁定的资源,此时会造成这些任务永久阻塞,从而出现死锁。
例如:
- 事务 A 获取了行 1 的共享锁。
- 事务 B 获取了行 2 的共享锁。
- 现在,事务 A 请求行 2 的排他锁,但在事务 B 完成并释放其对行 2 持有的共享锁之前被阻塞。
- 现在,事务 B 请求行 1 的排他锁,但在事务 A 完成并释放其对行 1 持有的共享锁之前被阻塞。
事务 B 完成之后事务 A 才能完成,但是事务 B 由事务 A 阻塞。该条件也称为循环依赖关系: 事务
A 依赖于事务 B,事务 B 通过对事务 A 的依赖关系关闭循环。
除非某个外部进程断开死锁,否则死锁中的两个事务都将无限期等待下去。 Microsoft SQL Server
数据库引擎死锁监视器定期检查陷入死锁的任务。 如果监视器检测到循环依赖关系,将选择其中一个任务作为牺牲品,然后终止其事务并提示错误。
这样,其他任务就可以完成其事务。 对于事务以错误终止的应用程序,它还可以重试该事务,但通常要等到与它一起陷入死锁的其他事务完成后执行。
2. 死锁检测
2.1 SQL Server 数据库引擎自动检测 SQL Server 中的死锁循环。数据库引擎选择一个会话作为死锁牺牲品,然后终止当前事务(出现错误)来打断死锁。
2.2 查看DMV: sys.dm_tran_locks
2.3 SQL Server Profiler能够直观的显示死锁的图形事件.
三、锁兼容性
锁兼容性控制多个事务能否同时获取同一资源上的锁。 如果资源已被另一事务锁定,则仅当请求锁的模式与现有锁的模式相兼容时,才会授予新的锁请求。
如果请求锁的模式与现有锁的模式不兼容,则请求新锁的事务将等待释放现有锁或等待锁超时间隔过期。 例如,没有与排他锁兼容的锁模式。
如果具有排他锁(X 锁),则在释放排他锁(X 锁)之前,其他事务均无法获取该资源的任何类型(共享、更新或排他)的锁。
另一种情况是,如果共享锁(S 锁)已应用到资源,则即使第一个事务尚未完成,其他事务也可以获取该项的共享锁或更新锁(U
锁)。 但是,在释放共享锁之前,其他事务无法获取排他锁。
四、总结
锁的原理比较抽象,对用户来说是透明的,不用过多的关注,应用程序一般不直接请求锁。 锁由数据库引擎的一个部件(称为“锁管理器”)在内部管理。当数据库引擎实例处理Transact-SQL
语句时, 数据库引擎查询处理器会决定将要访问哪些资源。查询处理器根据访问类型和事务隔离级别设置来确定保护每一资源所需的锁的类型。
然后, 查询处理器将向锁管理器请求适当的锁。如果与其他事务所持有的锁不会发生冲突, 锁管理器将授予该锁。 |