MS SQL巡检系列——检查外键字段是否缺少索引

时间:2021-11-18 18:46:33

前言感想:一时兴起,突然想写一个关于MS SQL的巡检系列方面的文章,因为我觉得这方面的知识分享是有价值,也是非常有意义的。一方面,很多经验不足的人,对于巡检有点茫然,不知道要从哪些方面巡检,另外一方面,网上关于MS SQL巡检方面的资料好像也不是特别多。写这个系列只是一个分享,自己的初衷是一个知识梳理、总结提炼过程,有些知识和脚本也不是原创,文章很多地方融入了自己的一些想法和见解的,不足和肤浅之处肯定也非常多,抛砖引玉,也希望大家提意见和建议、补充,指正其中的不足之处。Stay Hungry Stay Foolish!

MS SQL巡检系列——检查重复索引

MS SQL巡检系列——检查外键字段是否缺少索引

MS SQL巡检系列——检查数据库上一次DBCC CHECKDB的时间

对于绝大部分情况,外键字段都有必要建立对应的索引(注意,外键约束并不会自动建立索引),关于外键字段为什么要建立索引?下面从几个简单的例子来分析一下。我们先准备测试环境数据。

CREATE TABLE PRIMARY_TB

(

    PRODUCT_CD        VARCHAR(12)      ,

    PRODUCT_DATE    DATE             ,

    PRODUCT_DESC    VARCHAR(120)     ,

    CONSTRAINT PK_PRIMARY_TB  PRIMARY KEY CLUSTERED (PRODUCT_CD)

);

 

 

SET NOCOUNT ON;

GO

DECLARE @Index INT=1;

 

 

BEGIN TRAN

WHILE @Index <= 3000

BEGIN

 

    INSERT INTO dbo.PRIMARY_TB

    SELECT 'Prd' + CAST(@Index AS VARCHAR(4)), GETDATE() - CEILING(RAND()*200), 'production description' + CAST(@Index AS VARCHAR(4));

 

    SET @Index +=1;

END

 

COMMIT;

 

 

 

CREATE TABLE FK_TB

(

    FK_ID            INT IDENTITY(1,1),

    SALES_REGION    VARCHAR(32),

    SALES_CITY        VARCHAR(32),

    PRODUCT_CD        VARCHAR(12),

    SALIES_SUM        INT,

    CONSTRAINT PK_FK_TB PRIMARY KEY CLUSTERED (FK_ID)

)

GO

 

ALTER TABLE [dbo].[FK_TB]  WITH CHECK ADD  CONSTRAINT [FK_PRIMARY_TB_PRODUCT_CD] FOREIGN KEY([PRODUCT_CD])

REFERENCES [dbo].[PRIMARY_TB] ([PRODUCT_CD]) ON  DELETE CASCADE;

GO

 

 

SET NOCOUNT ON;

GO

DECLARE @Index INT=1;

 

BEGIN TRAN

WHILE @Index <=1000000

BEGIN

    INSERT INTO FK_TB

    SELECT 'REGION'+CAST(CEILING(RAND()*20) AS VARCHAR(2)), CAST(CEILING(RAND()*300) AS VARCHAR(3)),'Prd'+ CAST(CEILING(RAND()*3000) AS VARCHAR(8)),CEILING(RAND()*100000);

 

    SET @Index +=1;

 

END

 

COMMIT;

 

 

UPDATE STATISTICS dbo.PRIMARY_TB WITH FULLSCAN;

UPDATE STATISTICS dbo.FK_TB WITH FULLSCAN;

GO

1: 外键字段建立索引,在主表与子表JOIN操作时能提高性能,减少IO操作。

DBCC DROPCLEANBUFFERS;

GO

DBCC FREEPROCCACHE;

GO

SET STATISTICS IO ON;

SET STATISTICS TIME ON;

 

SELECT  p.PRODUCT_CD ,

        p.PRODUCT_DATE ,

        f.SALES_REGION ,

        f.SALES_CITY ,

        f.SALIES_SUM

FROM    dbo.PRIMARY_TB p

        INNER JOIN dbo.FK_TB f ON p.PRODUCT_CD = f.PRODUCT_CD

WHERE p.PRODUCT_CD ='Prd131';

SET STATISTICS IO OFF;

SET STATISTICS TIME OFF;

如下截图所示,如果外键字段缺少索引,这两个表关联查询时,子表就会走扫描(此处测试是聚集索引扫描),如果子表非常大(例如此处案例所示),IO开销就比较大。

MS SQL巡检系列——检查外键字段是否缺少索引

MS SQL巡检系列——检查外键字段是否缺少索引

我们对外键约束字段PRODUCT_CD建立下面非聚集索引IDX_FK_TB,然后对比两者的执行计划和IO开销

CREATE INDEX IDX_FK_TB ON dbo.FK_TB(PRODUCT_CD);

 

DBCC DROPCLEANBUFFERS;

GO

DBCC FREEPROCCACHE;

GO

 

SET STATISTICS IO ON;

SET STATISTICS TIME ON;

SELECT  p.PRODUCT_CD ,

        p.PRODUCT_DATE ,

        f.SALES_REGION ,

        f.SALES_CITY ,

        f.SALIES_SUM

FROM    dbo.PRIMARY_TB p

        INNER JOIN dbo.FK_TB f ON p.PRODUCT_CD = f.PRODUCT_CD

WHERE p.PRODUCT_CD ='Prd131'

 

SET STATISTICS IO OFF;

SET STATISTICS TIME OFF;

MS SQL巡检系列——检查外键字段是否缺少索引

MS SQL巡检系列——检查外键字段是否缺少索引

你会发现执行计划从原来的聚集索引扫描(Clustered Index Scan)变成了索引查找(Index Seek),IO的减少也是非常明显的。因为这里仅仅是测试数据,复杂的生产环境,性能的提升有可能比这更加明显。

2:如果外键约束为CASCADE(on update/delete)时,则当修改被引用行(referenced row)时,所有引用行(referencing rows )也必须修改(更新或级联删除)。外键列上的索引能减小锁的粒度和范围,从而提高效率和性能。如下所示:

 

我们先看看缺少索引的情况。

DROP INDEX IDX_FK_TB ON dbo.FK_TB;

 

 

 

DBCC DROPCLEANBUFFERS;

GO

DBCC FREEPROCCACHE;

GO

 

SET STATISTICS IO ON;

SET STATISTICS TIME ON;

 

DELETE FROM dbo.PRIMARY_TB WHERE PRODUCT_CD IN ('Prd132','Prd133')

GO

SET STATISTICS IO OFF;

SET STATISTICS TIME OFF;

MS SQL巡检系列——检查外键字段是否缺少索引

MS SQL巡检系列——检查外键字段是否缺少索引

CREATE INDEX IDX_FK_TB ON dbo.FK_TB(PRODUCT_CD);

 

GO

 

DBCC DROPCLEANBUFFERS;

 

GO

 

DBCC FREEPROCCACHE;

 

GO

 

SET STATISTICS IO ON;

 

SET STATISTICS TIME ON;

 

DELETE FROM dbo.PRIMARY_TB WHERE PRODUCT_CD IN ('Prd134','Prd135')

 

GO

 

SET STATISTICS IO OFF;

 

SET STATISTICS TIME OFF;

MS SQL巡检系列——检查外键字段是否缺少索引

MS SQL巡检系列——检查外键字段是否缺少索引

3:如果外键关系为NO ACTION(on update/delete)时,那么被引用的行不能被删除,因为这个操作会导致引用行变成“孤立”。删除之前,数据库会为了有效地查找、定位行,外键列上创建索引也非常有帮助。

 

上面肤浅的构造了简单案例,并对比外键约束字段拥有索引和缺少索引时,SQL的执行计划和IO的差异,那么接下来,我们进入正题,巡检的时候,我们必须通过脚本找到数据库里面外键字段没有索引的相关表和信息,并生成对应的创建索引的脚本。如下所示。

/*

One or more tables found, with foreign key constraint defined but no supporting indexes created on the foreign key columns.


SQL Server doesnt put an index on foreign key columns by default and indexing foreign key fields in referencing tables is not required. 


Foreign key columns usage must evaluated to determine whether or not indexing this column will help up increase the current

workloads performance by enhancing join performance, reducing table locking (for full table scans) while cascading updates and 

deletes, etc.


*/

 

;

WITH    FKTable

          AS ( SELECT   SCHEMA_NAME(po.schema_id) AS 'parent_schema_name' ,

                        OBJECT_NAME(fkc.parent_object_id) AS 'parent_table_name' ,

                        OBJECT_NAME(constraint_object_id) AS 'constraint_name' ,

                        SCHEMA_NAME(ro.schema_id) AS 'referenced_schema' ,

                        OBJECT_NAME(referenced_object_id) AS 'referenced_table_name' ,

                        ( SELECT    '[' + COL_NAME(k.parent_object_id,

                                                   parent_column_id) + ']' AS [data()]

                          FROM      sys.foreign_key_columns (NOLOCK) AS k

                                    INNER JOIN sys.foreign_keys  (NOLOCK) ON k.constraint_object_id = object_id

                                                              AND k.constraint_object_id = fkc.constraint_object_id

                          ORDER BY  constraint_column_id

                        FOR

                          XML PATH('')

                        ) AS 'parent_colums' ,

                        ( SELECT    '[' + COL_NAME(k.referenced_object_id,

                                                   referenced_column_id) + ']' AS [data()]

                          FROM      sys.foreign_key_columns (NOLOCK) AS k

                                    INNER JOIN sys.foreign_keys  (NOLOCK) ON k.constraint_object_id = object_id

                                                              AND k.constraint_object_id = fkc.constraint_object_id

                          ORDER BY  constraint_column_id

                        FOR

                          XML PATH('')

                        ) AS 'referenced_columns'

               FROM     sys.foreign_key_columns fkc ( NOLOCK )

                        INNER JOIN sys.objects po ( NOLOCK ) ON fkc.parent_object_id = po.object_id

                        INNER JOIN sys.objects ro ( NOLOCK ) ON fkc.referenced_object_id = ro.object_id

               WHERE    po.type = 'U'

                        AND ro.type = 'U'

               GROUP BY po.schema_id ,

                        ro.schema_id ,

                        fkc.parent_object_id ,

                        constraint_object_id ,

                        referenced_object_id

             ),

 

        /* Index Columns */

        IndexColumnsTable

          AS ( SELECT   SCHEMA_NAME(o.schema_id) AS 'schema_name' ,

                        OBJECT_NAME(o.object_id) AS TableName ,

                        ( SELECT    CASE key_ordinal

                                      WHEN 0 THEN NULL

                                      ELSE '[' + COL_NAME(k.object_id,

                                                          column_id) + ']'

                                    END AS [data()]

                          FROM      sys.index_columns (NOLOCK) AS k

                          WHERE     k.object_id = i.object_id

                                    AND k.index_id = i.index_id

                          ORDER BY  key_ordinal ,

                                    column_id

                        FOR

                          XML PATH('')

                        ) AS cols

               FROM     sys.indexes (NOLOCK) AS i

                        INNER JOIN sys.objects o ( NOLOCK ) ON i.object_id = o.object_id

                        INNER JOIN sys.index_columns ic ( NOLOCK ) ON ic.object_id = i.object_id

                                                              AND ic.index_id = i.index_id

                        INNER JOIN sys.columns c ( NOLOCK ) ON c.object_id = ic.object_id

                                                              AND c.column_id = ic.column_id

               WHERE    o.type = 'U'

                        AND i.index_id > 0

               GROUP BY o.schema_id ,

                        o.object_id ,

                        i.object_id ,

                        i.name ,

                        i.index_id ,

                        i.type

             ),

        FKWithoutIndexTable

          AS ( SELECT   fk.parent_schema_name AS SchemaName ,

                        fk.parent_table_name AS TableName ,

                        fk.referenced_schema AS ReferencedSchemaName ,

                        fk.referenced_table_name AS ReferencedTableName ,

                        fk.constraint_name AS ConstraintName ,

                        fk.referenced_columns AS Referenced_Columns ,

                        fk.parent_colums AS Parent_Columns

               FROM     FKTable fk

               WHERE    NOT EXISTS ( SELECT 1

                                     FROM   IndexColumnsTable ict

                                     WHERE  fk.parent_schema_name = ict.schema_name

                                            AND fk.parent_table_name = ict.TableName

                                            AND fk.parent_colums = LEFT(ict.cols,

                                                              LEN(fk.parent_colums)) )

             )

    SELECT  @@SERVERNAME AS InstanceName ,

            DB_NAME() AS DatabaseName ,

            SchemaName ,

            TableName ,

            Parent_Columns ,

            ReferencedSchemaName ,

            ReferencedTableName ,

            Referenced_Columns ,

            ConstraintName

    INTO    #ForeignKeyWithOutIndex

    FROM    FKWithoutIndexTable

    ORDER BY DatabaseName ,

            SchemaName ,

            TableName;

 

 

 

 

--输出临时表数据

SELECT  *

FROM    #ForeignKeyWithOutIndex;

 

 

--生成外键字段缺少的索引,请抽查、检验,确认后批量执行

SELECT  'CREATE INDEX IX_' + LTRIM(RTRIM(TableName)) + '_'

        + SUBSTRING(Parent_Columns, 2, LEN(Parent_Columns) - 2) + '  ON '

        + LTRIM(RTRIM(SchemaName)) + '.' + LTRIM(RTRIM(TableName)) + '('

        + Parent_Columns + ');'

FROM    #ForeignKeyWithOutIndex;

 

 

--删除临时表

DROP TABLE #ForeignKeyWithOutIndex;

 

在创建这些索引前最好检查、确认一下,外键字段创建索引能提高性能,但是肯定也要特殊的场景和上下文不适合,所以最好根据实际情况决定。索引创建之后,通过监控工具监控一下数据库性能、等待事件的变化。

参考资料:

http://*.com/questions/3650690/should-every-sql-server-foreign-key-have-a-matching-index

http://sqlblog.com/blogs/greg_low/archive/2008/07/29/indexing-foreign-keys-should-sql-server-do-that-automatically.aspx

http://www.sqlskills.com/blogs/kimberly/when-did-sql-server-stop-putting-indexes-on-foreign-key-columns/

MS SQL巡检系列——检查外键字段是否缺少索引的更多相关文章

  1. MS SQL巡检系列&mdash&semi;&mdash&semi;检查重复索引

    前言感想:一时兴起,突然想写一个关于MS SQL的巡检系列方面的文章,因为我觉得这方面的知识分享是有价值,也是非常有意义的.一方面,很多经验不足的人,对于巡检有点茫然,不知道要从哪些方面巡检,另外一方 ...

  2. MS SQL巡检系列&mdash&semi;&mdash&semi;检查数据库上一次DBCC CHECKDB的时间

    DBCC CHECKDB检查指定数据库中的所有对象的逻辑和物理完整性,具体请参考MSDN文档.我们必须定期对数据库做完整性检查(DBCC CHECKDB),以便能及时发现一些数据库损坏(Corrupt ...

  3. 基于MVC4&plus;EasyUI的Web开发框架经验总结(9)--在Datagrid里面实现外键字段的转义操作

    我们在使用EasyUI的时候,很多情况下需要使用到表格控件datagrid,这个控件控件非常强大,使用起来很简洁,但是我在使用中,发现对于一个表里面的外键字段进行转义,并显示引用表的一些名称的操作,却 ...

  4. DRF 外键字段深度查询优化、ListSerializer辅助完成群改

    目录 一.Response封装 二.外键字段深度查询 1.序列化配置exclude.depth 2.模型层函数.插拔式字段查询 三.listserializer辅助类 一.Response封装 用de ...

  5. day08 外键字段的增删查改

    day08 外键字段的增删查改 今日内容概要 外键字段的增删查改 正反向查询的概念 基于对象的跨表查询(子查询) 基于双下划线的跨表查询(连表操作) 聚合查询与分组查询 F查询和Q查询 前提准备 cl ...

  6. django模型中&comma; 外键字段使用to&lowbar;filed属性 指定到所关联主表的某个字段

    在django项目的开发过程中,在设计模型时一开始将主键设置成了一个自定义的字段,但是在创建搜索索引时却发现必须要存在一个id的字段,并且为主键(不知道是否是项目一开始就这样配置的原因), 但此时表结 ...

  7. &lpar;转)基于MVC4&plus;EasyUI的Web开发框架经验总结(9)--在Datagrid里面实现外键字段的转义操作

    http://www.cnblogs.com/wuhuacong/p/3872890.html 我们在使用EasyUI的时候,很多情况下需要使用到表格控件datagrid,这个控件控件非常强大,使用起 ...

  8. MySQL系列&lpar;十一&rpar;--外键约束foreign key的基本使用

    有些时候,为了保证数据的完整性,我们会选择的使用外键约束,例如教师对应的表和课程表中老师的id,这种时候就要使用外键约束了. PS:这里不考虑表结构设计,三范式与反范式等设计问题,基于MySQL8.0 ...

  9. 01&period;drf文档及外键字段反序列化

    一 安装drf 1.1 安装库 pip install djangorestframework pip install markdown # Markdown support for the brow ...

随机推荐

  1. 如何查看 Linux是32位还是64位?

    方法一:执行命令 file /sbin/init [root@localhost jianbao]# file /sbin/init /sbin/init: ELF 32-bit LSB shared ...

  2. 关于华擎X99&plus;5820K

    受到之前Intel  I7-5775C的困扰,于是直接整套平台换掉. 把Z97+I7-5775C+DDR3换成了X99+5820K+DDR4. 但是依然不理想,又是另外一个坑. 组装好后安装系统的过程 ...

  3. C&num; 通过SerialPort简单调用串口

    问题 最近比较经常使用串口进行发送以及传输数据,但是笔者在刚开始接触SerialPort类时,对于Write之后去Read数据的时候,由于设备上面还没有返回数据,读取到的只能是空值.然而,再进行下一次 ...

  4. Python3基础 用三个双引号 print输出多行文本

    镇场诗:---大梦谁觉,水月中建博客.百千磨难,才知世事无常.---今持佛语,技术无量愿学.愿尽所学,铸一良心博客.------------------------------------------ ...

  5. Mac OS X 10&period;9 编译C&plus;&plus;11

    Notice: How to compile C++ with C++ 11 support in Mac Terminal *上面的问题 其实mac里面的不是g++而是cla ...

  6. 关于django Models的个人理解和related&lowbar;name的使用

    作为一个新人(刚刚大学还没有毕业就出来实习,可以说是真的什么都不知到,什么都要重新学,但是这样真的可以锻炼自己的意志力和能力).现在在公 司是前端和后端一起坐,所以要学的东西是真的多的让人想不到.在学 ...

  7. mongodb基本概念解析

    MongoDB 概念解析 不管我们学习什么数据库都应该学习其中的基础概念,在mongodb中基本的概念是文档.集合.数据库,下面我们挨个介绍. 下表将帮助您更容易理解Mongo中的一些概念: SQL术 ...

  8. 7&period;31&period;1 java内存的主要划分

    java内存的主要划分:class文件加载到方法区,方法区还存放静态变量和常量,方法区开始执行程序,当调用方法时,会将该方法的栈帧压到栈区,该栈帧中存放局部变量,当方法中new出一个对象,则会在堆中开 ...

  9. MYSQL之视图、触发器、存储过程、函数、事物、数据库锁和数据库备份

    一.视图 -- view 视图:是一个虚报表,其内容由查询定义.同真实的表一样,视图包含一系列带有名称的列和行数据. 视图有如下特点: 1.视图的列可以来自不同的表,是表的抽象和逻辑意义上建立的新关系 ...

  10. MySQL主从复制作用和原理

    一.什么是主从复制?主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库:主数据库一般是准实时的业务数据库. 二.主从复制的作用1.做数据的热备,作为后备数据库,主数据库服务器故障后, ...