1

PHP / MySQLでWebサイトをコーディングしていますが、stackoverflowタグ付けエンジンに似たものを実装したいと思います。DBに3つの関連するテーブルがあります:1。アイテム2.タグ3. ItemTagMap(タグをアイテムにマップします。n:nマッピング)

ここで、検索ページに、(現在のページだけでなく)検索結果全体のすべてのタグの個別のリストを表示して、ユーザーがそのタグリストからタグを追加/削除することで検索を「絞り込む」ことができるようにします。

問題は、それがDBに対するかなり重いクエリであり、さまざまな結果セット、したがってさまざまなタグセットをもたらす大量の検索要求が存在する可能性があることです。

これを効果的に実装する方法を知っている人はいますか?

4

3 に答える 3

8

時期尚早の最適化モードに入る前に、次のクエリ テンプレートを調べると役立つ場合があります。これは、考えられる最適化の有効性を測定するためのベースラインとして使用できます。

SELECT T.Tagid, TagInfo.TagName,  COUNT(*)
FROM Items I
JOIN Tags TagInfo ON TagInfo.TagId = T.TagId
JOIN ItemTagMap T  ON I.ItemId = T.ItemId 
--JOIN ItemTagMap T1 ON I.ItemId = T1.ItemId
WHERE I.ItemId IN
  (
      SELECT ItemId 
      FROM Items
      WHERE   -- Some typical initial search criteria
         Title LIKE 'Bug Report%'   -- Or some fulltext filter instead...
         AND  ItemDate > '02/22/2008'
         AND  Status = 'C'
  )
--AND T1.TagId = 'MySql'
GROUP BY T.TagId, TagInfo.TagName
ORDER BY COUNT(*) DESC

サブクエリは「駆動クエリ」、つまりエンドユーザーの最初の基準に対応するものです。(このクエリの詳細については、以下を参照してください。最適化されたフロー全体に複数回必要になる可能性があります)基準。これらは、最初の検索の一部として、または絞り込みによって、ユーザーが特定のタグを選択するときに必要になります。(これらの結合と where 句をサブクエリ内に配置する方が効率的な場合があります。これらについては以下で詳しく説明します)

ディスカッション... 「駆動クエリ」またはそのバリエーションは、2 つの異なる目的で必要です。

  • 1 は、関連するすべてのタグを列挙するために必要な ItemIdの完全なリストを提供します。

  • 2 を使用して、最初の N 個の ItemId 値 (N は表示ページ サイズ) を提供し、Item テーブルで Item 詳細情報を検索します。

完全なリストを並べ替える必要がないことに注意してください (または、別の順序で並べ替えたほうがよい場合があります)。そのため、2 番目のリストは、ユーザーの選択に基づいて並べ替える必要があります (たとえば、日付順で降順、またはタイトル順でアルファベット昇順)。 )。また、ソート順が必要な場合、クエリのコストは完全なリストを処理することを意味することに注意してください (SQL 自体による奇妙な最適化や非正規化を避けて、SQL はそのリストの最後のレコードを「見る」必要があります)。 、それらが一番​​上に属している場合は、ソートごとに)。

この後者の事実は、両方の目的でまったく同じクエリを使用することを支持し、対応するリストを一時テーブルに格納できます。一般的なフローは、上位 N 個の Item レコードをその詳細とともにすばやく検索し、これを一度にアプリケーションに返すことです。その後、アプリケーションは絞り込み用のタグのリストを ajax 形式で取得できます。このリストは、サブクエリが「select * from temporaryTable」に置き換えられた上記のクエリと同様のクエリで生成されます。SQL オプティマイザーがこのリストをソートすることを決定する可能性は高く (場合によっては)、2 番目に推測して明示的にソートするのではなく、そうさせましょう。

考慮すべきもう1つのポイントは、上記のようにではなく、「駆動クエリ」内のItemTagMapテーブルに結合することです。パフォーマンスのためにも、2 番目の目的 (アイテムのページの表示) に適したリストを作成するためにも、おそらくそうするのが最善です。

上記のクエリ/フローは、比較的控えめなハードウェアでも、かなりうまくスケーリングされる可能性があります。暫定的に 1/2 ミリオン以上のアイテムに、持続的なユーザー検索はおそらく 1 秒あたり最大 10 まで可能です。重要な要素の 1 つは、最初の検索基準の選択性です。

最適化のアイデア

  • [典型的な検索ケースとデータ統計に応じて] Item のフィールドの一部を ItemTagMap テーブルに持ってくる (実際には複製する) ことによって、非正規化することが理にかなっている場合があります。特に短いフィールドは「歓迎」される場合があります。
  • データが 100 万以上のアイテムに成長するにつれて、いくつかのタグの典型的な強い相関関係を利用することができます (例: SO では、PHP は多くの場合 MySql に付属していますが、正当な理由がないことがよくあります...)、さまざまなトリックを使用します。たとえば、「マルチタグ」TagId を導入すると、入力ロジックが少し複雑になる可能性がありますが、Map サイズを大幅に縮小することもできます。


-- '何も言わなかった! --
実際の要件と効果的なデータ統計プロファイルに照らして、適切なアーキテクチャと最適化を選択する必要があります...

于 2009-10-07T02:30:43.833 に答える
0

仮定:

  • アイテム(id);
  • 名前にインデックスを付けたタグ(id、name)。
  • ItemTag(item_id、tag_id)。

それから:

SELECT t.name
FROM Tag t
WHERE EXISTS (SELECT 1 FROM ItemTag WHERE item_id = 1234)
ORDER BY t.name

それについて集中的なことは何もありません。これは似ていますが、私の推測では遅くなります。

SELECT t.name
FROM Tag t
WHERE t.id IN (SELECT tag_id FROM ItemTag WHERE item_id = 1234)
ORDER BY t.name

これは、結合としても実行できます。

SELECT DISTINCT t.name
FROM Tag t
JOIN ItemTag i WHERE i.tag_id = t.id
WHERE i.item_id = 1234
ORDER BY t.name

最初のものの方が高速だと思いますが、SQLの場合は常にそうであるように、(十分なサイズのデータ​​セットで)テストする価値があります。

上記は、単一のアイテムのタグを一覧表示するために行われました。検索結果用のタグの複合セットが必要です。上記から難しいことではありませんが、検索結果を取得する方法によって異なります。

于 2009-10-07T01:42:48.813 に答える
0

DB呼び出しの数を最小限に抑えて、PHPに多大な労力を費やすことをお勧めします。

まず、DBからすべてのアイテムを選択します。

select * from items where (conditions);

次に、結果セットからすべてのIDの配列を作成します。

$ids = array();
foreach ($items as $item) {
    $ids[] = $item['id'];
}
$ids = implode(',' $ids);

次に、以前に取得したアイテムIDのすべてのItemTagMapsと関連するタグデータを選択します。

select map.item_id, t.id, t.name from tags t, item_tag_maps map where t.id = map.tag_id and map.item_id in ($ids);

これで、$ items配列をループするときに、一致するitem_id値がある限り、実行した2番目のSQLクエリから一致するすべてのタグを見つけることができます。

于 2009-10-07T01:45:00.933 に答える