この質問の長さについて事前に謝罪させてください。すべての定義を与えずに質問する方法がわかりません。
変更追跡の独自の実装を含む 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 年以降にこれを解決する新しい言語要素があれば、クライアントは喜んでアップグレードすると思います。
ありがとう。