1970年に発行されたE.F.Coddの論文「ARelationalModelof Data for Large Shared Data Banks」に最初に記載されているように、プライマリキーと外部キーはリレーショナルデータベースの基本的な特性です。キー以外は何もないので、コッドを助けてください。」
背景:主キー
主キーはSQLServerの制約であり、テーブル内の各行を一意に識別するように機能します。キーは、単一の非NULL列、または一意の値を生成する非NULL列の組み合わせとして定義でき、テーブルのエンティティー整合性を強制するために使用されます。テーブルには主キーを1つだけ含めることができ、テーブルに主キー制約が定義されると、一意のインデックスが作成されます。主キー制約が定義されているときに非クラスター化インデックスとして指定されていない限り、そのインデックスはデフォルトでクラスター化インデックスになります。
Sales.SalesOrderHeader
について考えてみましょう。 AdventureWorks2012
のテーブル データベース。このテーブルには、注文日や顧客IDなど、販売注文に関する基本情報が保持され、各販売はSalesOrderID
によって一意に識別されます。 、これはテーブルの主キーです。テーブルに新しい行が追加されるたびに、主キー制約(PK_SalesOrderHeader_SalesOrderID
という名前) )は、SalesOrderID
と同じ値の行がすでに存在しないことを確認するためにチェックされます 。
外部キー
主キーとは別ですが、非常に関連性が高いのは外部キーです。外部キーは、主キーと同じであるがテーブルが異なる列または列の組み合わせです。外部キーは、関係を定義し、2つのテーブル間の整合性を強化するために使用されます。
前述の例を引き続き使用するには、SalesOrderID
列はSales.SalesOrderDetail
に外部キーとして存在します テーブル。製品IDや価格などの販売に関する追加情報が保存されます。 SalesOrderHeader
に新しいセールが追加されたとき テーブルの場合、その販売の行をSalesOrderDetail
に追加する必要はありません。 テーブルただし、SalesOrderDetail
に行を追加する場合 テーブル、SalesOrderID
に対応する行 必須 SalesOrderHeader
に存在します テーブル。
逆に、データを削除する場合、特定のSalesOrderID
の行 SalesOrderDetail
からいつでも削除できます テーブルですが、SalesOrderHeader
から行を削除するために テーブル、SalesOrderDetail
からの関連行 最初に削除する必要があります。
主キー制約とは異なり、テーブルに外部キー制約が定義されている場合、SQLServerではデフォルトでインデックスが作成されません。ただし、開発者やデータベース管理者が手動で追加することは珍しくありません。外部キーは、テーブルの複合主キーの一部である可能性があります。その場合、クラスター化インデックスは、クラスター化キーの一部として外部キーとともに存在します。または、クエリで外部キーとテーブル内の1つ以上の追加列を含むインデックスが必要になる場合があるため、これらのクエリをサポートするために非クラスター化インデックスが作成されます。さらに、外部キーのインデックスは、主キーと外部キーを含むテーブル結合のパフォーマンスを向上させることができ、主キーの値が更新されたとき、または行が削除されたときにパフォーマンスに影響を与える可能性があります。
AdventureWorks2012
内 データベースには、SalesOrderDetail
という1つのテーブルがあります 、SalesOrderID
を使用 外部キーとして。 SalesOrderDetail
の場合 テーブル、SalesOrderID
およびSalesOrderDetailID
結合して主キーを形成し、クラスター化インデックスによってサポートされます。 SalesOrderDetail
の場合 テーブルにSalesOrderID
のインデックスがありませんでした 列、次にSalesOrderHeader
から行が削除されたとき 、SQL Serverは、同じSalesOrderID
の行がないことを確認する必要があります 値が存在します。 SalesOrderID
を含むインデックスなし 列の場合、SQLServerはSalesOrderDetail
の全表スキャンを実行する必要があります 。ご想像のとおり、参照されるテーブルが大きいほど、削除に時間がかかります。
例
これは、AdventureWorks2012
からの前述のテーブルのコピーを使用する次の例で確認できます。 ここにあるスクリプトを使用して拡張されたデータベース。このスクリプトは、Jonathan Kehayias(ブログ| @SQLPoolBoy)によって開発され、SalesOrderHeaderEnlarged
を作成します。 1,258,600行のテーブル、およびSalesOrderDetailEnlarged
4,852,680行のテーブル。スクリプトの実行後、以下のステートメントを使用して外部キー制約が追加されました。制約はON DELETE CASCADE
で作成されることに注意してください オプション。このオプションを使用すると、SalesOrderHeaderEnlarged
に対して更新または削除が発行された場合 テーブル、対応するテーブルの行–この場合はSalesOrderDetailEnlarged
–更新または削除されます。
さらに、SalesOrderDetailEnglarged
のデフォルトのクラスター化されたインデックス SalesOrderDetailID
だけを持つように削除され、再作成されました 典型的なデザインを表すため、主キーとして使用します。
USE [AdventureWorks2012]; GO /* remove original clustered index */ ALTER TABLE [Sales].[SalesOrderDetailEnlarged] DROP CONSTRAINT [PK_SalesOrderDetailEnlarged_SalesOrderID_SalesOrderDetailID]; GO /* re-create clustered index with SalesOrderDetailID only */ ALTER TABLE [Sales].[SalesOrderDetailEnlarged] ADD CONSTRAINT [PK_SalesOrderDetailEnlarged_SalesOrderDetailID] PRIMARY KEY CLUSTERED ( [SalesOrderDetailID] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]; GO /* add foreign key constraint for SalesOrderID */ ALTER TABLE [Sales].[SalesOrderDetailEnlarged] WITH CHECK ADD CONSTRAINT [FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID] FOREIGN KEY([SalesOrderID]) REFERENCES [Sales].[SalesOrderHeaderEnlarged] ([SalesOrderID]) ON DELETE CASCADE; GO ALTER TABLE [Sales].[SalesOrderDetailEnlarged] CHECK CONSTRAINT [FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID]; GO
外部キー制約があり、サポートインデックスがない場合、SalesOrderHeaderEnlarged
に対して1回の削除が発行されました。 テーブル。これにより、SalesOrderHeaderEnlarged
から1行が削除されました。 およびSalesOrderDetailEnlarged
からの72行 :
SET STATISTICS IO ON; SET STATISTICS TIME ON; DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; USE [AdventureWorks2012]; GO DELETE FROM [Sales].[SalesOrderHeaderEnlarged] WHERE [SalesOrderID] = 292104;
統計IOおよびタイミング情報は次のことを示しています:
SQL Serverの解析とコンパイル時:CPU時間=8ミリ秒、経過時間=8ミリ秒。
テーブル'SalesOrderDetailEnlarged'。スキャンカウント1、論理読み取り50647、物理読み取り8、先読み読み取り50667、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
テーブル'Worktable'。スキャンカウント2、論理読み取り7、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
テーブル'SalesOrderHeaderEnlarged'。スキャンカウント0、論理読み取り15、物理読み取り14、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
SQL Serverの実行時間:
CPU時間=1045ミリ秒、経過時間=1898ミリ秒。
SQL Sentry Plan Explorerを使用すると、実行プランはSalesOrderDetailEnlarged
に対するクラスター化インデックススキャンを表示します。 SalesOrderID
にはインデックスがないため :
SalesOrderDetailEnlarged
をサポートする非クラスター化インデックス 次に、次のステートメントを使用して作成されました:
USE [AdventureWorks2012]; GO /* create nonclustered index */ CREATE NONCLUSTERED INDEX [IX_SalesOrderDetailEnlarged_SalesOrderID] ON [Sales].[SalesOrderDetailEnlarged] ( [SalesOrderID] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]; GO
SalesOrderID
に対して別の削除が実行されました SalesOrderHeaderEnlarged
の1行に影響しました SalesOrderDetailEnlarged
の72行 :
SET STATISTICS IO ON; SET STATISTICS TIME ON; DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; USE [AdventureWorks2012]; GO DELETE FROM [Sales].[SalesOrderHeaderEnlarged] WHERE [SalesOrderID] = 697505;
統計IOとタイミング情報は劇的な改善を示しました:
SQL Serverの解析とコンパイル時:CPU時間=0ミリ秒、経過時間=7ミリ秒。
テーブル'SalesOrderDetailEnlarged'。スキャンカウント1、論理読み取り48、物理読み取り13、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
表'Worktable'。スキャンカウント2、論理読み取り7、物理読み取り0、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
テーブル'SalesOrderHeaderEnlarged'。スキャンカウント0、論理読み取り15、物理読み取り15、先読み読み取り0、lob論理読み取り0、lob物理読み取り0、lob先読み読み取り0。
SQL Serverの実行時間:
CPU時間=0ミリ秒、経過時間=27ミリ秒。
また、クエリプランは、SalesOrderID
の非クラスター化インデックスのインデックスシークを示しました。 、予想どおり:
クエリの実行時間は1898ミリ秒から27ミリ秒に短縮され、98.58%短縮され、SalesOrderDetailEnlarged
を読み取ります。 テーブルは50647から48に減少しました– 99.9%の改善。パーセンテージはさておき、削除によって生成されたI/Oのみを考慮してください。 SalesOrderDetailEnlarged
この例では、テーブルはわずか500 MBであり、256 GBの使用可能なメモリを備えたシステムの場合、バッファキャッシュで500MBを占めるテーブルはひどい状況のようには見えません。しかし、500万行のテーブルは比較的小さいです。ほとんどの大規模なOLTPシステムには、数億行のテーブルがあります。さらに、主キーに対して複数の外部キー参照が存在することも珍しくありません。主キーを削除するには、関連する複数のテーブルから削除する必要があります。その場合、分離レベルによっては、パフォーマンスの問題だけでなく、ブロッキングの問題でもある削除の期間が長くなる可能性があります。
結論
一般に、主キーと外部キーの間の結合だけでなく、更新と削除もサポートするために、外部キー列につながるインデックスを作成することをお勧めします。テーブルサイズが非常に小さいために外部キーの追加インデックスが使用されなかったエッジケースシナリオがあり、追加のインデックス更新が実際にパフォーマンスに悪影響を及ぼしたため、これは一般的な推奨事項であることに注意してください。他のスキーマ変更と同様に、インデックスの追加は、実装後にテストおよび監視する必要があります。追加のインデックスが目的の効果を生み出し、ソリューションのパフォーマンスに悪影響を与えないようにすることが重要です。また、外部キーのインデックスに必要な追加スペースの量にも注意してください。これは、インデックスを作成する前に検討する必要があり、インデックスがメリットをもたらす場合は、今後のキャパシティプランニングで検討する必要があります。