在上一篇文章里我讨论了SQL Server里的闩锁。在文章的最后我给你简单介绍了下自旋锁(Spinlock)。基于那个基础,今天我会继续讨论SQL Server中的自旋锁,还有给你展示下如何对它们进行故障排除。

为什么我们需要自旋锁?

在上篇文章我已经指出,用闩锁同步多个线程间数据结构访问,在每个共享数据结构前都放置一个闩锁没有意义的。闩锁与此紧密关联:当你不能获得闩锁(因为其他人已经有一个不兼容的闩锁拿到),查询就会强制等待,并进入挂起(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:自旋锁名称
  • collisions:当尝试访问保护的数据结构时,被自旋锁阻塞的线程次数
  • 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里的自旋锁。在第1部分我们讨论了为什么SQL Server需要实现自旋锁。如你所见,使用自旋锁保护自并发线程对“忙碌”共享数据结构的访问更“便宜”——例如锁管理器。在第2部分我们详细讨论了对SQL Server的自旋锁竞争你如何进行故障排除,还有使用标识的调用堆栈如何找出问题的根源。

感谢关注!

参考文章:

https://www.sqlpassion.at/archive/2014/06/30/introduction-to-spinlocks-in-sql-server-2/

SQL Server里的自旋锁介绍的更多相关文章

  1. SQL Server里的闩锁介绍

    在今天的文章里我想谈下SQL Server使用的更高级的,轻量级的同步对象:闩锁(Latch).闩锁是SQL Server存储引擎使用轻量级同步对象,用来保护多线程访问内存内结构.文章的第1部分我会介 ...

  2. SQL Server里的闩锁耦合(Latch Coupling)

    几年前,我写了篇关于闩锁和为什么SQL Server需要它们的文章.在今天的文章里,我想进一步谈下非缓存区闩锁(Non-Buffer Latches),还有在索引查找操作期间,SQL Server如何 ...

  3. 在SQL Server里为什么我们需要更新锁

    今天我想讲解一个特别的问题,在我每次讲解SQL Server里的锁和阻塞(Locking & Blocking)都会碰到的问题:在SQL Server里,为什么我们需要更新锁?在我们讲解具体需 ...

  4. SQL Server里等待统计(Wait Statistics)介绍

    在今天的文章里我想详细谈下SQL Server里的统计等待(Wait Statistics),还有她们如何帮助你立即为什么你的SQL Server当前很慢.一提到性能调优,对我来说统计等待是SQL S ...

  5. SQL Server里的文件和文件组

    在今天的文章里,我想谈下SQL Server里非常重要的话题:SQL Server如何处理文件的文件组.当你用CREATE DATABASE命令创建一个简单的数据库时,SQL Server为你创建2个 ...

  6. 在SQL Server里我们为什么需要意向锁(Intent Locks)?

    在1年前,我写了篇在SQL Server里为什么我们需要更新锁.今天我想继续这个讨论,谈下SQL Server里的意向锁,还有为什么需要它们. SQL Server里的锁层级 当我讨论SQL Serv ...

  7. SQL Server里的INTERSECT

    在今天的文章里,我想讨论下SQL Server里的INTERSECT设置操作.INTERSECT设置操作彼此交叉2个记录集,返回2个集里列值一样的记录.下图演示了这个概念. INTERSECT与INN ...

  8. SQL Server里Grouping Sets的威力

    在SQL Server里,你有没有想进行跨越多个列/纬度的聚集操作,不使用SSAS许可(SQL Server分析服务).我不是说在生产里使用开发版,也不是说安装盗版SQL Server. 不可能的任务 ...

  9. SQL Server里如何随机记录集

    今天的文章,我想给你简单介绍下SQL Server里如何随机记录集. SELECT * FROM Person.Person ORDER BY NEWID() GO 这会引入新的UNIQUEIDENT ...

随机推荐

  1. linux服务器调整参数支持高并发

    服务端调整系统的参数,在/etc/sysctl.conf中: ◦net.core.somaxconn = 2048◦net.core.rmem_default = 262144◦net.core.wm ...

  2. Undokumentierte @Formeln/LotusScript im Lotus Notes Client/Server

    Erstellung von Replik-IDs mittels @Text-Funktion@Text( @Now; "*" )Source NoPersist option ...

  3. [C#] zdbviewcs: 跨平台数据库查看器。支持SqlServer、Oracle、MySql等数据库

    作者:zyl910 一.说明 本工具有适合以下情况使用—— * 快速查看数据库中数据及表结构信息.* 测试ADO.Net下连接字符串的写法.* 帮忙分析ADO.Net数据库操作. 二.用法 运行本程序 ...

  4. arcgis server10.1注册服务——避免在发布服务中拷贝数据

    之前用的arcgis10.1前的版本,后来换成10.1还有点不习惯,变化挺多的.发布服务过程中,进行分析的时候,其中有一项提醒:xxx图层没有注册到服务,很纳闷,为什么会有这种提示,不管,点击发布,会 ...

  5. [转] Autofac创建实例的方法总结

    1.InstancePerDependency 对每一个依赖或每一次调用创建一个新的唯一的实例.这也是默认的创建实例的方式. 官方文档解释:Configure the component so tha ...

  6. Asp.net Core WebApi 使用Swagger做帮助文档,并且自定义Swagger的UI

    WebApi写好之后,在线帮助文档以及能够在线调试的工具是专业化的表现,而Swagger毫无疑问是做Docs的最佳工具,自动生成每个Controller的接口说明,自动将参数解析成json,并且能够在 ...

  7. Windows调试学习笔记:(一)WinDBG中加载SOS和CLR

    最近产品环境出现了部分服务器当机情况,虽然重启之后问题就不出现了.但本着彻底解决问题的态度,想要找到root cause.多次尝试Visual Studio失败(可能是代码惊醒了优化和签名)之后,决定 ...

  8. schedule() 和 scheduleAtFixedRate() 区别

    1.  schedule() ,2个参数方法:在执行任务时,如果指定的计划执行时间scheduledExecutionTime <= systemCurrentTime,则task会被立即执行. ...

  9. AppStore 相关

    App 跳转 AppStore 网址链接   https://itunes.apple.com/app/uri/id582319843?mt=8   https 可替换成 itms,可直接避免进入 S ...

  10. 在SpringMVC中获取request对象的几种方式

    1.最简单的方式(注解法) @Autowired private HttpServletRequest request; 2.最麻烦的方法 a. 在web.xml中配置一个监听 <listene ...