死锁杂谈
当数据库死锁时,SqlServer会释放一个优先级较低的锁,让另一个事务运行;所以,即时去捕捉数据库死锁,是挺不容易的。
如果,数据库死锁比较长时间,那么死锁是可以被捕捉的。
可以用SqlServer活动监视器来查看,哪些进程锁了数据库。
首先打开SqlServer活动监视器,然后可以看到,界面里有进程,查看资源,数据文件I/O,最近消耗大量资源的查询四项。
四项显示内容如下:
进程:在进程里可以看到哪些进程被阻塞,查看属性【阻塞者】可以看到,【阻塞者】的会话ID。
等待资源:等待资源里有一些锁,可以看看那些锁累计等待时间较多。
数据文件I/O:数据文件I/O记录一些数据库MDF,LDF的读写速度。
最近消耗大量资源的查询:记录一些消耗资源较大的SQL查询。
查询进程里被死锁的会话ID,然后执行下面的SQL,进行解锁。
- declare @spid int Set @spid = 518 --锁表进程会话ID
- declare @sql varchar(1000)
- set @sql='kill '+cast(@spid as varchar)
- exec(@sql)
也可以用下面SQL语句查询死锁进程,这样查询死锁进程,定位比较快。
- select request_session_id spid, OBJECT_NAME(resource_associated_entity_id) tableName
- from sys.dm_tran_locks where resource_type='OBJECT'
优化杂谈
最近消耗大量资源的查询也可以用SQL查询。
下面SQL是查询最耗时的前10条SQL语句。
- SELECT TOP 10 total_worker_time / 1000 AS [自编译以来执行所用的CPU时间总量(ms-毫秒)],
- total_elapsed_time/1000 as [完成执行此计划所用的总时间],
- total_elapsed_time / execution_count/1000 as [平均完成执行此计划所用时间],
- execution_count as [上次编译以来所执行的次数],
- creation_time as [编译计划的时间],
- deqs.total_worker_time / deqs.execution_count / 1000 AS [平均使用CPU时间(ms)],
- last_execution_time AS [上次开始执行计划的时间],
- total_physical_reads [编译后在执行期间所执行的物理读取总次数],
- total_logical_reads/execution_count [平均逻辑读次数],
- min_worker_time /1000 AS [单次执行期间所用的最小CPU时间(ms)],
- max_worker_time / 1000 AS [单次执行期间所用的最大 CPU 时间(ms)],
- SUBSTRING(dest.text, deqs.statement_start_offset / 2 + 1,
- (CASE WHEN deqs.statement_end_offset = -1 THEN DATALENGTH(dest.text) ELSE deqs.statement_end_offset END - deqs.statement_start_offset) / 2 + 1) AS [执行SQL],
- dest.text as [完整SQL],
- db_name(dest.dbid) as [数据库名称],
- object_name(dest.objectid, dest.dbid) as [对象名称]
- ,deqs.plan_handle [查询所属的已编译计划]
- FROM sys.dm_exec_query_stats deqs WITH(NOLOCK)
- CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest --平均使用CPU时间降序
- ORDER BY (deqs.total_worker_time / deqs.execution_count / 1000) DESC
在SqlServer活动监视器里,查看资源等待。
通常可以看到等待类别是Latch的排在最上面,如下图:

Latch 【闩锁】虽然是一种轻量级的锁,但等待的锁越多,肯定越影响数据库性能。
执行下面SQL,查看下哪些Latch比较耗资源。
- SELECT * FROM sys.dm_os_latch_stats
查询结果如下图所示:

从结果中可以看到各种锁类型的请求的次数,等待时间,最大等待时间(毫秒)。
但这些锁类型都是英文简写,需要使用下面表格查询它们的真实意义。
通过对比表格,我们发现了最消耗资源的ACCESS_METHODS_DATASET_PARENT锁的意义是并发操作时资源访问的锁。那么想降低并发操作,就可以减少ACCESS_METHODS_DATASET_PARENT锁的资源消耗了。
Latch参考网址:https://docs.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-os-latch-stats-transact-sql?view=sql-server-2017
DBCC杂谈
DBCC 语句是SQL Server 的数据库控制台命令,共有以下四种类型。
维护:对数据库、索引或文件组进行维护的任务。
杂项:杂项任务,如启用跟踪标志或从内存中删除 DLL。
信息:收集并显示各种类型信息的任务。
验证:对数据库、表、索引、目录、文件组或数据库页的分配进行的验证操作。
DBCC shrinkdatabase
DBCC shrinkdatabase用于收缩数据库,SQL语句如下:
- DBCC shrinkdatabase (N'库名' , 1)
执行结果如下:

各字段含义如下:
DbId:数据库引擎试图收缩的文件的数据库标识号。
FileId:数据库引擎尝试收缩的文件的文件标识号。
CurrentSize:文件当前占用的 8 KB 页数。
MinimumSize:文件最低可以占用的 8 KB 页数。 这与文件的最小大小或最初创建时的大小相对应。
UsedPages:文件当前使用的 8 KB 页数。
EstimatedPages:数据库引擎估计文件能够收缩到的 8 KB 页数。
如果收缩不成功,可以查看下数据库是否有可以收缩的空间。
SQL如下:
- SELECT name ,size/128.0 - CAST(FILEPROPERTY(name, 'SpaceUsed') AS int)/128.0 AS AvailableSpaceInMB
- FROM sys.database_files;
如果有空间还收缩不成功,则可能是别原因。
DBCC参考网址:https://docs.microsoft.com/zh-cn/sql/t-sql/database-console-commands/dbcc-shrinkdatabase-transact-sql?view=sql-server-2017
数据库日志杂谈
SqlServer数据库日志对执行的SQL语句进行了加密,所以,在日志里,我们看不到真正执行的SQL语句。
如果想查看SQL语句,需要借助一些工具,如ApexSQLLog。
不过,虽然看不到SQL语句,也可以通过日志看出一些数据库问题,比如,可以查看数据库执行了多少次插入,更新等操作。
查看数据库日志的SQL如下:
- SELECT * FROM [sys].[fn_dblog](NULL,NULL)
查询结果如下:

查询结果各字段含义如下:
Operation
|
Context
|
解释
|
LOP_SET_BITS
|
LCX_DIFF_MAP |
设置位图,资料: 差异(Differential)备份:只备份上次完整备份后,做修改的部分。备份单位是区(Extent)。意味着某个区内即使只有一页做了变动,则在差异备份里会被体现.差异备份依靠一个BitMap进行维护,一个Bit对应一个区,自上次完整备份后,被修改的区会被置为1,而BitMap中被置为1对应的区会被差异备份所备份。而到下一次完整备份后,BitMap中所有的Bit都会被重置为0
而这个BitMap在数据库第7页:
DCM页 差异变更(Differential Changed Map,DCM)页面他跟踪一个文件中的哪一个区在最新一次完整数据库备份之后被修改过。SQLSERVER用在增量备份时只对已发生数据变更的分区进行增量备份即可
|
LOP_BEGIN_XACT |
|
事务开始 |
LOP_MODIFY_ROW |
LCX_HEAP |
修改堆表中的某一行记录 |
LOP_PREP_XACT |
|
准备启动数据库 |
LOP_COMMIT_XACT |
|
提交事务 |
LOP_MODIFY_ROW |
LCX_BOOT_PAGE |
修改数据库启动页 |
LOP_MODIFY_HEADER |
LCX_PFS |
修改PFS页的页头部信息 |
LOP_INSERT_ROWS |
LCX_CLUSTERED |
插入数据到聚集索引的索引页 |
LOP_INSERT_ROWS |
LCX_INDEX_LEAF |
插入数据到索引的叶子节点即数据页 |
LOP_FORMAT_PAGE |
LCX_CLUSTERED |
重新组织聚集索引 |
LOP_DELETE_SPLIT |
LCX_CLUSTERED |
删除聚集索引表的一行记录引起页拆分 |
LOP_MODIFY_HEADER |
LCX_HEAP |
修改堆表的某页的页头信息 |
LOP_BEGIN_CKPT |
LCX_NULL |
检查点开始 |
LOP_END_CKPT |
LCX_NULL |
检查点结束 |
LOP_SET_FREE_SPACE |
LCX_PFS |
修改PFS页设置那个数据页是空闲的 |
LOP_ROOT_CHANGE |
LCX_CLUSTERED |
聚集索引的根节点改变 |
LOP_INSERT_ROWS |
LCX_HEAP |
插入数据到堆表 |
LOP_FORMAT_PAGE |
LCX_HEAP |
格式化堆里的数据页 |
LOP_LOCK_XACT |
|
在事务里获取锁 |
LOP_FORMAT_PAGE |
LCX_HEAP |
格式化堆里的数据页 |
----------------------------------------------------------------------------------------------------
注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错,请点击下右下角的【推荐】,非常感谢!