web-dev-qa-db-ja.com

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

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

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

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

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

7
mpen

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
12
Aaron Brown

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

ただし、このデータベースとINFORMATION_SCHEMA間の同期の問題は注意が必要です。

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

別のアイデアは、MySQLプロキシを使用してALTER TABLEステートメント(およびCREATEDROPCREATE INDEX、および必要な情報を変更するその他のステートメント)をキャッチして同期することです。これらのステートメントが成功した後の複製情報スキーマ。


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

SELECT * FROM TableName WHERE FALSE ;

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

3
ypercubeᵀᴹ

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

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

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

[...]

  • SHOW COLUMNSおよびDESCRIBEステートメントは一部の列のタイプとしてBLOBを使用するため、結果に使用される一時テーブルはディスク上のテーブルです。

これは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倍高速に実行されます。

2
Aya

@ 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サーバーを使用することです。

1
RolandoMySQLDBA