20

私は2つのテーブルを持っています:

CREATE TABLE `articles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(1000) DEFAULT NULL,
  `last_updated` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `last_updated` (`last_updated`),
) ENGINE=InnoDB AUTO_INCREMENT=799681 DEFAULT CHARSET=utf8 

CREATE TABLE `article_categories` (
  `article_id` int(11) NOT NULL DEFAULT '0',
  `category_id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`article_id`,`category_id`),
  KEY `category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

これは私のクエリです:

SELECT a.*
FROM
    articles AS a,
    article_categories AS c
WHERE
    a.id = c.article_id
    AND c.category_id = 78
    AND a.comment_cnt > 0
    AND a.deleted = 0
ORDER BY a.last_updated
LIMIT 100, 20

そしてそれのEXPLAINため:

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: a
         type: index
possible_keys: PRIMARY
          key: last_updated
      key_len: 9
          ref: NULL
         rows: 2040
        Extra: Using where
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: c
         type: eq_ref
possible_keys: PRIMARY,fandom_id
          key: PRIMARY
      key_len: 8
          ref: db.a.id,const
         rows: 1
        Extra: Using index

ソートには最初のテーブルのフル インデックス スキャンをlast_updated使用しますが、結合には y インデックスを使用しません (type: index説明内)。これは非常に頻繁なクエリであるため、パフォーマンスが非常に悪く、データベース サーバー全体が停止します。

を使用してテーブルの順序を逆にしてみましSTRAIGHT_JOINたが、これにより が得られfilesort, using_temporary、これはさらに悪いことです。

結合とソートに同時に mysql にインデックスを使用させる方法はありますか?

===更新===

私はこれで本当に絶望的です。たぶん、ある種の非正規化がここで役立つでしょうか?

4

6 に答える 6

20

カテゴリが多数ある場合、このクエリは効率的ではありません。では、1 つのインデックスで 2 つのテーブルを同時にカバーすることはできませんMySQL

非正規化を行う必要があります: add last_updatedhas_commentsおよびdeletedinto article_categories:

CREATE TABLE `article_categories` (
  `article_id` int(11) NOT NULL DEFAULT '0',
  `category_id` int(11) NOT NULL DEFAULT '0',
  `last_updated` timestamp NOT NULL,
  `has_comments` boolean NOT NULL,
  `deleted` boolean NOT NULL,
  PRIMARY KEY (`article_id`,`category_id`),
  KEY `category_id` (`category_id`),
  KEY `ix_articlecategories_category_comments_deleted_updated` (category_id, has_comments, deleted, last_updated)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

次のクエリを実行します。

SELECT  *
FROM    (
        SELECT  article_id
        FROM    article_categories
        WHERE   (category_id, has_comments, deleted) = (78, 1, 0)
        ORDER BY
                last_updated DESC
        LIMIT   100, 20
        ) q
JOIN    articles a
ON      a.id = q.article_id

もちろんarticle_categories、 で関連する列を更新するたびに、同様に更新する必要がありますarticle。これはトリガーで実行できます。

has_commentsがブール値であることに注意してください。これにより、等価述語を使用して、インデックスに対して単一の範囲スキャンを行うことができます。

LIMITまた、サブクエリに入ることにも注意してください。これによりMySQL、デフォルトでは使用されない後期行ルックアップが使用されます。なぜパフォーマンスが向上するのかについては、私のブログのこの記事を参照してください。

SQL Server を使用している場合は、クエリに対してインデックス可能なビューを作成できます。これにより、基本的article_categoriesに、サーバーによって自動的に維持される、追加のフィールドを含む非正規化されたインデックス付きコピーが作成されます。

残念ながら、MySQLはこれをサポートしていないため、このようなテーブルを手動で作成し、追加のコードを記述してベース テーブルとの同期を維持する必要があります。

于 2013-05-07T17:20:46.060 に答える
2

まず、MySQL がインデックスを使用する 3 つの方法の記事を読むことをお勧めします。

基本を理解したら、この特定のクエリを最適化できます。

MySQL は順序付けにインデックスを使用できません。インデックスの順序でデータを出力するだけです。MySQL は結合にネストされたループを使用するため、並べ替えたいフィールドは結合の最初のテーブルにある必要があります (EXPLAIN の結果に結合の順序が表示され、特定のインデックスを作成することで影響を与えることができます)。 ) 必要なインデックスを強制することにより)。

もう 1 つの重要なことは、並べ替える前に、フィルター処理されたすべての行のすべての列をatable からフェッチし、おそらくそれらのほとんどをスキップすることです。必要な行 ID のリストを取得し、それらの行のみをフェッチする方がはるかに効率的です。

これを機能させるには(deleted, comment_cnt, last_updated)、 tableaにカバリング インデックスが必要です。これで、次のようにクエリを書き直すことができます。

SELECT *
FROM (
  SELECT a.id
  FROM articles AS a,
  JOIN article_categories AS c
    ON a.id = c.article_id AND c.category_id = 78
  WHERE a.comment_cnt > 0 AND a.deleted = 0
  ORDER BY a.last_updated
  LIMIT 100, 20
) as ids
JOIN articles USING (id);

PS table のテーブル定義には列aが含まれていません;)comment_cnt

于 2013-05-07T16:47:42.520 に答える
2

インフルエンスMySQLを使用して、KEYSまたはINDEXESを使用できます

為に

  • 注文するか、
  • グループ化、または
  • 加入

詳細については、このリンクをたどってください。これを結合に使用するつもりでした (つまりUSE INDEX FOR JOIN (My_Index)、期待どおりに動作しませんでした。FOR JOIN部分を削除すると、クエリが 3.5 時間以上から 1 ~ 2 秒に大幅に高速化されました。単純に、MySQL が正しいインデックスを使用するように強制されたためです。

于 2013-10-23T14:59:23.807 に答える
2

非カバー インデックスの使用にはコストがかかります。行ごとに、主キーを使用して、カバーされていない列をベース テーブルから取得する必要があります。articlesそこで、まずカバーリングのインデックスを作成しようと思います。これは、インデックスが有用であることを MySQL クエリ オプティマイザーに納得させるのに役立つ場合があります。例えば:

KEY IX_Articles_last_updated (last_updated, id, title, comment_cnt, deleted),

それでも解決しない場合は、次のことを試してみてくださいFORCE INDEX

SELECT  a.*
FROM    article_categories AS c FORCE INDEX (IX_Articles_last_updated)
JOIN    articles AS a FORCE INDEX (PRIMARY)
ON      a.id = c.article_id
WHERE   c.category_id = 78
        AND a.comment_cnt > 0
        AND a.deleted = 0
ORDER BY 
        a.last_updated
LIMIT   100, 20

主キーを適用するインデックスの名前は常に「primary」です。

于 2013-05-05T15:00:46.937 に答える