设为首页 收藏本站
查看: 907|回复: 0

[经验分享] MS SQL巡检系列——检查外键字段是否缺少索引

[复制链接]

尚未签到

发表于 2017-7-14 17:51:05 | 显示全部楼层 |阅读模式
  前言感想一时兴起,突然想写一个关于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;GODECLARE @Index INT=1;  BEGIN TRANWHILE @Index <= 3000BEGIN     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;GODECLARE @Index INT=1; BEGIN TRANWHILE @Index <=1000000BEGIN    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;GODBCC FREEPROCCACHE;GOSET STATISTICS IO ON;SET STATISTICS TIME ON; SELECT  p.PRODUCT_CD ,        p.PRODUCT_DATE ,        f.SALES_REGION ,        f.SALES_CITY ,        f.SALIES_SUMFROM    dbo.PRIMARY_TB p        INNER JOIN dbo.FK_TB f ON p.PRODUCT_CD = f.PRODUCT_CDWHERE p.PRODUCT_CD ='Prd131';SET STATISTICS IO OFF;SET STATISTICS TIME OFF;
  
  如下截图所示,如果外键字段缺少索引,这两个表关联查询时,子表就会走扫描(此处测试是聚集索引扫描),如果子表非常大(例如此处案例所示),IO开销就比较大。
   DSC0000.png
   DSC0001.png
    我们对外键约束字段PRODUCT_CD建立下面非聚集索引IDX_FK_TB,然后对比两者的执行计划和IO开销




CREATE INDEX IDX_FK_TB ON dbo.FK_TB(PRODUCT_CD); DBCC DROPCLEANBUFFERS;GODBCC 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_SUMFROM    dbo.PRIMARY_TB p        INNER JOIN dbo.FK_TB f ON p.PRODUCT_CD = f.PRODUCT_CDWHERE p.PRODUCT_CD ='Prd131' SET STATISTICS IO OFF;SET STATISTICS TIME OFF;
   DSC0002.png
   DSC0003.png
  
  你会发现执行计划从原来的聚集索引扫描(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;GODBCC FREEPROCCACHE;GO SET STATISTICS IO ON;SET STATISTICS TIME ON; DELETE FROM dbo.PRIMARY_TB WHERE PRODUCT_CD IN ('Prd132','Prd133')GOSET STATISTICS IO OFF;SET STATISTICS TIME OFF;
   DSC0004.png    DSC0005.png




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;
   DSC0006.png
   DSC0007.png
  
  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 currentworkloads 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://stackoverflow.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/
    

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-393916-1-1.html 上篇帖子: SQL VS NoSQL 如何选择数据库 下篇帖子: 【T-SQL性能优化】01.TempDB的使用和性能问题
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表