11

約100個のテーブル(主キーを含む)を(外部キーとして)参照する100個の列を持つメインテーブルがあるとします。

情報のパック全体では、これらの100個のテーブルを結合する必要があります。そして、そのような数のテーブルを結合することは間違いなくパフォーマンスの問題です。うまくいけば、すべてのユーザーが、次のフィールドに条件(クエリのWHERE部分)を設定するクエリで、5〜7個以下のテーブル(100個のうち)からの値を含む一連のデータを要求することを期待できます。いくつかの3-4テーブル(それらの100のうち)。クエリが異なれば、クエリの「SELECT」部分を生成し、「WHERE」に条件を設定するために使用されるテーブルの組み合わせも異なります。ただし、ここでも、すべてのSELECTには5〜7個のテーブルが必要であり、すべてのWHEREには3〜4個のテーブルが必要です(間違いなく、SELECTの生成に使用されるテーブルのリストは、WHEREに条件を設定するために使用されるテーブルのリストと重複する場合があります)。

これらの100個のテーブルすべてを結合する基になるコードを使用してVIEWを記述できます。次に、上記のSQLクエリをこのビューに書き込むことができます。しかし、この場合、SQL Serverに(コード内の100個のテーブルすべてを結合する明示的な指示にもかかわらず)11個のテーブルのみを結合するように指示する方法は私にとって大きな問題です(11個のテーブルはSELECTを生成するために結合するのに十分です)結果とWHERE条件を考慮に入れます)。

別のアプローチは、次の「偽の」コードを変換する「機能」を作成することです。

SELECT field1, field2, field3 FROM TheFakeTable WHERE field1=12 and field4=5

次の「実際の」コードに:

SELECT T1.field1, T2.field2, T3.field3 FROM TheRealMainTable 
join T1 on ....
join T2 on ....
join T3 on ....
join T4 on ....
WHERE T1.field1=12 and T4.field4=5

文法的な観点からは、この「TheFakeTable-mechanism」と実際のテーブルおよび構造との混合組み合わせを許可することさえ問題ではありません。ここでの本当の問題は、この「機能」を技術的に実現する方法です。「偽の」コードを入力として受け取り、「実際の」コードを生成する関数を作成できます。ただし、この「TheFakeTable-mechanism」が表示される場所では、動的SQLツールを使用する必要があるため便利ではありません。幻想的な解決策は、Management StudioでSQL言語のグラマを拡張して、そのような偽のコードを記述し、サーバーに送信する前にこのコードを実際のコードに自動的に変換できるようにすることです。

私の質問は次のとおりです。

  1. SQl Serverがshomehowに(または天才になるように)指示されて、上記のVIEWで100ではなく11のテーブルのみを結合できるかどうか。
  2. この「TheFakeTable-mechanism」機能を作成することにした場合、この機能を技術的に実現するための最良の形式は何でしょうか。

すべてのコメントをありがとう!

PS 100個のテーブルを持つ構造は、私がここで尋ねた次の質問から生じます。 非常に大きなテーブルの正規化

4

3 に答える 3

17

SQL Serverオプティマイザーには、冗長結合を削除するロジックが含まれていますが、制限があり、結合はおそらく冗長である必要があります。要約すると、結合には4つの効果があります。

  1. (結合されたテーブルから)列を追加できます
  2. 行を追加できます(結合されたテーブルがソース行と複数回一致する場合があります)
  3. 行を削除できます(結合されたテーブルが一致しない場合があります)
  4. NULLsを導入できます(RIGHTまたはの場合FULL JOIN

冗長な結合を正常に削除するには、クエリ(またはビュー)で4つの可能性すべてを考慮する必要があります。これが正しく行われると、その効果は驚くべきものになる可能性があります。例えば:

USE AdventureWorks2012;
GO
CREATE VIEW dbo.ComplexView
AS
    SELECT
        pc.ProductCategoryID, pc.Name AS CatName,
        ps.ProductSubcategoryID, ps.Name AS SubCatName,
        p.ProductID, p.Name AS ProductName,
        p.Color, p.ListPrice, p.ReorderPoint,
        pm.Name AS ModelName, pm.ModifiedDate
    FROM Production.ProductCategory AS pc
    FULL JOIN Production.ProductSubcategory AS ps ON
        ps.ProductCategoryID = pc.ProductCategoryID
    FULL JOIN Production.Product AS p ON
        p.ProductSubcategoryID = ps.ProductSubcategoryID
    FULL JOIN Production.ProductModel AS pm ON
        pm.ProductModelID = p.ProductModelID

オプティマイザーは、次のクエリを正常に簡略化できます。

SELECT
    c.ProductID,
    c.ProductName
FROM dbo.ComplexView AS c
WHERE
    c.ProductName LIKE N'G%';

に:

簡略化された計画

Rob Farleyは、元のMVP Deep Divesの本でこれらのアイデアについて詳しく書いています。また、SQLBitsでこのトピックについて発表した彼の記録があります。

主な制限は、単純化プロセスに寄与するために外部キーの関係は単一のキーに基づく必要があることです。このようなビューに対するクエリのコンパイル時間は、特に結合の数が増えると非常に長くなる可能性があります。すべてのセマンティクスを正確に取得する100テーブルのビューを作成することは非常に難しい場合があります。おそらく動的SQLを使用して、別の解決策を見つけたいと思います。

とは言うものの、非正規化されたテーブルの特定の品質は、ビューの組み立てが非常に簡単であり、強制された不可能な参照列と、100の物理結合演算子のオーバーヘッドなしでこのソリューションを期待どおりに機能させるための適切な制約のみを必要とすることを意味する場合FOREIGN KEYsNULLありUNIQUEます計画の中で。

100ではなく10のテーブルを使用する:

-- Referenced tables
CREATE TABLE dbo.Ref01 (col01 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref02 (col02 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref03 (col03 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref04 (col04 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref05 (col05 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref06 (col06 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref07 (col07 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref08 (col08 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref09 (col09 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref10 (col10 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);

親テーブルの定義(ページ圧縮あり):

CREATE TABLE dbo.Normalized
(
    pk      integer IDENTITY NOT NULL,
    col01   tinyint NOT NULL REFERENCES dbo.Ref01,
    col02   tinyint NOT NULL REFERENCES dbo.Ref02,
    col03   tinyint NOT NULL REFERENCES dbo.Ref03,
    col04   tinyint NOT NULL REFERENCES dbo.Ref04,
    col05   tinyint NOT NULL REFERENCES dbo.Ref05,
    col06   tinyint NOT NULL REFERENCES dbo.Ref06,
    col07   tinyint NOT NULL REFERENCES dbo.Ref07,
    col08   tinyint NOT NULL REFERENCES dbo.Ref08,
    col09   tinyint NOT NULL REFERENCES dbo.Ref09,
    col10   tinyint NOT NULL REFERENCES dbo.Ref10,

    CONSTRAINT PK_Normalized
        PRIMARY KEY CLUSTERED (pk)
        WITH (DATA_COMPRESSION = PAGE)
);

景色:

CREATE VIEW dbo.Denormalized
WITH SCHEMABINDING AS
SELECT
    item01 = r01.item,
    item02 = r02.item,
    item03 = r03.item,
    item04 = r04.item,
    item05 = r05.item,
    item06 = r06.item,
    item07 = r07.item,
    item08 = r08.item,
    item09 = r09.item,
    item10 = r10.item
FROM dbo.Normalized AS n
JOIN dbo.Ref01 AS r01 ON r01.col01 = n.col01
JOIN dbo.Ref02 AS r02 ON r02.col02 = n.col02
JOIN dbo.Ref03 AS r03 ON r03.col03 = n.col03
JOIN dbo.Ref04 AS r04 ON r04.col04 = n.col04
JOIN dbo.Ref05 AS r05 ON r05.col05 = n.col05
JOIN dbo.Ref06 AS r06 ON r06.col06 = n.col06
JOIN dbo.Ref07 AS r07 ON r07.col07 = n.col07
JOIN dbo.Ref08 AS r08 ON r08.col08 = n.col08
JOIN dbo.Ref09 AS r09 ON r09.col09 = n.col09
JOIN dbo.Ref10 AS r10 ON r10.col10 = n.col10;

統計をハックして、オプティマイザーにテーブルが非常に大きいと思わせるようにします。

UPDATE STATISTICS dbo.Normalized WITH ROWCOUNT = 100000000, PAGECOUNT = 5000000;

ユーザークエリの例:

SELECT
    d.item06,
    d.item07
FROM dbo.Denormalized AS d
WHERE
    d.item08 = 'Banana'
    AND d.item01 = 'Green';

この実行プランを提供します。

実行計画1

正規化されたテーブルのスキャンは不良に見えますが、両方のブルームフィルタービットマップがストレージエンジンによるスキャン中に適用されます(したがって、一致できない行はクエリプロセッサまで表示されません)。これは、あなたのケースで許容できるパフォーマンスを提供するのに十分であり、オーバーフローした列を持つ元のテーブルをスキャンするよりも確かに優れています。

ある段階でSQLServer2012 Enterpriseにアップグレードできる場合は、別のオプションがあります。正規化されたテーブルに列ストアインデックスを作成することです。

CREATE NONCLUSTERED COLUMNSTORE INDEX cs 
ON dbo.Normalized (col01,col02,col03,col04,col05,col06,col07,col08,col09,col10);

実行計画は次のとおりです。

列ストア計画

それはおそらくあなたには悪いように見えますが、列ストレージは並外れた圧縮を提供し、実行プラン全体がバッチモードで実行され、すべての寄与列にフィルターが適用されます。サーバーに十分なスレッドと使用可能なメモリがある場合、この代替手段は実際に飛ぶ可能性があります。

最終的に、この正規化が、テーブルの数と、不十分な実行プランを取得したり、過度のコンパイル時間を必要としたりする可能性を考慮すると、正しいアプローチであるかどうかはわかりません。私はおそらく最初に非正規化されたテーブルのスキーマを修正し(適切なデータ型など)、おそらくデータ圧縮を適用します...通常のもの。

データが本当にスタースキーマに属している場合は、繰り返しデータ要素を別々のテーブルに分割するだけでなく、おそらくより多くの設計作業が必要になります。

于 2013-02-08T20:34:29.297 に答える
4

100個のテーブルを結合することがパフォーマンスの問題になるのはなぜだと思いますか?

すべてのキーが主キーである場合、すべての結合はインデックスを使用します。したがって、唯一の問題は、インデックスがメモリに収まるかどうかです。それらがメモリに収まる場合、パフォーマンスはおそらくまったく問題ではありません。

このようなステートメントを作成する前に、100個の結合を使用してクエリを試行する必要があります。

さらに、元の質問に基づいて、参照テーブルにはいくつかの値が含まれています。テーブル自体は、1つのページに加えて、インデックス用の別のページに収まります。これは200ページであり、ページキャッシュの最大で数メガバイトを占有します。最適化について心配する必要はありません。ビューを作成し、パフォーマンスに問題がある場合は、次の手順を検討してください。パフォーマンスの問題を前提としないでください。

精緻化:

これは多くのコメントを受け取りました。このアイデアが思ったほどクレイジーではない理由を説明しましょう。

まず、すべての結合が主キーインデックスを介して行われ、インデックスがメモリに収まると想定しています。

ページ上の100個のキーは400バイトを占めます。元の文字列が平均してそれぞれ40バイトであるとしましょう。これらはページ上で4,000バイトを占めていたので、節約できます。実際、以前のスキームでは、約2つのレコードが1ページに収まります。キーを使用して1ページに約20個収まります。

したがって、キーを使用してレコードを読み取ることは、元のレコードを読み取るよりもI/Oの点で約10倍高速です。値の数が少ないことを前提として、インデックスと元のデータはメモリに収まります。

20レコードを読み取るのにどのくらい時間がかかりますか?古い方法では10ページを読む必要がありました。キーを使用すると、1ページの読み取りと100 * 20のインデックスルックアップがあります(値を取得するための追加のルックアップが必要になる可能性があります)。システムによっては、2,000個のインデックスルックアップが追加の9ページのI/Oよりも高速になる場合があります。私が言いたいのは、これは合理的な状況であるということです。特定のシステムで発生する場合と発生しない場合がありますが、それほどクレイジーではありません。

これは少し単純化されすぎています。SQL Serverは、実際には一度に1ページずつ読み取ることはありません。それらは4つのグループで読み取られると思います(全表スキャンを実行するときに先読み読み取りが行われる可能性があります)。ただし、反対に、ほとんどの場合、テーブルスキャンクエリはプロセッサバウンドよりもI / Oバウンドになるため、参照テーブルで値を検索するための予備のプロセッササイクルがあります。

実際、キーを使用すると、キーを使用しない場合よりもテーブルの読み取りが速くなる可能性があります。これは、ルックアップに予備の処理サイクルが使用されるためです(読み取り時に処理能力が利用できるという意味での「予備」)。実際、キーを含むテーブルは、使用可能なキャッシュに収まるほど小さい場合があり、より複雑なクエリのパフォーマンスが大幅に向上します。

実際のパフォーマンスは、文字列の長さ、元のテーブル(使用可能なキャッシュよりも大きいかどうか)、I / O読み取りと処理を同時に実行する基盤となるハードウェアの能力など、多くの要因によって異なります。結合を正しく行うためのクエリオプティマイザへの依存。

私の最初のポイントは、100の結合が悪いことであるとアプリオリに仮定することは正しくないということでした。仮定をテストする必要があり、キーを使用するとパフォーマンスが向上する場合もあります。

于 2013-02-08T19:56:18.177 に答える
0

データがあまり変わらない場合は、基本的に ビューを具体化するインデックス付きビューを作成することでメリットが得られる場合があります。

データが頻繁に変更される場合、サーバーはビューの基になるテーブルの変更ごとにインデックス付きビューを維持する必要があるため、これは適切なオプションではない可能性があります。

これは、それをもう少しよく説明している良いブログ投稿です。

ブログから:

CREATE VIEW dbo.vw_SalesByProduct_Indexed
 WITH SCHEMABINDING
 AS
      SELECT 
            Product, 
            COUNT_BIG(*) AS ProductCount, 
            SUM(ISNULL(SalePrice,0)) AS TotalSales
      FROM dbo.SalesHistory
      GROUP BY Product
 GO

以下のスクリプトは、ビューにインデックスを作成します。

CREATE UNIQUE CLUSTERED INDEX idx_SalesView ON vw_SalesByProduct_Indexed(Product)

ビューにインデックスが作成され、データベース内のスペースを占有していることを示すには、次のスクリプトを実行して、クラスター化インデックス内の行数とビューが占有するスペースを確認します。

EXECUTE sp_spaceused 'vw_SalesByProduct_Indexed'

以下のSELECTステートメントは、以前と同じステートメントですが、今回はクラスター化インデックスシークを実行します。これは通常、非常に高速です。

SELECT 
      Product, TotalSales, ProductCount 
 FROM vw_SalesByProduct_Indexed
 WHERE Product = 'Computer'
于 2013-02-08T20:46:04.083 に答える