1

多くの情報を保持する2つのレベルでネストされた5つのテーブルを持つRails 3アプリケーションがあります(テーブル1には多く>テーブル2には多く>テーブル3があります)。大量のデータが保存され、すばやく保存する必要があり、データを引き出すためにカウントが行われることを考えると、表示用のデータを取得するときに多くのクエリが実行される Web サイト訪問者の追跡システムのようなものだと考えてください。

最初はSQLをあまり考えずにアプリを作成しましたが、それを実行するためだけに、使用するデータがあるため、db部分の最適化を開始すると考えました。

すべてのテーブルで約 100 万件のレコードが合計された時点で、リクエストごとに 1 秒の応答があるため、最適化を開始する時期だと考えました。

私の Rails アプリは、結合を伴わずにカウントごとにクエリを実行します。user.websites.hits (ユーザーを選択してから別の選択を実行して Web サイトを取得し、Web サイトごとに選択を実行して訪問者の数を取得する) のようなもののデフォルトの動作です。合計で、必要なものすべてを含むページの結果を取得するために約 80 のクエリが必要になると思います (わかっています...)。そのため、1 つの要求からすべての結果を取得するクエリを作成しました。

問題は、データベース管理者でクエリを実行すると、ページが 80 個のクエリを実行し、テンプレートとアセットを読み込んで 1.1 秒でレンダリングする間、フェッチに約 2 秒かかることです。

私はデータベースの専門家ではありませんが、クエリが悪いのでしょうか、それとも私のように複数のテーブルにまたがる結合を使用しない方がよい場合もあります。データがこのように増加し続ける場合、結合クエリは速くなりますか、それとも両方のテストの読み込みが遅くなりますか?

そのクエリのすべての結合ポイントと WHERE フィールドにインデックスがあるので、それは問題ではないと思います。

キャッシングを検討しましたが、1 ミルの小さなデータのレコードでキャッシングを開始するには時期尚早だと感じています。

何かアドバイスはありますか?

domain -> has_many: channels(we use it for split testing)
channel -> has_many: visits, visitors (unique visits by ip), sales
product -> has_many: visits, visitors (unique visits by ip), sales

The query tries to get the domains which includes:
domain_name,
channels_count,
visits_count,
visitors_count,
sales_count and
products_count via the visits table


ACTUAL QUERY:
SELECT
    domains.id,
    domains.domain,
    COUNT(distinct kc.id) AS visits_count,
    COUNT(distinct kv.id) AS visits_count,
    COUNT(distinct kv.ip_address) AS visitors_count,
    COUNT(distinct kp.id) AS products_count,
    COUNT(distinct ks.id) AS sales_count
FROM
    domains
LEFT JOIN
    channels AS kc ON domains.id=kc.domain_id
LEFT JOIN
    visits AS kv ON kc.id=kv.channel_id
LEFT JOIN
    products AS kp ON kv.product_id=kp.id
LEFT JOIN
    sales AS ks ON kc.id=ks.channel_id
WHERE
    (domains.user_id=2)
GROUP BY
    domains.id
LIMIT 20
OFFSET 0


"QUERY PLAN"
"Limit  (cost=7449.20..18656.41 rows=20 width=50) (actual time=947.837..5093.929 rows=20 loops=1)"
"  ->  GroupAggregate  (cost=7449.20..20897.86 rows=24 width=50) (actual time=947.832..5093.845 rows=20 loops=1)"
"        ->  Merge Left Join  (cost=7449.20..17367.45 rows=282413 width=50) (actual time=947.463..4661.418 rows=99940 loops=1)"
"              Merge Cond: (domains.id = kc.domain_id)"
"              Filter: (kc.deleted_at IS NULL)"
"              ->  Index Scan using domains_pkey on domains  (cost=0.00..12.67 rows=24 width=30) (actual time=0.022..0.146 rows=21 loops=1)"
"                    Filter: ((deleted_at IS NULL) AND (user_id = 2))"
"              ->  Materialize  (cost=7449.20..16619.27 rows=58836 width=32) (actual time=947.430..4277.029 rows=99923 loops=1)"
"                    ->  Nested Loop Left Join  (cost=7449.20..16472.18 rows=58836 width=32) (actual time=947.424..3872.057 rows=99923 loops=1)"
"                          Join Filter: (kc.id = kv.channel_id)"
"                          ->  Index Scan using index_channels_on_domain_id on channels kc  (cost=0.00..12.33 rows=5 width=16) (actual time=0.008..0.090 rows=5 loops=1)"
"                          ->  Materialize  (cost=7449.20..10814.25 rows=58836 width=24) (actual time=189.470..536.745 rows=99923 loops=5)"
"                                ->  Hash Right Join  (cost=7449.20..10175.07 rows=58836 width=24) (actual time=947.296..1446.256 rows=99923 loops=1)"
"                                      Hash Cond: (ks.product_id = kp.id)"
"                                      ->  Seq Scan on sales ks  (cost=0.00..1082.22 rows=59022 width=8) (actual time=0.027..119.767 rows=59022 loops=1)"
"                                      ->  Hash  (cost=6368.75..6368.75 rows=58836 width=20) (actual time=947.213..947.213 rows=58836 loops=1)"
"                                            Buckets: 2048  Batches: 4  Memory Usage: 808kB"
"                                            ->  Hash Left Join  (cost=3151.22..6368.75 rows=58836 width=20) (actual time=376.685..817.777 rows=58836 loops=1)"
"                                                  Hash Cond: (kv.product_id = kp.id)"
"                                                  ->  Seq Scan on visits kv  (cost=0.00..1079.36 rows=58836 width=20) (actual time=0.011..135.584 rows=58836 loops=1)"
"                                                  ->  Hash  (cost=1704.43..1704.43 rows=88143 width=4) (actual time=376.537..376.537 rows=88143 loops=1)"
"                                                        Buckets: 4096  Batches: 4  Memory Usage: 785kB"
"                                                        ->  Seq Scan on products kp  (cost=0.00..1704.43 rows=88143 width=4) (actual time=0.006..187.174 rows=88143 loops=1)"
"Total runtime: 5096.723 ms"
4

1 に答える 1

3

100 万件のレコードは大したことではなく、5 つのテーブルを結合することはデータベースにとって簡単なタスクです。索引があるのはいいことですが、役に立ちますか? EXPLAIN ANALYZEはクエリについて何を教えてくれますか? そして、構成はどうですか?既定の構成は、開始するのに十分であり、すべての種類のワークロードに対して可能な限り最高のパフォーマンスを実現するための設定ではありません。

しかし、いくつかの結合について心配する必要はありません。リレーショナル データベースはそれに慣れています。

于 2012-07-26T04:44:37.760 に答える