1

この質問の長さについて事前に謝罪させてください。すべての定義を与えずに質問する方法がわかりません。

変更追跡の独自の実装を含む SQL Server 2005 データベースを継承しました。トリガーを通じて、データベース内の事実上すべてのフィールドへの変更が 3 つのテーブルのセットに格納されます。このデータベースのアプリケーションでは、ユーザーはさまざまなアイテムの履歴を要求できます。返されるのは、アイテム自体の変更だけでなく、関連するテーブルの変更です。問題は、場合によっては非常に遅くなり、場合によっては、リクエストによって最終的にアプリケーションがクラッシュすることです。クライアントは、誰かが履歴を要求したときに他のユーザーが問題を抱えていることも報告しています.

変更データを格納するテーブルは次のとおりです。

CREATE TABLE [dbo].[tblSYSChangeHistory](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [date] [datetime] NULL,
    [obj_id] [int] NULL,
    [uid] [varchar](50) NULL

このテーブルは、変更されたテーブルを追跡します。Obj_id は Object_ID() が返す値です。

CREATE TABLE [dbo].[tblSYSChangeHistory_Items](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [h_id] [bigint] NOT NULL,
    [item_id] [int] NULL,
    [action] [tinyint] NULL

このテーブルは、変更された項目を追跡します。h_id は tblSYSChangeHistory への外部キーです。item_id は、指定されたテーブル内の変更されたアイテムの PK です。アクションは、挿入、削除、または変更を示します。

CREATE TABLE [dbo].[tblSYSChangeHistory_Details](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    [i_id] [bigint] NOT NULL,
    [col_id] [int] NOT NULL,
    [prev_val] [varchar](max) NULL,
    [new_val] [varchar](max) NULL

このテーブルは、個々の変更を追跡します。i_id は tblSYSChangeHistory_Items への外部キーです。col_id は変更された列を示し、prev_val と new_val はそのフィールドの元の値と新しい値を示します。

実際には、このアーキテクチャをサポートする 4 番目のテーブルがあります。tblSYSChangeHistory_Objects は、オペレーションの平易な英語の説明をデータベース内の特定のテーブルにマップします。

アイテムの履歴を検索するコードは非常に複雑です。非常に長い SP の 1 つのブランチです。関連するパラメータは次のとおりです。

@action varchar(50),
@obj_id bigint = 0,
@uid varchar(50) = '',
@prev_val varchar(MAX) = '',
@new_val varchar(MAX) = '',
@start_date datetime = '',
@end_date datetime = ''

それらをすぐにローカル変数に保存しています(そうすることで別のSPを大幅に高速化できたため):

declare @iObj_id bigint,
        @cUID varchar(50),
        @cPrev_val varchar(max),
        @cNew_val varchar(max),
        @tStart_date datetime,
        @tEnd_date datetime

set @iObj_id = @obj_id
set @cUID = @uid
set @cPrev_val = @prev_val
set @cNew_val = @new_val
set @tStart_date = @start_date
set @tEnd_date = @end_date

そして、SPのそのブランチからのコードは次のとおりです。

    create table #r (obj_id int, item_id int, l tinyint)
    create clustered index #ri on #r (obj_id, item_id)
    insert into #r 
        select object_id(obj_name), @iObj_id, 0  
            from dbo.tblSYSChangeHistory_Objects 
            where obj_type = 'U' and descr = cast(@cPrev_val AS varchar(150))
    declare @i tinyint, @cnt int
    set @i = 1
    while @i <= 4
    begin
        insert into #r 
            select obj_id, item_id, @i 
                from dbo.vSYSChangeHistoryFK a with (nolock) 
                where exists (select null from #r where obj_id = a.rel_obj_id and item_id = a.rel_item_id and l = @i - 1)
                and not exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id)
        set @cnt = @@rowcount
        insert into #r 
            select rel_obj_id, rel_item_id, @i 
                from dbo.vSYSChangeHistoryFK a with (nolock) 
                where object_name(obj_id) not in (<this is a list of particular tables in the database>) 
                and exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id and l between @i - 1 and @i)
                and not exists (select null from #r where obj_id = a.rel_obj_id and item_id = a.rel_item_id)
        set @i = case @cnt + @@rowcount when 0 then 100 else @i + 1 end
    end
    select date, obj_name, item, [uid], [action], 
        pkey, item_id, id, key_obj_id into #tCH_R 
        from dbo.vSYSChangeHistory a with (nolock) 
        where exists (select null from #r where obj_id = a.obj_id and item_id = a.item_id) 
        and (@cUID = '' or uid = @cUID) 
        and (@cNew_val = '' or [action] = @cNew_val)
    declare ch_item_cursor cursor for 
        select distinct pkey, key_obj_id, item_id 
            from #tCH_R 
            where item = '' and pkey <> ''
    open ch_item_cursor
    fetch next from ch_item_cursor 
        into @cPrev_val, @iObj_id, @iCol_id
    while @@fetch_status = 0
    begin
        set @SQLStr = 'select @val = ' + @cPrev_val + 
            ' from ' + object_name(@iObj_id) + ' with (nolock)' + 
            ' where id = @id'
        exec sp_executesql @SQLStr, 
            N'@val varchar(max) output, @id int', 
            @cNew_val output, @iCol_id
        update #tCH_R 
            set item = @cNew_val 
            where key_obj_id = @iObj_id 
            and item_id = @iCol_id
        fetch next from ch_item_cursor 
            into @cPrev_val, @iObj_id, @iCol_id
    end
    close ch_item_cursor
    deallocate ch_item_cursor
    select date, obj_name, 
        cast(item AS varchar(254)) AS item, 
        uid, [action], 
        cast(id AS int) AS id 
        from #tCH_R 
        order by id
    return

ご覧のとおり、コードはビューを使用しています。その定義は次のとおりです。

ALTER VIEW [dbo].[vSYSChangeHistoryFK]
AS
SELECT     i.obj_id, i.item_id, c1.parent_object_id AS rel_obj_id, i2.item_id AS rel_item_id
FROM         dbo.vSYSChangeHistoryItemsD AS i INNER JOIN
                      sys.foreign_key_columns AS c1 ON c1.referenced_object_id = i.obj_id AND c1.constraint_column_id = 1 INNER JOIN
                      dbo.vSYSChangeHistoryItemsD AS i2 ON c1.parent_object_id = i2.obj_id INNER JOIN
                      dbo.tblSYSChangeHistory_Details AS d1 ON d1.i_id = i.min_id AND d1.col_id = c1.referenced_column_id INNER JOIN
                      dbo.tblSYSChangeHistory_Details AS d1k ON d1k.i_id = i2.min_id AND d1k.col_id = c1.parent_column_id AND ISNULL(d1.new_val, 
                      ISNULL(d1.prev_val, '')) = ISNULL(d1k.new_val, ISNULL(d1k.prev_val, '')) --LEFT OUTER JOIN
UNION ALL
SELECT     i0.obj_id, i0.item_id, c01.parent_object_id AS rel_obj_id, i02.item_id AS rel_item_id
FROM         dbo.vSYSChangeHistoryItemsD AS i0 INNER JOIN
                      sys.foreign_key_columns AS c01 ON c01.referenced_object_id = i0.obj_id AND c01.constraint_column_id = 1 AND col_name(c01.referenced_object_id, 
                      c01.referenced_column_id) = 'ID' INNER JOIN
                      dbo.vSYSChangeHistoryItemsD AS i02 ON c01.parent_object_id = i02.obj_id INNER JOIN
                      dbo.tblSYSChangeHistory_Details AS d01k ON i02.min_id = d01k.i_id AND d01k.col_id = c01.parent_column_id AND ISNULL(d01k.new_val, 
                      d01k.prev_val) = CAST(i0.item_id AS varchar(max))

最後に、そのビューはもう 1 つのビューを使用します。

ALTER VIEW [dbo].[vSYSChangeHistoryItemsD]
AS
SELECT     h.obj_id, m.item_id, MIN(m.id) AS min_id
FROM         dbo.tblSYSChangeHistory AS h INNER JOIN
                      dbo.tblSYSChangeHistory_Items AS m ON h.id = m.h_id
GROUP BY h.obj_id, m.item_id

プロファイラーを使用すると、ビュー vSYSChangeHistoryFK が大きな原因であることがわかります。私のテストでは、vSYSChangeHistoryItemsD の 2 つのコピーとforeign_key_columns テーブル間の結合に特定の問題があることが示唆されています。

ここで許容できるパフォーマンスを提供する方法についてのアイデアを探しています。クライアントは、結果が得られずに 15 分ほど待機することがあると報告しています。私は 10 分近くテストしましたが、少なくとも 1 つのケースでは結果が得られませんでした。

2008 年以降にこれを解決する新しい言語要素があれば、クライアントは喜んでアップグレードすると思います。

ありがとう。

4

1 に答える 1

1

うわー、それは混乱です。あなたの大きな利益は、カーソルを削除することです。「存在する場所」が表示されます-これは、一致するものが1つ見つかるとすぐに中止される、素晴らしく効率的なb/cです。そして、「存在しない場所」が表示されます-定義により、すべてをスキャンする必要があります。トップ4を見つけることですか?ROW_NUMBER() OVER (PARTITON BY [一意にするもの] ORDER BY [ID が何であれ] を使用すると、より適切に実行できます。わかりにくいです。select object_id(obj_name), @iObj_id, 0 を使用すると、@ i=1 ループは実際には何でもします (?)

それがやっていることなら、次のように書くことができます

SELECT * from
(
select ROW_NUMBER() OVER (PARTITION BY obj_id ORDER BY item_id desc) as Row, 
     obj_id, item_id
FROM bo.vSYSChangeHistoryFK a with (nolock) 
where obj_type = 'U' and descr = cast(@cPrev_val AS varchar(150))
   ) paged
where Row between 1 and 4 
ORDER BY Row

役立つ DBA レベルの変更は、日付に基づいてパーティション分割スキームを設定することです。頻繁に新しいパーティションにロールオーバーします。古いパーティションを別のディスクに配置します。ほとんどのクエリは、最近のパーティションにヒットするだけで済みます。これは、以前の 1/5 のサイズになるため、他に何も変更せずにはるかに高速になります。

完全な回答ではありません、申し訳ありません。その混乱は解析するのに何時間もかかります

于 2012-06-26T20:43:04.430 に答える