クエリ中にディスクから何が取得されますか?


13

かなり簡単な質問で、おそらくどこかで答えられましたが、Googleの正しい検索質問を作成できないようです...

特定のテーブルの列の数は、そのテーブルのサブセットでクエリを実行するとき、クエリのパフォーマンスに影響しますか?

たとえば、テーブルFooに20個の列があり、クエリでそれらの列のうち5個しか選択されていない場合、20個(たとえば10個)の列があるとクエリのパフォーマンスに影響しますか?簡単にするために、WHERE句のすべてがこれらの5つの列に含まれていると仮定します。

オペレーティングシステムのディスクキャッシュに加えて、Postgresのバッファキャッシュの使用が心配です。Postgresの物理ストレージ設計に対する理解が非常に失われています。テーブルは複数のページに保存されます(デフォルトではページごとに8kのサイズに設定されています)が、そこからタプルがどのように配置されているのかよくわかりません。PGは、これら5つの列を構成するデータのみをディスクからフェッチするのに十分スマートですか?


残りの150バイトではなく、50バイトのフェッチについて話しています。ディスクはおそらくそれより大きな増分で読み込みます!
アンドマー

これらの番号はどこから取得していますか?
Jmoney38

回答:


14

行の物理ストレージについては、データベースページレイアウトのドキュメントで説明しています。同じ行の列の内容はすべて、同じディスクページに格納されますが、TOASTされたコンテンツ(ページに収まるには大きすぎる)の顕著な例外があります。説明したように、コンテンツは各行内で順番に抽出されます。

データを読み取るには、各属性を順番に調べる必要があります。最初に、nullビットマップに従ってフィールドがNULLかどうかを確認します。ある場合は、次へ進みます。次に、正しい配置であることを確認します。フィールドが固定幅フィールドである場合、すべてのバイトが単純に配置されます。

最も単純な場合(TOASTされていない列)、postgresは必要な列が少ない場合でも行全体をフェッチします。したがって、この場合の答えは「はい」です。特にTOASTしきい値を下回っている間に列の内容が大きい場合、列を増やすと無駄なバッファキャッシュに明らかな悪影響が生じる可能性があります。

TOASTの場合:個々のフィールドが〜2kBを超えると、エンジンはフィールドの内容を個別の物理テーブルに保存します。また、行全体がページ(デフォルトでは8kB)に収まらない場合にも機能します。一部のフィールドはTOASTストレージに移動されます。Docによると:

可変長フィールド(attlen = -1)の場合は、もう少し複雑です。すべての可変長データ型は、共通のヘッダー構造体struct varlenaを共有します。これには、格納された値の全長といくつかのフラグビットが含まれます。フラグに応じて、データはインラインまたはTOASTテーブルのいずれかになります。それも圧縮されている可能性があります

TOASTされたコンテンツは、明示的に必要でない場合はフェッチされないため、フェッチするページの合計数への影響は小さくなります(列ごとに数バイト)。これは、@ dezsoの回答の結果を説明しています。

書き込みに関しては、どの列が変更されても、すべての列を含む各行は各UPDATEで完全に書き換えられます。したがって、列を増やすと書き込みのコストが明らかに高くなります。


それはキックアンサー答えです。まさに私が探しているもの。ありがとうございました。
Jmoney38

1
私は行構造(pageinspect、およびいくつかのサンプルの使用)に関してで見出さ良いリソースここ
Jmoney38

9

ダニエルの答えは、個々の行を読み取るコストに焦点を当てています。このコンテキストNOT NULLでは、固定サイズの列をテーブルの最初に配置すると少し役立ちます。関連する列(クエリ対象の列)を最初に置くと、少し役立ちます。列で整列テトリス再生することで(データの整列による)パディングを最小限に抑えると、少し役立ちます。しかし、特に大きなテーブルについては、最も重要な効果はまだ言及されていません。

列を追加すると、明らかに1つのデータページに収まる行数が少なくなるように、行がより多くのディスクスペースをカバーするようになります(デフォルトでは8 kB)。個々の行は、より多くのページに広がっています。データベースエンジンは通常、個々の行ではなくページ全体フェッチする必要があります。同じ数のページを読み取らなければならない限り、個々の行がいくぶん小さいか大きいかは重要ではありません。

クエリが大きなテーブルの(比較的)小さな部分をフェッチし、インデックスでサポートされているテーブル全体に行が多少ランダムに分散している場合、ほとんど考慮せずにほぼ同じ数のページ読み取りが行われます行サイズに。このような(まれな)場合、無関係な列はそれほど遅くなりません。

通常、順番にまたは近接して入力されたパッチまたは行のクラスターをフェッチし、データページを共有します。これらの行は散らかったために広がっており、クエリを満たすためにより多くのディスクページを読み込む必要があります。通常、クエリを遅くする最も重要な理由は、より多くのページを読む必要があることです。そして、それが無関係な列がクエリを遅くする最も重要な要因です。

大きなデータベースでは、通常、すべてのキャッシュをキャッシュメモリに保持するのに十分なRAMがありません。より大きな行は、より多くのキャッシュ、より多くの競合、より少ないキャッシュヒット、より多くのディスクI / Oを占有します。また、ディスク読み取りは通常、はるかに高価です。SSDではそれほどではありませんが、大きな違いが残っています。これにより、ページ読み取りに関する上記のポイントが追加されます。

それはよく、またはしないことがあり無関係な列がTOAST-EDである場合には問題。関連する列も同様にTOASTされ、ほぼ同じ効果が得られます。


1

小さなテスト:

CREATE TABLE test2 (
    id serial PRIMARY KEY,
    num integer,
    short_text varchar(32),
    longer_text varchar(1000),
    long_long_text text
);

INSERT INTO test2 (num, short_text, longer_text, long_long_text)
SELECT i, lpad('', 32, 'abcdefeghji'), lpad('', 1000, 'abcdefeghji'), lpad('', (random() * 10000)::integer, 'abcdefeghji')
FROM generate_series(1, 10000) a(i);

ANALYZE test2;

SELECT * FROM test2;
[...]
Time: 1091.331 ms

SELECT num FROM test2;
[...]
Time: 21.310 ms

クエリを最初の250行(WHERE num <= 250)に制限すると、それぞれ34.539ミリ秒と8.343ミリ秒になります。long_long_textこの限られたセット以外をすべて選択すると、18.432ミリ秒になります。これは、PGが十分に賢いということを示しています。


まあ、私は確かに入力に感謝します。ただし、このテストシナリオが最初に提案したものを証明しているとは断言できません。いくつかの問題があります。たとえば、最初に「SELECT * FROM test2」を実行すると、共有バッファキャッシュがいっぱいになります。そのクエリは、ディスクから取得するのにはるかに時間がかかります。したがって、2番目のクエリはSBキャッシュからフェッチされていたため、理論的にははるかに高速でした。しかし、後のテスト/比較に基づいて、PGが必要な行のみをフェッチすることを「提案」することに同意します。
Jmoney38

あなたは正しい、このテスト(単純な)には欠点がある。十分な時間があれば、これらについてもカバーしようとします。
dezso
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.