web-dev-qa-db-ja.com

「SELECT COUNT(*)」は、where句がある場合でも遅い

私はMySQLで非常に遅いクエリを最適化する方法を見つけようとしています(私はこれを設計しませんでした):

SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391';
+----------+
| COUNT(*) |
+----------+
|  3224022 |
+----------+
1 row in set (1 min 0.16 sec)

それをフルカウントと比較する:

select count(*) from change_event;
+----------+
| count(*) |
+----------+
|  6069102 |
+----------+
1 row in set (4.21 sec)

ここで説明文は役に立たない:

 explain SELECT COUNT(*) FROM change_event me WHERE change_event_id > '1212281603783391'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: me
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 8
          ref: NULL
         rows: 4120213
        Extra: Using where; Using index
1 row in set (0.00 sec)

OK、カウントするには約400万のエントリが必要だとまだ考えていますが、ファイル内の行をそれより速くカウントできました! MySQLがこれほど長くかかっている理由がわかりません。

テーブル定義は次のとおりです。

CREATE TABLE `change_event` (
  `change_event_id` bigint(20) NOT NULL default '0',
  `timestamp` datetime NOT NULL,
  `change_type` enum('create','update','delete','noop') default NULL,
  `changed_object_type` enum('Brand','Broadcast','Episode','OnDemand') NOT NULL,
  `changed_object_id` varchar(255) default NULL,
  `changed_object_modified` datetime NOT NULL default '1000-01-01 00:00:00',
  `modified` datetime NOT NULL default '1000-01-01 00:00:00',
  `created` datetime NOT NULL default '1000-01-01 00:00:00',
  `pid` char(15) default NULL,
  `episode_pid` char(15) default NULL,
  `import_id` int(11) NOT NULL,
  `status` enum('success','failure') NOT NULL,
  `xml_diff` text,
  `node_digest` char(32) default NULL,
  PRIMARY KEY  (`change_event_id`),
  KEY `idx_change_events_changed_object_id` (`changed_object_id`),
  KEY `idx_change_events_episode_pid` (`episode_pid`),
  KEY `fk_import_id` (`import_id`),
  KEY `idx_change_event_timestamp_ce_id` (`timestamp`,`change_event_id`),
  KEY `idx_change_event_status` (`status`),
  CONSTRAINT `fk_change_event_import` FOREIGN KEY (`import_id`) REFERENCES `import` (`import_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

バージョン:

$ mysql --version
mysql  Ver 14.12 Distrib 5.0.37, for pc-solaris2.8 (i386) using readline 5.0

私が見逃している明らかなものはありますか? (はい、すでに「SELECT COUNT(change_event_id)」を試しましたが、パフォーマンスの違いはありません)。

43
Ovid

InnoDBはクラスター化された主キーを使用するため、主キーは個別のインデックスページではなく、データページの行とともに保存されます。範囲スキャンを実行するには、データページ内の潜在的に幅の広い行をすべてスキャンする必要があります。このテーブルにはTEXT列が含まれていることに注意してください。

私が試みる2つのこと:

  1. 実行optimize table。これにより、データページがソート順に物理的に保存されます。これにより、クラスター化された主キーの範囲スキャンが高速化される可能性があります。
  2. change_event_id列のみに追加の非プライマリインデックスを作成します。これにより、インデックスページにその列のコピーが保存され、スキャンがはるかに高速になります。作成後、説明計画をチェックして、新しいインデックスを使用していることを確認します。

(おそらくchange_event_idカラムをbigint nsignedにしたい場合は、ゼロから増分します)

46
ʞɔıu

ここに私が提案するいくつかのものがあります:

  • 列を「bigint」から「int unsigned」に変更します。このテーブルに42億件以上のレコードがあると本当に期待していますか?そうでない場合は、余分なフィールドのスペース(および時間)を無駄にしています。 MySQLインデックスは、より小さなデータ型でより効率的です。

  • OPTIMIZE TABLE 」コマンドを実行し、その後クエリが高速かどうかを確認します。

  • また、IDフィールドに応じて テーブルのパーティション分割 を検討することもできます。特に、古いレコード(ID値が低い)が時間の経過とともに関連性が低くなる場合。パーティションテーブルは、多くの場合、1つの巨大な非パーティションテーブルよりも高速に集計クエリを実行できます。


編集:

このテーブルをより詳しく見ると、行が挿入されているが変更されていないロギングスタイルのテーブルのように見えます。

もしそうなら、InnoDBストレージエンジンが提供するすべてのトランザクションの安全性を必要としないかもしれませんし、 MyISAMへの切り替え で逃げることができるかもしれません。

14
benjismith

IPジオロケーションデータベースでは、これまでこのような動作に遭遇しました。いくつかのレコードを超えると、範囲ベースのクエリのインデックスから利点を得るMySQLの機能が明らかに失われます。ジオロケーションDBでは、インデックスを使用するのに十分な妥当なチャンクにデータをセグメント化することで処理しました。

5
chaos

インデックスの断片化の程度を確認してください。私の会社では、インデックスを破棄する夜間のインポートプロセスがあり、時間が経つにつれてデータアクセス速度に大きな影響を与える可能性があります。たとえば、インデックスを最適化してから3分かかった後、1日実行するのに2時間かかったSQLプロシージャがありました。 SQL Server 2005を使用して、MySQLでこれをチェックできるスクリプトを探します。

更新:このリンクを確認してください: http://dev.mysql.com/doc/refman/5.0/en/innodb-file-defragmenting.html

3

analyze table_name "そのテーブルで-インデックスが最適でなくなっている可能性があります。

多くの場合、「show index from table_name "。カーディナリティー値がNULLの場合、再分析を強制する必要があります。

1
Alnitak

MySQLは、実際にカウントするためにインデックスデータからすべてのレコード/値を読み取る必要があるため、最初に「どこを使用する」と言います。 InnoDbでは、その4 milレコード範囲を「取得」してカウントします。

さまざまなトランザクション分離レベルを試す必要がある場合があります。 http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-uncommitted

どちらが優れているかを確認してください。

MyISAMでは高速ですが、集中的な書き込みモデルではロックの問題が発生します。

1

「counters」テーブルを作成し、カウントするテーブルに「create row」/「delete row」トリガーを追加します。トリガーは、挿入/削除ごとに「カウンター」テーブルのカウント値を増減する必要があるため、必要なたびにトリガーを計算する必要はありません。

カウンターをキャッシュすることでアプリケーション側でこれを実現することもできますが、これには挿入/削除のたびに「カウンターキャッシュ」をクリアする必要があります。

いくつかの参照については、これをご覧ください http://pure.rednoize.com/2007/04/03/mysql-performance-use-counter-tables/

0
knoopx