MySQLで「列を表示」を高速化するにはどうすればよいですか?


7

私のアプリケーションは、特定のテーブルの「列の表示」の実行に依存しています。実行には約60ミリ秒かかりますが、他のすべてのクエリには1ミリ秒未満かかります。information_schema直接のクエリはさらに遅くなります。

データベースには約250のデータベースが含まれ、データベースごとに100から200のテーブル(合計で約2万テーブル)があります。

  • これらの操作が非常に遅い理由を知るにはどうすればよいですか?
  • 実行を高速化したり、SQL側にキャッシュしたりするために変更できる設定はありますか?

(アプリケーションは、ページの読み込みごとにこのようなクエリを約14回実行します。このレガシーコードをクリーンアップする必要があることは承知していますが、長期的な修正に取り組んでいる間、可能なオプションを探しています。)


1
興味深いことに、どのシナリオで60msが遅すぎてテーブルの列を検査できないのでしょうか。それはあなたがすべての要求をしているべきものではありません

1
列を表示するとはどういう意味ですか?テーブルから列名を削除するか、列全体を印刷しますか?その名前の場合、一度取得してアプリケーションに格納しないでください。それが不可能な場合は、テーブルに基づいてすべての列を保持する別のテーブルを作成しないのはなぜですか。

@Jaitsu:いいえ、それは私たちがしなければならないことではありませんが、それはそうです。レガシーコード。クリーンアップして適切に実行する時間ができるまでは、スピードアップできるかどうかを確認したいと思います。私はこれらの約14を持っているすべてのページの読み込みを実行します。

@FlorinStingaciu:はい、列名。それらを別のテーブルに入れると速度が上がる可能性がありますが、同期が取れなくなり、テーブルに直接問い合わせるという目的全体が無効になります。

1
@マット:悪い考えではありません。dbaへの移行に投票しました。

回答:


12

MySQL は、INFORMATION_SCHEMAテーブルにアクセスする特定の操作のテーブル統計を再計算します(SHOW COLUMNSクエリの便利なエイリアスにすぎませんINFORMATION_SCHEMA.COLUMNS)。innodb_stats_on_metadataをfalseに設定すると、テーブルからメタデータを要求したときにこの再計算が行われなくなります。

SET GLOBAL innodb_stats_on_metadata=0;

以下を追加します my.cnf

[mysqld]
innodb_stats_on_metadata = 0

私は実際にMyISAMを使用していることを述べたはずです。とにかくこれを設定しようとしましたが、メリットはありませんでした。
mpen 2012

ALTER TABLE foo ENGINE = InnoDBを検討しましたか?:) MyISAMを使用する正当な理由はありますか?
アーロンブラウン

主にレガシーの理由だと思います。私がそれを試した場合、どうなるか心配です。すべてのFKが整列するかどうかは不明です。もう少し考えてみます。
mpen

すべてのInnoDBデータベースでこの状況に直面している人は誰でもこの情報を必要とするため、この回答の@AaronBrown +1。
RolandoMySQLDBA 2012

1
[mysqld]そこに置くための+1 。この設定がmysqldの下にあることは多くの人にとって明白かもしれませんが、この質問をする人にとっては明白ではないかもしれません。ちなみに、これSELECT COUNT(*)は私のinformation_schemaテーブルの1つで1 分以上から6秒にスピードアップしました。まだ遅いですが、大きな改善です。
Buttle Butkus 2013

3

INFORMATION_SCHEMAレプリケートとしてテーブル(または必要なテーブルのみ)を持つデータベースを作成することをお勧めします。それらを適切にインデックス付けすると、パフォーマンスが向上します。

このデータベース間の同期の問題INFORMATION_SCHEMAはトリッキーですが。

これらのテーブルを1時間ごとまたは5分ごとに同期するプロシージャを使用できます(テーブルの構造はどのくらいの頻度で変更されますか?)。

別のアイデアは、MySQLプロキシを使用してすべてのALTER TABLEステートメント(およびCREATEand DROPand CREATE INDEXand and and other other commands modify your information)をキャッチし、これらのステートメントが成功した後に複製された情報スキーマを同期することです。


列名だけが必要で、データ型、長さ、使用可能なインデックスなどの他の情報が必要ない場合は、SHOW COLUMNS1行のみを返す(高速)クエリの使用を、LIMIT 1以下のいずれかで、またはまったく使用しないで置き換えることができますLIMIT 0

SELECT * FROM TableName WHERE FALSE ;

の使用に対する一般的なアドバイスにもかかわらずSELECT *、これは他に何も役に立たない正当な場合かもしれません。(それ以外はすべて*、エラーになる可能性があります!)


2

この特定のケースでは、私はそれINFORMATION_SCHEMAが赤いニシンだと思います。私自身のSHOW COLUMNSパフォーマンステストから、innodb_stats_on_metadata変数はMyISAMまたはInnoDBテーブルのいずれにも影響を与えないようです。

ただし、MySQL 5.0マニュアルから ...

一部の条件では、メモリ内の一時テーブルを使用できません。その場合、サーバーは代わりにディスク上のテーブルを使用します。

[...]

  • 文を使用いくつかの列の型として、このような結果のために使用される一時テーブルはディスク上の表です。SHOW COLUMNSDESCRIBEBLOB

これは、MySQL 5.5の時点ではマニュアルから削除されているようですが、そのバージョンでも適用されるようです...

mysql> SHOW VARIABLES LIKE 'version';
+---------------+-------------------------+
| Variable_name | Value                   |
+---------------+-------------------------+
| version       | 5.5.41-0ubuntu0.14.04.1 |
+---------------+-------------------------+
1 row in set (0.00 sec)

mysql> SHOW STATUS LIKE 'Created_tmp_disk_tables';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 0     |
+-------------------------+-------+
1 row in set (0.00 sec)

mysql> SHOW COLUMNS FROM mysql.user;
[...snip...]
42 rows in set (0.00 sec)

mysql> SHOW STATUS LIKE 'Created_tmp_disk_tables';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 1     |
+-------------------------+-------+
1 row in set (0.00 sec)

クエリ結果セットで返されるフィールド情報には、によって返されるものと同じ情報が含まれているSHOW COLUMNSため、SELECT * FROM my_table LIMIT 0はクエリごとにディスク上の一時テーブルを作成しなくても同じことを実現する必要があります。

PHPでフィールド名を取得する簡単な例...

$mysql = new mysqli('localhost', 'root', '', 'my_database');
$field_names = array();
$result = $mysql->query("SELECT * FROM my_table LIMIT 0");
$fields = $result->fetch_fields();
foreach ($fields as $fields)
{
    $field_names[] = $field->name;
}
var_dump($field_names);

この方法でフィールド情報を取得すると、デコードが少し厄介になります。MYSQL_FIELDデータ型とフラグを引き出すには、基礎となる構造の説明を参照する必要がありますが、私のシステムでは約7倍速く実行されます。


1

@yerpcubeの回答(+1)の最初の提案が好きですが、何か提案したいです

  • ポート3307に別のデータベースインスタンスを作成する
  • mysqldumpは、次のオプションを使用して本番データベースをSQLテキストファイルに保存します。
    • --no-data
    • --routines
    • --triggers
    • --all-databasesまたは--databases、必要なデータベースのリストが後に続きます
  • SQLテキストファイルをポート3307 MySQLインスタンスにロードします。

したがって、mysqldumpは次のようになります。

mysqldump --no-data --routines --triggers --all-databases > ImportFile.sql

それでおしまい。今後は、このポート3307データベースインスタンスに接続して、スキーマ関連のクエリを実行するだけで済みます。変更される本番データベースのテーブルを知っている場合は、mysqldumpを本番環境からmysqldumpして、ポート3307インスタンスに再度リロードします。

警告:mysqlインスタンスを本番環境と同じマシンにインストールする場合は、必ずそのインスタンスに接続してください

mysql -u... -p... -h127.0.0.1 -P3307 < ImportFile.sql

実行すると

mysql -u... -p... -P3307 < ImportFile.sql

ホース生産になります。ので注意してください !!!!

別の方法は、別のDBサーバーを使用することです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.