承接国内外服务器租用托管、定制开发、网站代运营、网站seo优化托管接单、网站代更新,新老站点皆可!!咨询QQ:3787320601

简单介绍SQL Server中的自旋锁

管理员 2023-06-21 08:51:58 互联网圈 13 ℃ 0 评论 7284字 收藏

为何我们需要自旋锁?
用闩锁同步多个线程间数据结构访问,在每一个同享数据结构前都放置一个闩锁没成心义的。闩锁与此紧密关联:当你不能取得闩锁(由于其他人已有一个不兼容的闩锁拿到),查询就会强迫等待,并进入挂起(SUSPENDED)状态。查询在挂起状态等待直到可以拿到闩锁,然后就会进入可履行(RUNNABLE)状态。对查询履行只要没有可用的CPU,查询就一直在可履行(RUNNABLE)状态。一旦CPU有空闲,查询会进入运行(RUNNING)状态,最后成功获得到闩锁,用它来保护访问的同享数据结构。下图展现了SQLOS对调和线程调度实现的状态机。

由于太多关联的闩锁,对“繁忙”数据结构使用闩锁保护没成心义。因此SQL Server实现所谓自旋锁(Spinlocks)。自旋锁就像一个闩锁,存储引擎使用的一个轻量级同步对象,用来同步对同享数据结构线程访问。和闩锁的主要区分是你积极等待自旋锁——不离开CPU。在自旋锁上的“等待”总会产生在运行(RUNNING)状态的CPU。在你闭合循环里旋转直到取得自旋锁。这就是所谓的繁忙等待(busy wait)。自旋锁的最大优点是当查询在自旋锁上等待时,不会触及到上下文切换。另外一方面繁忙等待浪费CPU周期,其他查询或许能对它们更有效的使用。

为了不太多的CPU周期浪费,SQL Server 2008 R2及后续版本实现所谓的指数补偿机制(exponential backoff mechanism),那里在CPU上一些时间的休眠后,线程停止旋转。在线程进入休眠期间,增加了尝试取得自旋锁的超时。这个行动可以下降对CPU性能的影响。

(补充说明:Spinlock中文可以称为自旋锁。它是一个轻量级的,用户态的同步对象,和critical section类似,但是粒度比前者小多了。它主要用来保护某些特定的内存对象的多线程并发访问。Spinlock是排他性的。一次只能一个线程具有。

Spinlock的设计目标是非常快和高效力。Spinlock内部如何工作呢?它首先试图取得某个对象的锁,如果目标被其它线程占有,就在那里轮询(spin)一定时间。如果还得不到锁,就sleep一小会,然后继续spin。反复这个进程直到得到对象的占有权。)

自旋锁与故障排除
对自旋锁故障排除的主要DMV是 sys.dm_os_spinlock_stats。这个DMV里返回的每行都代表SQL Server里的一个自旋锁。SQL Server 2014实现了262个区别自旋锁。我们来详细看下这个DMV里的各个列:

name:自旋锁名称
collision:当尝试访问保护的数据结构时,被自旋锁阻塞的线程次数
spins:在循环里尝试取得自旋锁的自旋锁线程次数
spins_per_collision:旋转和碰撞之间的比率
sleep_time:由于退避线程休眠时间
backoffs:为了其他线程在CPU上继续,线程退避次数
在这个DMV里最重要的列是backoffs,对特定的自旋锁类型,这列告知你退避产生频率。高频率的退避会屈服于CPU消耗引发SQL Server里的自旋锁竞争(Spinlock Contention)。我就见过一个32核的SQL Server服务器,CPU运行在100%而不进行任何工作——典型的自旋锁竞争症状。

对自旋锁问题进行故障排除你可使用扩大事件提供的sqlos.spinlock_backoff。当退避(backoff)产生时,就会触发这个扩大事件。如果你捕获了这个事件,你还要保证你使用非常好的选择性谓语,由于在SQL Server里退避会常常产生。一个好的谓语可以是特定的自旋锁类型,通过刚才提到的DMV你已看到。以下代码给你展现了怎样创建这样的扩大事件会话。

— Retrieve the type value for the LOCK_HASH spinlock.
— That value is used by the next XEvent session
SELECT * FROM sys.dm_xe_map_values
WHERE name = ‘spinlock_types’
AND map_value = ‘LOCK_HASH’
GO

— Tracks the spinlock_backoff event
CREATE EVENT SESSION SpinlockContention ON SERVER
ADD EVENT sqlos.spinlock_backoff
(
ACTION
(
package0.callstack
)
WHERE
(
[type] = 129 — <<< Value from the previous query
)
)
ADD TARGET package0.histogram
(
SET source = ‘package0.callstack’, source_type = 1
)
GO

从代码里可以看到,这里我在调用堆栈(callstack)上使用了直方图(histogram)目标来bucktize。因此对特定的自旋锁,你可以可能到SQL Serve里生成的最高退避(backoffs)代码路径。你乃至可以通过启用3656跟踪标记(trace flag)来标识调用堆栈。这里你可以看到来自这个扩大会话的输出:

sqldk.dll!XeSosPkg::spinlock_backoff::Publish+0x138
sqldk.dll!SpinlockBase::Sleep+0xc5
sqlmin.dll!Spinlock<129,7,1>::SpinToAcquireWithExponentialBackoff+0x169
sqlmin.dll!lck_lockInternal+0x841
sqlmin.dll!XactWorkspaceImp::GetSharedDBLockFromLockManager+0x18d
sqlmin.dll!XactWorkspaceImp::GetDBLockLocal+0x15b
sqlmin.dll!XactWorkspaceImp::GetDBLock+0x5a
sqlmin.dll!lockdb+0x4a sqlmin.dll!DBMgr::OpenDB+0x1ec
sqlmin.dll!sqlusedb+0xeb
sqllang.dll!usedb+0xb3
sqllang.dll!LoginUseDbHelper::UseByMDDatabaseId+0x93
sqllang.dll!LoginUseDbHelper::FDetermineSessionDb+0x3e1
sqllang.dll!FRedoLoginImpl+0xa1b
sqllang.dll!FRedoLogin+0x1c1
sqllang.dll!process_request+0x3ec
sqllang.dll!process_commands+0x4a3
sqldk.dll!SOS_Task::Param::Execute+0x21e
sqldk.dll!SOS_Scheduler::RunTask+0xa8
sqldk.dll!SOS_Scheduler::ProcessTasks+0x279
sqldk.dll!SchedulerManager::WorkerEntryPoint+0x24c
sqldk.dll!SystemThread::RunWorker+0x8f
sqldk.dll!SystemThreadDispatcher::ProcessWorker+0x3ab
sqldk.dll!SchedulerManager::ThreadEntryPoint+0x226

使用提供调用堆栈,不难找出自旋锁竞争产生的地方。在那个指定的笤俑堆栈里竞争产生在LOCK_HASH自旋锁类型里,它是保护锁管理器的哈希表。每次在锁管理器里加锁或解锁被履行时,自旋锁一定要在对应的哈希桶里取得。如你所见,在调用堆栈里,当从XactWorkspacelmp类调用GetSharedDBLockFromLockManager函数时,自旋锁被取得。这表示当竞争到数据库时,同享数据库锁被尝试获得。最后在用很高的退避(backoffs)的LOCK_HASH自旋锁里,这屈服于自旋锁竞争。

本篇文章到此结束,如果您有相关技术方面疑问可以联系我们技术人员远程解决,感谢大家支持本站!

文章来源:丸子建站

文章标题:简单介绍SQL Server中的自旋锁

https://www.wanzijz.com/view/57637.html

X

截屏,微信识别二维码

微信号:weimawl

(点击微信号复制,添加好友)

打开微信