sql >> データベース >  >> RDS >> Database

MSSQLまたは最適化ですぐに不足しているインデックス

    クエリを実行するとき、SQL Serverオプティマイザーは、もちろん、このプランがサーバーキャッシュにまだ格納されていない場合、既存のインデックスと利用可能な最新の統計に基づいて最適なクエリプランを妥当な時間で見つけようとします。いいえの場合、クエリはこのプランに従って実行され、プランはサーバーキャッシュに保存されます。このクエリのプランがすでに作成されている場合、クエリは既存のプランに従って実行されます。

    次の問題に関心があります:

    クエリプランのコンパイル中に、可能なインデックスを並べ替えるときに、サーバーが最適なインデックスを見つけられない場合、不足しているインデックスはクエリプランにマークされ、サーバーはそのようなインデックスの統計を保持します:サーバーがこのインデックスを使用する回数このクエリにかかる費用。

    この記事では、これらの欠落しているインデックスを分析します–それらを処理する方法。

    特定の例でこれを考えてみましょう。ローカルサーバーとテストサーバーのデータベースにいくつかのテーブルを作成します。

    [expand title =” Code”]

    if object_id ('orders_detail') is not null drop table orders_detail;
    
    if object_id('orders') is not null drop table orders;
    
    go
    
    create table orders
    
    (
    
    id int identity primary key,
    
    dt datetime,
    
    seller nvarchar(50)
    
    )
    
    create table orders_detail
    
    (
    
    id int identity primary key,
    
    order_id int foreign key references orders(id),
    
    product nvarchar(30),
    
    qty int,
    
    price money,
    
    cost as qty * price
    
    )
    
    go
    
    with cte as
    
    (
    
    select 1 id union all
    
    select id+1 from cte where id < 20000
    
    )
    
    insert orders
    
    select
    
    dt,
    
    seller
    
    from
    
    (
    
    select
    
    dateadd(day,abs(convert(int,convert(binary(4),newid()))%365),'2016-01-01') dt,
    
    abs(convert(int,convert(binary(4),newid()))%5)+1 seller_id
    
    from cte
    
    ) c
    
    left join
    
    (
    
    values
    (1,'John'),
    
    (2,'Mike'),
    
    (3,'Ann'),
    
    (4,'Alice'),
    
    (5,'George')
    ) t (id,seller) on t.id = c.seller_id
    
    option(maxrecursion 0)
    
     
    
    insert orders_detail
    
    select
    
    order_id,
    
    product,
    
    qty,
    
    price
    
    from
    
    (
    
    select
    
    o.id as order_id,
    
    abs(convert(int,convert(binary(4),newid()))%5)+1 product_id,
    
    abs(convert(int,convert(binary(4),newid()))%20)+1 qty
    
    from orders o cross join
    
    (
    
    select top(abs(convert(int,convert(binary(4),newid()))%5)+1) *
    
    from
    
    (
    
    values (1),(2),(3),(4),(5),(6),(7),(8)
    
    ) n(num)
    
    ) n
    
    ) c
    
    left join
    
    (
    
    values
    (1,'Sugar', 50),
    
    (2,'Milk', 80),
    
    (3,'Bread', 20),
    
    (4,'Pasta', 40),
    
    (5,'Beer', 100)
    
    ) t (id,product, price) on t.id = c.product_id
    
    go

    [/エキスパンド]

    構造は単純で、2つのテーブルで構成されています。最初のテーブルは、識別子、販売日、販売者などのフィールドを持つ注文と呼ばれます。 2つ目は注文の詳細で、一部の商品は価格と数量で指定されます。

    簡単なクエリとその計画を見てください:

    select count(*) from orders o join orders_detail d on o.id = d.order_id
    
    where d.cost > 1800
    
    go

    クエリプランのグラフィック表示に、欠落しているインデックスに関する緑色のヒントが表示されます。それを右クリックして「MissingIndexDetails..」を選択すると、提案されたインデックスのテキストが表示されます。唯一行うことは、テキスト内のコメントを削除し、インデックスに名前を付けることです。スクリプトを実行する準備ができました。

    SSMSによって提供されたヒントから受け取ったインデックスは作成しません。代わりに、このインデックスが、欠落しているインデックスにリンクされた動的ビューによって推奨されるかどうかを確認します。ビューは次のとおりです。

    select * from sys.dm_db_missing_index_group_stats
    
    select * from sys.dm_db_missing_index_details
    
    select * from sys.dm_db_missing_index_groups

    ご覧のとおり、最初のビューには欠落しているインデックスに関する統計がいくつかあります。

    1. 提案されたインデックスが存在する場合、検索は何回実行されますか?
    2. 提案されたインデックスが存在する場合、スキャンは何回実行されますか?
    3. インデックスを使用した最新の日時
    4. 提案されたインデックスを含まないクエリプランの現在の実際のコスト。

    2番目のビューはインデックス本体です:

    1. データベース
    2. オブジェクト/テーブル
    3. 並べ替えられた列
    4. インデックスカバレッジを増やすために追加された列

    3番目のビューは、1番目と2番目のビューの組み合わせです。

    したがって、これらの動的ビューから欠落しているインデックスを作成するスクリプトを生成するスクリプトを取得することは難しくありません。スクリプトは次のとおりです。

    [expand title =” Code”]

    with igs as
    
    (
    
    select *
    
    from sys.dm_db_missing_index_group_stats
    
    )
    
    , igd as
    
    (
    
    select *,
    
    isnull(equality_columns,'')+','+isnull(inequality_columns,'') as ix_col
    
    from sys.dm_db_missing_index_details
    
    )
    
    select --top(10)
    
    'use ['+db_name(igd.database_id)+'];
    
    create index ['+'ix_'+replace(convert(varchar(10),getdate(),120),'-','')+'_'+convert(varchar,igs.group_handle)+'] on '+
    
    igd.[statement]+'('+
    
    case
    
    when left(ix_col,1)=',' then stuff(ix_col,1,1,'')
    
    when right(ix_col,1)=',' then reverse(stuff(reverse(ix_col),1,1,''))
    
    else ix_col
    
    end
    
    +') '+isnull('include('+igd.included_columns+')','')+' with(online=on, maxdop=0)
    
    go
    
    ' command
    
    ,igs.user_seeks
    
    ,igs.user_scans
    
    ,igs.avg_total_user_cost
    
    from igs
    
    join sys.dm_db_missing_index_groups link on link.index_group_handle = igs.group_handle
    
    join igd on link.index_handle = igd.index_handle
    
    where igd.database_id = db_id()
    
    order by igs.avg_total_user_cost * igs.user_seeks desc

    [/エキスパンド]

    インデックスの効率を上げるために、欠落しているインデックスが出力されます。完璧な解決策は、この結果セットが何も返さない場合です。この例では、結果セットは少なくとも1つのインデックスを返します:

    時間がなく、クライアントのバグに対処したくない場合は、クエリを実行し、最初の列をコピーしてサーバー上で実行しました。この後、すべてがうまくいきました。

    これらのインデックスの情報を意識的に扱うことをお勧めします。たとえば、システムが次のインデックスを推奨している場合:

    create index ix_01 on tbl1 (a,b) include (c)
    
    create index ix_02 on tbl1 (a,b) include (d)
    
    create index ix_03 on tbl1 (a)

    そして、これらのインデックスは検索に使用されます。これらのインデックスを、提案された3つすべてをカバーするインデックスに置き換える方が論理的であることは明らかです。

    create index ix_1 on tbl1 (a,b) include (c,d)

    したがって、本番サーバーにデプロイする前に、欠落しているインデックスを確認します。それでも…。繰り返しになりますが、たとえば、失われたインデックスをTFSサーバーにデプロイして、全体的なパフォーマンスを向上させました。この最適化を実行するのに最小限の時間がかかりました。しかし、TFS2015からTFS2017に変更すると、これらの新しいインデックスが原因で更新が行われないという問題に直面しました。それにもかかわらず、それらはマスクによって簡単に見つけることができます

    select * from sys.indexes where name like 'ix[_]2017%'
    

    便利なツール:

    dbForge Index Manager – SQLインデックスのステータスを分析し、インデックスの断片化に関する問題を修正するための便利なSSMSアドイン。


    1. 高度なSQL:CROSSAPPLYおよびOUTERAPLY

    2. T-SQLで文字列の先頭または末尾のスペースを削除する方法

    3. 最初にEntityFrameworkコードを使用してストアドプロシージャを作成しますか?

    4. PHPで複数のテーブルのクエリを書く