8

数億行のデータベースがあります。私は次のクエリを実行しています:

select * from "Payments" as p
inner join "PaymentOrders" as po
on po."Id" = p."PaymentOrderId"
inner join "Users" as u
On u."Id" = po."UserId"
INNER JOIN "Roles" as r
on u."RoleId" = r."Id"
Where r."Name" = 'Moses'
LIMIT 1000

where句がデータベースで一致するものを見つけると、数ミリ秒で結果が得られますが、クエリを変更しr."Name"てwhere句に存在しないものを指定すると、完了するまでに時間がかかりすぎます。PaymentsPostgreSQLは、各行を1つずつ比較して、テーブル(最も多くの行を含む)に対して順次スキャンを実行していると思います。

Rolespostgresqlは、テーブルに次の行が含まれているかどうかを最初にチェックするのに十分スマートではありませんName 'Moses'か?

Rolesテーブルには15行しか含まれていませんが、Paymentsには約3億5000万行が含まれています。

PostgreSQL9.2.1を実行しています。

ところで、同じスキーマ/データに対するこの同じクエリは、MSSQLServerで完了するのに0.024msかかります。

質問を更新し、数時間以内にEXPLAINANALYZEデータを投稿します。


分析結果の説明:http://explain.depesz.com/s/7e7


そして、ここにサーバー構成があります:

version PostgreSQL 9.2.1, compiled by Visual C++ build 1600, 64-bit
client_encoding UNICODE
effective_cache_size    4500MB
fsync   on
lc_collate  English_United States.1252
lc_ctype    English_United States.1252
listen_addresses    *
log_destination stderr
log_line_prefix %t 
logging_collector   on
max_connections 100
max_stack_depth 2MB
port    5432
search_path dbo, "$user", public
server_encoding UTF8
shared_buffers  1500MB
TimeZone    Asia/Tbilisi
wal_buffers 16MB
work_mem    10MB

私はpostgresqlをi5cpu(4コア、3.3 GHz)、8 GBのRAM、およびCrucial m4SSD128GBで実行しています。


更新 これはクエリプランナーのバグのようです。Erwin Brandstetterの推薦で、私はそれをPostgresqlバグメーリングリストに報告しました。

4

2 に答える 2

10

PostgreSQLコミュニティのパフォーマンスリストのスレッドで数回提案されているように、次のようにCTEを使用して最適化バリアを強制することでこの問題を回避できます。

WITH x AS
(
SELECT *
  FROM "Payments" AS p
  JOIN "PaymentOrders" AS po ON po."Id" = p."PaymentOrderId"
  JOIN "Users" as u ON u."Id" = po."UserId"
  JOIN "Roles" as r ON u."RoleId" = r."Id"
  WHERE r."Name" = 'Moses'
)
SELECT * FROM x
  LIMIT 1000;

「Roles」、「Name」、「ANALYZE」の統計ターゲットを高く設定すると、元のクエリの適切な計画を立てることもできます。例えば:

ALTER TABLE "Roles"
  ALTER COLUMN "Name" SET STATISTICS 1000;
ANALYZE "Roles";

より詳細な統計で行われる可能性が高いため、テーブルに存在する一致する行が少ないと予想される場合、順次スキャンでそれらを見つけるには、テーブルのより高い割合を読み取る必要があると想定されます。これにより、テーブルを順番にスキャンするのではなく、インデックスを使用する方が好まれる可能性があります。

また、プランナーのコスト定数とキャッシングの仮定の一部を調整することで、元のクエリのより良いプランを取得できる場合があります。SET次のコマンドを使用して、1回のセッションで試すことができること:

  • 減らすrandom_page_cost。これは主に、データがどれだけ大量にキャッシュされているかに基づいています。数億行のテーブルがあるとすると、おそらく2を下回ったくないでしょう。ただし、データベース内のアクティブなデータセットが大量にキャッシュされている場合は、の設定seq_page_costまで減らすことができ、両方を1桁減らすことができます。

  • shared_buffersEffective_cache_sizeが、OSがキャッシュしているものの合計に設定されていることを確認してください。これはメモリを割り当てません。これは、アクセスが多いときにインデックスページがキャッシュに残る可能性をオプティマイザに通知するだけです。設定を高くすると、シーケンシャルスキャンと比較してインデックスの見栄えが良くなります。

  • cpu_tuple_cost0.03から0.05の範囲のどこかに増加します。デフォルトの0.01は低すぎることがわかりました。私はそれを増やすことによってより良い計画を得ることがよくあります、そして私が提案した範囲の値がより悪い計画が選ばれる原因となるのを見たことがありません。

  • work_mem設定が適切であることを確認してください。私がPostgreSQLを実行しているほとんどの環境では、16MBから64MBの範囲です。これにより、ハッシュテーブル、ビットマップインデックスのスキャン、並べ替えなどをより適切に使用できるようになり、計画を完全に変更できます。ほとんどの場合、より良い方向に向かっています。多数の接続がある場合は、これを適切な計画が得られるレベルに設定することに注意してください。各接続が、実行中のクエリのノードごとにこれだけのメモリを割り当てることができるという事実を考慮に入れる必要があります。「経験則」は、この設定時間の前後でピークに達することを理解することですmax_connections。これが、接続プールを使用してデータベース接続の実際の数を制限することが賢明な理由の1つです。

これらの設定の適切な組み合わせを見つけた場合は、ファイルにこれらの変更を加えることをお勧めしpostgresql.confます。その場合は、パフォーマンスの低下を注意深く監視し、全体的な負荷のパフォーマンスが最高になるように設定を微調整する準備をしてください。

オプティマイザーが平均してより速く実行されるように見えても、オプティマイザーを「危険な」計画から遠ざけるために何かをする必要があることに同意します。しかし、オプティマイザーが各選択肢の実際のコストをより適切にモデル化して効率的な計画を使用しないように構成を調整すると、少し驚かれることでしょう。

于 2012-11-20T18:09:14.313 に答える
5

最後に成功した試み

私の他のアイデア-コメントによると:役割が見つからない場合
の条項を削除するとどうなりますか?LIMIT私はそれが迅速な計画につながるのではないかと疑っています-LIMITここで犯人を作ります。

クエリをサブクエリLIMITにプッシュダウンし、外部クエリ(テストされていない)にのみ適用することで、問題を解決できる場合があります。

SELECT *
FROM  (
   SELECT *
   FROM   "Roles"         AS r  
   JOIN   "Users"         AS u  ON u."RoleId" = r."Id"
   JOIN   "PaymentOrders" AS po ON po."UserId" = u."Id"
   JOIN   "Payments"      AS p  ON p."PaymentOrderId" = po."Id"
   WHERE  r."Name" = 'Moses'
  ) x
LIMIT  1000;

コメントによると:@Davitaはこの回避策をテストし、除外しました。@Kevinの回答は、回避策が失敗した理由を後で明らかにしました。サブクエリの代わりにCTEを使用してください。
または、大きなクエリを使用して悪いケースを排除する前に、ロールの存在を確認してください。

これにより、。を使用したクエリの最適化に関するPostgreSQLの質問が残りますLIMIT

を使用したクエリプランに関する最近のバグレポートがLIMIT多数あります。私はここでこれらのレポートの1つにコメントしているSimonRiggsを引用します:

LIMITを使用した非常に悪い計画が頻繁に発生します。LIMITを追加すると、通常、クエリが遅くなるのではなく、速くなるはずなので、これは私たちにとって悪いことです。

私たちは何かをする必要があります。

成功しなかった最初の試み

@Craigjoin_collapse_limitがコメントですでに言及していることを見逃しました。したがって、それは限られた用途でした:

JOIN句の並べ替えは効果がありますか?

SELECT *
FROM   "Roles"         AS r  
JOIN   "Users"         AS u  ON u."RoleId" = r."Id"
JOIN   "PaymentOrders" AS po ON po."UserId" = u."Id"
JOIN   "Payments"      AS p  ON p."PaymentOrderId" = po."Id"
WHERE  r."Name" = 'Moses'
LIMIT  1000

関連:あなたはたまたままたはの設定を台無しにしませんでしたjoin_collapse_limitgeqo_threshold?設定が非常に低いと、プランナーがJOIN句を並べ替えることができなくなり、問題が説明される可能性があります。

それでも問題が解決しない場合は、にインデックスを作成してみます"Roles"(Name)。これは15行だけでは意味がありませんが、無効な統計やコストパラメータ(またはバグ)によってプランナーが「ロール」のシーケンシャルスキャンが実際よりも高価であると信じ込ませる疑いを排除しようと思います。

于 2012-11-16T11:47:07.977 に答える