常用SQL Server Management Studio (ssms)调优

查询死锁语句

原理:将sp_who和sp_lock查询结果放入两个临时表方便查看

--开始
--创建两个临时表
CREATE Table #Who(
    spid int,
    ecid int,
    status nvarchar(50),
    loginname nvarchar(50),
    hostname nvarchar(50),
    blk int,
    dbname nvarchar(50),
    cmd nvarchar(50),
    request_ID int);
CREATE Table #Lock(
    spid int,
    dbid int,
    objid int,
    indld int,
    [Type] nvarchar(20),
    Resource nvarchar(50),
    Mode nvarchar(10),
    Status nvarchar(10)
);
 
delete #Who;delete #Lock;
INSERT INTO #Who exec sp_who;--查看进程对应的的用户
INSERT INTO #Lock exec sp_lock;--查看当前阻塞的数据表的进程
 
--查看哪台客户端造成锁,记下spid
SELECT #Who.spid , --进程id,
       hostname ,--计算机名,
       [type],
       #Who.cmd ,
       mode ,
       #Who.blk,--阻塞了进程的spid
       objid,-- 锁定的对象id
       Object_name(objid) objName,-- 锁定的对象名
       #Who.status
FROM   #Lock,
       #Who
WHERE  #Who.spid = #Lock.spid
       AND objid <> 0
       AND dbname = 'QPIS'; -- 按需更改数据库名
 
--可以忽略下方查找死锁原因的语句,直接跳到杀进程
 
exec sp_who2;--查看链接数据库的有什么程序
 
dbcc inputbuffer(97);--查看该spid的进程正在执行的语句
 
select * from Sys.dm_exec_connections a where a.session_id=97;--session_id=spid,查看该进程的主机的ip
 
select * from Sys.dm_exec_Sessions a where a.session_id=97;--session_id=spid,查看该进程的主机什么程序在使用数据库
 
kill 97; --杀进程
drop table #Who;drop table #Lock;

模糊查询函数和存储过程的代码

select *
from sysobjects o, syscomments s
where o.id = s.id
and s.text like '%代码内容%'

查看语句执行性能耗时(使用活动和监视器也可)

/*SELECT (q.total_elapsed_time / q.execution_count) / 1000 平均时间ms,
       q.total_elapsed_time / 1000 总花费时间ms,
       q.total_worker_time / 1000 所用的CPU总时间ms,
       q.total_physical_reads 物理读取总次数,
       q.total_logical_reads / execution_count 每次逻辑读次数,
       q.total_logical_reads 逻辑读取总次数,
       q.total_logical_writes 逻辑写入总次数,
       q.execution_count 执行次数,
       q.creation_time 语句编译时间,
       q.last_execution_time 上次执行时间,
       s.text
FROM sys.dm_exec_query_stats q
    CROSS APPLY sys.dm_exec_sql_text(q.sql_handle) s 
    --使用不了cross apply语法需要兼容性级别COMPATIBILITY_LEVEL为90或以上
    --右键数据库属性->选项->兼容性级别,调整到sqlserver 2005(90)以上
ORDER BY q.total_elapsed_time / q.execution_count DESC;
*/
弃用CROSS APPLY,非广泛使用的SQL语法
 
SELECT (q.total_elapsed_time / q.execution_count) / 1000 平均时间ms,
       q.total_elapsed_time / 1000 总花费时间ms,
       q.total_worker_time / 1000 所用的CPU总时间ms,
       q.total_physical_reads 物理读取总次数,
       q.total_logical_reads / execution_count 每次逻辑读次数,
       q.total_logical_reads 逻辑读取总次数,
       q.total_logical_writes 逻辑写入总次数,
       q.execution_count 执行次数,
       q.creation_time 语句编译时间,
       q.last_execution_time 上次执行时间,
       (SELECT text FROM  sys.dm_exec_sql_text(q.sql_handle)) SQL_TEXT
FROM sys.dm_exec_query_stats q
WHERE q.last_execution_time>'2022-05-26 07:00:00.000'--上次执行时间
ORDER BY q.total_elapsed_time / q.execution_count DESC;

纵向列出表字段

SELECT
    b.name AS tablename,
    a.name AS columnname,
    c.name AS typename,
    a.max_length AS typelength
FROM
    sys.columns a
    INNER JOIN sys.tables b ON b.object_id = a.object_id
    INNER JOIN sys.types c ON c.system_type_id= a.system_type_id
WHERE
    b.name= '' ;--表名,纵向列出字段

横向列出表字段

SELECT c.columnname + ','
FROM   (SELECT a.name AS columnname
        FROM   sys.columns a
               INNER JOIN sys.tables b
                       ON b.object_id = a.object_id
        WHERE  b.name = '' --表名,横向列出字段
       ) c
FOR XML PATH ( '' );

索引碎片查询和重建重组(ssms右键查看表属性也可以)

--查询数据库下所有索引碎片率
--登录的用户要有master权限,在对应数据库下操作,$DatabaseName换成对应数据库名
--行索引,所有sql版本通用
SELECT CASE
         WHEN t.[type] = 'U' THEN '表'
         WHEN t.[type] = 'V' THEN '视图'
       END                                               AS '类型',
       Schema_name(t.schema_id) + '.' + t.[name]         AS '(表/视图)名称',
       i.[name]                                          AS '索引名称',
       d.column_names AS '列名',
       CASE i.[type]
         WHEN 0 THEN '堆索引'
         WHEN 1 THEN '聚集索引'
         WHEN 2 THEN '非聚集索引'
         WHEN 3 THEN 'XML索引'
         WHEN 4 THEN '空间索引'
         WHEN 5 THEN '聚簇列存储索引'
         WHEN 6 THEN '非聚集列存储索引'
         WHEN 7 THEN '非聚集哈希索引'
       END                                               AS '索引类型',
       CASE
         WHEN i.is_unique = 1 THEN '唯一'
         ELSE '不唯一'
       END                                               AS '索引是否唯一',
       case s.alloc_unit_type_desc when 'IN_ROW_DATA' THEN '索引' when 'LOB_DATA' then '大数据字段索引' else s.alloc_unit_type_desc end ,
       s.avg_fragmentation_in_percent '碎片率',
       s.fragment_count,
       s.avg_fragment_size_in_pages '一个碎片的平均页数',/*根据函数最后一个参数而变化,参考:https://docs.microsoft.com/zh-cn/sql/relational-databases/system-dynamic-management-views/sys-dm-db-index-physical-stats-transact-sql?view=sql-server-ver15#evaluating-index-fragments*/
       s.page_count
FROM   $DatabaseName.sys.objects t
       INNER JOIN $DatabaseName.sys.indexes i
               ON t.object_id = i.object_id
       INNER JOIN sys.dm_db_index_physical_stats(Db_id('$DatabaseName'), NULL, NULL, NULL,NULL) s--最后一个参数NULL代表LIMITED,改成DETAILED可以更详细但查询很慢
               ON t.object_id = s.object_id
                  AND i.index_id = s.index_id
       CROSS APPLY (SELECT col.[name] + ', '
                    FROM   #DatabaseName.sys.index_columns ic
                           INNER JOIN #DatabaseName.sys.columns col
                                   ON ic.object_id = col.object_id
                                      AND ic.column_id = col.column_id
                    WHERE  ic.object_id = t.object_id
                           AND ic.index_id = i.index_id
                    ORDER  BY col.column_id
                    FOR XML PATH('')) d(column_names)
WHERE  t.is_ms_shipped <> 1--排除系统索引
       AND i.index_id > 0 --排除堆索引(表没有聚合索引就会产生堆索引)
ORDER  BY t.schema_id;
 
--列索引,sql2016以上可用
select * from sys.dm_db_column_store_row_group_physical_stats
 
--可选
--数据库用户权限下
declare @table_id int;
set @table_id=object_id('用户名.dbo.表名');--表名
dbcc showcontig(@table_id);

avg_fragmentation_in_percent范围:
大于5% 且 小于 30%,需要重组,
ALTER INDEX #IndexName ON #TableName REORGANIZE;
30%,需要重建,
ALTER INDEX #IndexName ON #TableName REBUILD;

简单总结:
1、重建重组消耗大量资源,数据库空闲时候操作。
2、碎片多或者页面密度低的时候重建重组才有效果。
3、自 SQL Server 2016 (13.x) 起,通常不需要重新生成列存储索引

自动重组重建索引存储过程

--作用:索引页数大于20的情况下,碎片率大于30的索引重建,碎片率大于5小于30的索引重组
--$DatabaseName替换为需要的数据库名
CREATE PROCEDURE [dbo].[index_rebuild_reorganize] AS
SET NOCOUNT ON;
DECLARE
@s_tableName VARCHAR(64),
@s_indexName VARCHAR(128),
@f_fragmentationInPercent FLOAT,
@s_execSql NVARCHAR(200),
@s_action VARCHAR(10);
DECLARE c_indexes CURSOR FOR
SELECT SCHEMA_NAME(t.schema_id)+'.'+t.name tableName, i.name indexName,
    s.avg_fragmentation_in_percent fragmentationInPercent
FROM $DataBaseName.sys.objects t
     INNER JOIN $DatabaseName.sys.indexes i ON t.object_id=i.object_id
     INNER JOIN sys.dm_db_index_physical_stats(DB_ID('$DatabaseName'), NULL, NULL, NULL, NULL) s ON t.object_id=s.object_id
                                                                AND i.index_id=s.index_id
WHERE t.is_ms_shipped<>1 --排除系统索引
      AND i.index_id>0 --排除堆索引(表没有聚合索引就会产生堆索引)
      AND i.type IN (1, 2) --聚合索引和非聚合索引
      AND t.type='U' --U=表 V=视图
      AND s.avg_fragmentation_in_percent>5 --碎片率大于5%
      AND s.page_count>20 --索引页数大于20
     -- AND t.name='XXXXX'
ORDER BY s.avg_fragmentation_in_percent DESC;
OPEN c_indexes;
FETCH NEXT FROM c_indexes
INTO @s_tableName,@s_indexName,@f_fragmentationInPercent;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @s_execSql= 'ALTER INDEX '+@s_indexName +' ON '+ @s_tableName+' ' +@s_action;
    IF @f_fragmentationInPercent >= 30
        SET @s_action='REBUILD';
    ELSE
        SET @s_action='REORGANIZE';
    EXEC (@s_execSql)
    FETCH NEXT FROM c_indexes
    INTO @s_tableName,@s_indexName,@f_fragmentationInPercent;
END;
CLOSE c_indexes;
DEALLOCATE c_indexes;