【问题标题】:MySQL - Fastest way to check if data in InnoDB table has changedMySQL - 检查 InnoDB 表中的数据是否已更改的最快方法
【发布时间】:2012-01-28 10:17:58
【问题描述】:

我的应用程序是数据库密集型的。目前,我正在运行 MySQL 5.5.19 并使用 MyISAM,但我正在迁移到 InnoDB。剩下的唯一问题是校验和性能。

我的应用程序在高峰时间每秒执行大约 500-1000 条“CHECKSUM TABLE”语句,因为客户端 GUI 不断轮询数据库以进行更改(它是一个监控系统,因此必须非常灵敏且快速)。

使用 MyISAM,可以在修改表时预先计算实时校验和,并且速度非常快。但是,在 InnoDB 中没有这样的东西。所以,CHECKSUM TABLE 很慢……

我希望能够检查表的最后更新时间,不幸的是,这在 InnoDB 中也不可用。我现在卡住了,因为测试表明应用程序的性能急剧下降......

更新表的代码行太多了,所以在应用程序中实现逻辑来记录表更改是不可能的......

数据库生态系统由一个主节点和 3 个从节点组成,因此本地文件检查不是一种选择。 我想到了一种模拟校验和缓存的方法 - 一个包含两列的查找表 - table_name、校验和,并在表发生更改时使用触发器更新该表,但我有大约 100 个表要监视,这意味着每个表有 3 个触发器= 300 个触发器。难以维护,我不确定这是否会再次成为性能问题。

那么有什么 FAST 方法可以检测 InnoDB 表的变化吗?

谢谢!

【问题讨论】:

  • 您可能想查看this question/answer(如果您还没有这样做的话)。
  • 啊,好吧,这也是我的主题,但我不认为这是同一个网站...对不起,我是新来的。
  • 感谢您的链接,所以看来我在触发器的正确​​轨道上,虽然看起来很丑陋......很遗憾 InnoDB 缺乏这样的机制......
  • 您可以添加last_update TIMESTAMP 列(默认情况下应自动存储插入或上次更新的时间戳)并运行SELECT MAX(last_update) FROM tableX 而不是校验和表语句

标签: mysql triggers innodb checksum


【解决方案1】:

最简单的方法是添加一个 TIMESTAMP 类型的可为空列,触发器为:ON UPDATE CURRENT_TIMESTAMP

因此,插入不会更改,因为该列接受空值,您可以通过以下方式仅选择新的和更改的列:

SELECT * FROM `table` WHERE `mdate` > '2011-12-21 12:31:22'

每次更新一行时,此列都会自动更改。

这里有更多信息:http://dev.mysql.com/doc/refman/5.0/en/timestamp.html

要查看已删除的行,只需创建一个触发器,它将每次删除记录到另一个表:

DELIMITER $$
CREATE TRIGGER MyTable_Trigger
AFTER DELETE ON MyTable
FOR EACH ROW
BEGIN
    INSERT INTO MyTable_Deleted VALUES(OLD.id, NOW());
END$$

【讨论】:

  • 同意,但这种方式的问题是我无法检测到表中的删除(除非它是最后一行)。
  • 查看 AFTER DELETE 后的触发器(答案已在上面编辑)。
【解决方案2】:

我想我已经找到了解决方案。有一段时间我一直在寻找 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")); 

我希望这可以为遇到同样问题的其他人省去一些麻烦。

【讨论】:

猜你喜欢
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
  • 2014-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多