我想我已经找到了解决方案。有一段时间我一直在寻找 Percona Server 来替换我的 MySQL 服务器,现在我认为这是有充分理由的。
Percona 服务器引入了许多新的 INFORMATION_SCHEMA 表,例如 INNODB_TABLE_STATS,这在标准 MySQL 服务器中是不可用的。
当你这样做时:
SELECT rows, modified FROM information_schema.innodb_table_stats WHERE table_schema='db' AND table_name='table'
您会得到实际的行数和一个计数器。 Official documentation 对此字段表示如下:
如果修改列的值超过“rows / 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"));
我希望这可以为遇到同样问题的其他人省去一些麻烦。