Sybase 数据库中自旋锁和CPU使用率
目的
这篇文章的目的是清楚的了解SAP ASE怎样使用自旋锁及对整体CPU使用率可能产生的影响。
简介
通常,SAP ASE中CPU高可以归因为自旋锁的使用。这篇文章将要展示怎样识别这些条件和推荐的ASE调优方法。
什么是自旋锁?
-
在一个多引擎的服务中需要使用同步机制保护共享资源。
ASE使用自旋锁作为它同步机制的一种
自旋锁是一种只能被原子性更新的数据结构(就是在同一个时刻只能有一个引擎修改它) -
当一个任务修改一个共享的数据项,它必须先持有自旋锁。
共享数据项是类似于运行队列、数据缓存页列表、锁结构等 -
自旋锁阻止同一时刻另外一个任务修改它的值。
这当然是假设另外一个任务也在自旋锁的保护机制下执行访问 -
一个需要获取自旋锁的任务在被授予之前将要“自旋”(阻塞)
当同一时刻多个引擎在自旋时CPU使用率会大大升高 -
自旋锁必须尽可能的快和高效
为了减小竞争一个进程通常在不断的循环请求和释放他的自旋锁
因此自旋锁使用平台相关的汇编语言编写
自旋锁和其他同步机制的比较
类型 | 复杂度 | CPU 开销 | 等待时间 |
---|---|---|---|
自旋锁 | 低 | 高 | 非常低 |
闩锁 | 中等 | 低 | 比较小 |
表、页、行地址锁 | 高 | 低 | 可能有很大差别 |
自旋锁和CPU 使用率
-
spid 在获取自旋锁之前不会让出引擎
所以一个正在等待自旋锁的spid,在获取这个自旋锁之前会引起该引擎用户态100% -
自旋锁竞争百分比是用等待/抢占来度量的
比如:10000抢占中3000次等待=30% 竞争 -
为了查找性能问题,使用总自旋数,不是竞争比
比如:两个自旋锁- 一个100次抢占中40次等待但是200次自旋 = 40% 竞争
- 第二个10000次抢占中400次等待但是20000次自旋 = 4%竞争
第二个虽然竞争度很低,但是使用了更多CPU周期自旋。
我们应该关注第二个例子的调优,而不是第一个。
解决自旋锁问题
- 自旋锁的竞争和自旋是CPU高的一个主要原因
- 第一步是判断实际上CPU高是不是由自旋锁使用引起的。
- 第二步是判断哪一个或哪一些自旋锁引起了这种情况。
- 第三步是判断使用哪种调优来减小这个问题。
注意 你永远也不可能得到0%自旋锁竞争除非在只有一个引擎的服务上运行。就是说,不要去想消除自旋锁竞争。它只能被尽可能的减小。
第一步 - 检查自旋锁竞争和自旋
- 使用sp_sysmon去判断是否CPU高是因为自旋锁
- 检查“CPU Busy”(或者“User Busy” 在15.7线程模式中)。
- 如果引擎没有显示出高busy%那么自旋锁不是一个大问题。
- 在“Data Cache Management”中检查"Total Cache Hits"
如果每秒缓存命中率高并且和cpu busy%一起上升,那么你应该查找全表扫描和查询计划问题而不是自旋锁。 - 通常,如果CPU使用率增加但是事务提交量、缓存命中数、锁请求数、扫描数等在下降,那么自旋锁使用可能是一个问题。
第二步 - 哪一个或哪一些自旋锁引起竞争?
- 使用sp_sysmon
- 这里列出了几种自旋锁,但是只展示了竞争百分比
- Object Manager Spinlock Contention
- Object Spinlock Contention
- Index Spinlock Contention
- Index Hash Spinlock Contention
- Partition Spinlock Contention
- Partition Hash Spinlock Contention
- Lock Hashtables Spinlock Contention
- Data Cache Spinlock Contention
-
这些中的任何一个竞争高可能暗示一个问题
但是,你可能有其他的自旋锁竞争并没有在sp_sysmon报告中显示 -
使用MDA表 monSpinlockActivity
这张表在15.7 ESD#2版本加入 -
使用标准SQL查询
一个根据一分钟间隔内的自旋次数展示前10条自旋锁的查询可能是这样的
select * into #t1 from monSpinlockActivity
waitfor delay "00:01:00"
select * into #t2 from monSpinlockActivity
select top 10 convert(char(30),a.SpinlockName) as SpinlockName,
(b.Grabs - a.Grabs) as Grabs,(b.Spins - a.Spins) as Spins,
case when a.Grabs = b.Grabs then 0.00 else convert(numeric(5,2),(100.0 * (b.Waits - a.Waits))/(b.Grabs-a.Grabs)) end as Contention
from #t1 a,#t2 b where a.SpinlockName = b.SpinlockName
order by 3 desc
使用monSpinlockActivity可能的问题
- 多个引擎的自旋锁将被聚集计算
例如,所有的数据缓存分区自旋锁将被显示成一行
这可能使仅一个缓存分区引起问题的情况不能被发现 - 必须设置'enable spinlock monitoring' 配置参数
测试显示对于繁忙的服务这又增加了大约1%的开销 - monSpinlockActivity 显示当前和最后一个拥有者的KPIDs。这可能是有用的对检查的进程是否是造成特定自旋锁。
第三步 - 做什么调优会对减小这个问题
- 这要依据哪些自旋锁是高度自旋的大量判断
- 注意这也有可能降低了一个自旋锁的竞争然后又增加了另一个
一些更通用的自旋锁和可能的解决办法
-
Object Manager Spinlock(Resource ->rdesmgr_spin)
- 确定配置了充足的'number of open objects'
- 用monOpenObjectActivity识别热点对象
- 使用 dbcc ture(des_bing)将热点对象绑定到DES缓存
- 修改起作用的原因是用来保护DES 不被使用的自旋锁知道确切数目。当DES被绑定整个进程将跳过。
-
Data Cache spinlocks
- 减少data cache spinlock 利用率最简单的方法是增加data cache 分区的数量。
- 注意如果data cache 被设置成了"relaxed LRU",自旋锁使用率可能会大幅减小。这是因为relaxed LRU缓存不用维护LRU->MRU链,所以不需要抢占自旋锁去移动页到MRU端。
- 有一些定义条件使用这个帮助(一个周转率非常高的缓存是不适合relaxed LRU的)
-
Procedure Cache Spinlock (Resource ->rproccache_spin)
- 从全局过程缓存内存池(包括语句缓存)中分配和释放页需要是用自旋锁。
- 一些可能的原因包括:
过程缓存太小, 存储过程和语句经常被移出和替换
存储过程重新编译
Large scale 分配 - 减小这个自旋锁的压力
- 消除存储过程重新编译的原因
- 如果运行一个比ASE 15.7ESD#4低的版本,需要升级。ASE 15.7 ESD#4和4.2有一些修改,减小了持有自旋锁的时间。
- trace flags 753和757可以减小 large-scale的分配
- 在ASE15.7 SP100之后的版本,使用配置参数"enable large chunk elc"
- 使用dbcc proc_cache(free_unused) 作为临时的减小自旋锁和CPU使用率的方法。
-
Procedure Cache Manager Spinlock( Resource ->rprocmgr_spin)
- 动态SQL和存储过程进入和移出过程缓存时使用自旋锁
- ASE15.7 ESD#1之前的版本当更新内存计数结构时使用自旋锁
原因是创建另外一个自旋锁的竞争
3. 高竞争比的原因包括:
大量使用动态SQL
过程缓存太小 - 可能的解决措施和rproccache_spin 一样
-
Lock Manager spinlocks(fglockspins,addrlockspins,tablockspins)
- 这些自旋锁用来保护 lock manager hashtables
- 如果lock HWMs 设置的太高,意为着更多的锁和竞争
- 达到目标的主要方式是调节配置
lock spinlock ratio
lock address spinlock ratio
lock table spinlock ratio
lock hashtable size
谨记
- 抵制因CPU高而添加更多引擎的诱惑
如果CPU高是因为自旋锁竞争引起的,添加更多的CPU只会使事情更糟。
添加更多的"spinner"只会简单的增加每个spid 获取自旋锁的时间,甚至更慢。