web-dev-qa-db-ja.com

InnoDBテーブルが変更されたかどうかを確認する最速の方法

私のアプリケーションは非常にデータベースを集中的に使用します。現在、MySQL 5.5.19を実行してMyISAMを使用していますが、現在InnoDBに移行中です。残っている唯一の問題は、チェックサムのパフォーマンスです。

私のアプリケーションは約500-1000 CHECKSUM TABLEピーク時の1秒あたりのステートメント数。これは、クライアントGUIがデータベースの変更を常にポーリングしているためです(これは監視システムであるため、非常に応答性が高く高速である必要があります)。

MyISAMでは、テーブルの変更時に事前計算され、非常に高速なライブチェックサムがあります。ただし、InnoDBにはそのようなものはありません。そう、 CHECKSUM TABLEは非常に遅いです。

テーブルの最終更新時刻を確認できることを望んでいましたが、残念ながら、これはInnoDBでも利用できません。アプリケーションのパフォーマンスが大幅に低下することがテストで示されているため、今は行き詰まっています。

テーブルを更新するコード行が多すぎるため、アプリケーションにロジックを実装してテーブルの変更をログに記録することは問題外です。

InnoDBテーブルの変更を検出する高速な方法はありますか?

22
Jacket

私は解決策を見つけたと思います。しばらくの間、MySQLサーバーを置き換えるためにPercona Serverを探していましたが、これには十分な理由があると思います。

Perconaサーバーは、標準のMySQLサーバーでは利用できないINNODB_TABLE_STATSのような多くの新しいINFORMATION_SCHEMAテーブルを導入します。あなたがするとき:

SELECT rows, modified FROM information_schema.innodb_table_stats WHERE table_schema='db' AND table_name='table'

実際の行数とカウンターを取得します。 公式ドキュメント は、このフィールドについて次のように述べています。

変更された列の値が「行/ 16」または2000000000を超える場合、innodb_stats_auto_update == 1のときに統計の再計算が行われます。この値から統計の古さを推定できます。

したがって、このカウンターは時々折り返しますが、行数とカウンターのチェックサムを作成し、テーブルを変更するたびに固有のチェックサムを取得できます。例えば。:

SELECT MD5(CONCAT(rows,'_',modified)) AS checksum FROM information_schema.innodb_table_stats WHERE table_schema='db' AND table_name='table';

とにかく、サーバーをPerconaサーバーにアップグレードするつもりだったので、この境界は問題ではありません。何百ものトリガーを管理し、テーブルにフィールドを追加することは、開発が非常に遅いため、このアプリケーションの大きな問題です。

これはPHP関数です。使用するエンジンとサーバーが何であれ、テーブルをチェックサムできることを確認するために思いついた関数です。

function checksum_table($input_tables){
    if(!$input_tables) return false; // Sanity check
    $tables = (is_array($input_tables)) ? $input_tables : array($input_tables); // Make $tables always an array
    $where = "";
    $checksum = "";
    $found_tables = array();
    $tables_indexed = array();
    foreach($tables as $table_name){
        $tables_indexed[$table_name] = true; // Indexed array for faster searching
        if(strstr($table_name,".")){ // If we are passing db.table_name
            $table_name_split = explode(".",$table_name);
            $where .= "(table_schema='".$table_name_split[0]."' AND table_name='".$table_name_split[1]."') OR ";
        }else{
            $where .= "(table_schema=DATABASE() AND table_name='".$table_name."') OR ";
        }
    }
    if($where != ""){ // Sanity check
        $where = substr($where,0,-4); // Remove the last "OR"
        $get_chksum = mysql_query("SELECT table_schema, table_name, rows, modified FROM information_schema.innodb_table_stats WHERE ".$where);
        while($row = mysql_fetch_assoc($get_chksum)){
            if($tables_indexed[$row[table_name]]){ // Not entirely foolproof, but saves some queries like "SELECT DATABASE()" to find out the current database
                $found_tables[$row[table_name]] = true;
            }elseif($tables_indexed[$row[table_schema].".".$row[table_name]]){
                $found_tables[$row[table_schema].".".$row[table_name]] = true;
            }
            $checksum .= "_".$row[rows]."_".$row[modified]."_";
        }
    }

    foreach($tables as $table_name){
        if(!$found_tables[$table_name]){ // Table is not found in information_schema.innodb_table_stats (Probably not InnoDB table or not using Percona Server)
            $get_chksum = mysql_query("CHECKSUM TABLE ".$table_name); // Checksuming the old-fashioned way
            $chksum = mysql_fetch_assoc($get_chksum);
            $checksum .= "_".$chksum[Checksum]."_";
        }
    }

    $checksum = sprintf("%s",crc32($checksum)); // Using crc32 because it's faster than md5(). Must be returned as string to prevent PHPs signed integer problems.

    return $checksum;
}

次のように使用できます。

// checksum a signle table in the current db
$checksum = checksum_table("test_table");

// checksum a signle table in db other than the current
$checksum = checksum_table("other_db.test_table");

// checksum multiple tables at once. It's faster when using Percona server, because all tables are checksummed via one select.
$checksum = checksum_table(array("test_table, "other_db.test_table")); 

これにより、同じ問題を抱えている他の人々の手間が省かれることを願っています。

3
Jacket

テーブルmydb.mytableに対して、次のクエリを実行します。

SELECT update_time
FROM information_schema.tables
WHERE table_schema='mydb'
AND table_name='mytable';

過去5分間に変更されたテーブルを知りたい場合は、次のコマンドを実行します。

SELECT table_schema,table_name,update_time
FROM information_schema.tables
WHERE update_time > (NOW() - INTERVAL 5 MINUTE);

試してみる !!!

UPDATE 2011-12-21 20:04 EDT

私の雇用主(DB/Wwebホスティング会社)には、112,000のInnoDBテーブルを持つクライアントがあります。ピーク時にINFORMATION_SCHEMA.TABLESを読み取るのは非常に困難です。私には別の提案があります:

Innodb_file_per_tableを有効にしていて、すべてのInnoDBテーブルが.ibdファイルに格納されている場合、最後の更新の時刻(分単位)を確認する方法があります。

テーブルmydb.mytableについて、オペレーティングシステムで次の手順を実行します。

$ cd /var/lib/mysql/mydb
$ ls -l mytable.ibd | awk '{print $4,$5}'

このタイムスタンプはOSからのものです。これで間違いはありません。

UPDATE 2011-12-21 22:04 EDT[mysqld] innodb_max_dirty_pages_pct = 0;

これをmy.cnfに追加し、mysqlを再起動すると、すべてのInnoDBテーブルでバッファプールからの高速フラッシュが発生します。

再起動しないようにするには、次を実行します

mysql> SET GLOBAL innodb_max_dirty_pages_pct=0;

UPDATE 2013-06-27 07:15 EDT

ファイルの日付と時刻を取得する場合、lsには--time-styleオプションがあります。

$ cd /var/lib/mysql/mydb
$ ls -l --time-style="+%s" mytable.ibd | awk '{print $6}'

ファイルのタイムスタンプを UNIX_TIMESTAMP(NOW()) と比較できます。

15
RolandoMySQLDBA

Mysql v5.6 +に更新する必要があります。そのバージョンでは、innodbもチェックサムテーブルをサポートしています。 http://dev.mysql.com/doc/refman/5.6/en/checksum-table.html

それ以外の場合、理想的な解決策は、クライアントが常に結果をポーリングしていなかった場合です。代わりに、新しいデータと変更されたデータが利用可能な場合は、それをプッシュします。それはより速くなり、サーバーにかかる負荷は少なくなります。 WebベースのGUIを使用している場合は、APE http://ape-project.org/ または他の同様のプロジェクトを調べる必要があります。

1
Gamesh

主にテーブルに追加している場合は、更新の尺度としてAUTO_INCREMENTをフックできます。

SELECT `AUTO_INCREMENT` FROM `information_schema`.`tables` 
WHERE `table_schema` = DATABASE() AND `table_name` = 'YOUR_TABLE';

ただし、データベース内の何かを変更するたびにインクリメントされるMemcachedのカウンターのようなotsideソースを参照したいと思います。

1
sanmai

この回答はmysqlデータベースのバージョンやタイプとは関係ありません。更新ステートメントが変更を行っているかどうか、そして私のphpコードでこれを行うことを知りたかったのです。

  1. Mysqlのcurrent_timestampの値を取得するためにクエリする1つのレコードと1つのフィールドを持つダミーテーブルを作成しました。

  2. 更新されるデータテーブルにタイムスタンプフィールドを追加し、mysqlオプション「ON UPDATE CURRENT_TIMESTAMP」を使用しました

  3. #1と#2の比較

これは100%の時間では機能しませんが、私のアプリケーションでは、シンプルで優れたソリューションでした。これが誰かを助けることを願っています

0
Steve Padgett

あなたは次のことを試みることができます:

SELECT rows_changed
FROM information_schema.table_statistics
WHERE table_schema = 'mydb' AND table_name='mytable';

これは、テーブルを更新するたびに増加する数値を返します。これを追跡することで、変更を検出できます。

重要な注意:COMMITの後ではなく、UPDATEの直後に値が変更されます。そのため、完了しなかった別のトランザクション内で変更が行われた場合、変更が表示されない場合があります。

0
Romuald Brunet