mysql的row_count()只返回“真改动”行数,因innodb执行update时会跳过新旧值完全一致的行(含null对null),不写入也不记日志;它反映实际变更而非匹配结果,且必须紧接update调用,否则被后续语句覆盖。

MySQL 的 ROW_COUNT() 为什么只返回“真改动”的行数?
因为 MySQL 的 InnoDB 引擎在执行 UPDATE 时做了写前比对:如果某行 WHERE 匹配成功,但 SET 子句中所有字段的新旧值完全一致(包括 NULL 对 NULL、空字符串对空字符串),引擎会跳过物理写入,也不计入 undo log 和 binlog 变更记录。ROW_COUNT() 捕获的就是这个最终写入行为的结果,不是匹配逻辑的结果。
常见误判点:
-
WHERE status = 'active'匹配到 10 行,但其中 7 行status原值已是'active'→ROW_COUNT()返回 3,不是 10 -
UPDATE t SET name = TRIM(name)在 name 末尾有空格的行上会生效;但若原值无空格,TRIM(name)不变 → 这些行不计入 - 触发器里
SET NEW.status = OLD.status会让所有行“白更新” →ROW_COUNT()为 0
为什么必须紧接在 UPDATE 后调用 ROW_COUNT()?
该函数读取的是当前会话中“上一条语句”的影响计数,且该计数会被任何后续语句立即覆盖——哪怕是一条 SELECT 1、IF @x > 0 或注释后的空行,在存储过程或客户端脚本中都可能破坏链路。
安全做法:
- 在命令行或 SQL 脚本中,写成:
UPDATE ...; SELECT ROW_COUNT();(两语句在同一执行批次) - 在存储过程中,立刻赋值给变量:
SET @affected = ROW_COUNT(); - 在 PHP mysqli 中,不要用
$conn->query("UPDATE ...; SELECT ROW_COUNT()")—— 多语句需启用MYSQLI_MULTI_STATEMENTS,否则只执行第一条
PHP/Python/Java 等语言里,affected_rows 或 executeUpdate() 和 ROW_COUNT() 是一回事吗?
大多数情况下是,但有关键例外:
- PHP mysqli 默认开启
MYSQLI_CLIENT_FOUND_ROWS时,$stmt->affected_rows返回的是匹配行数(类似 PostgreSQL),不是变更行数 —— 必须显式关闭:mysqli_options($conn, MYSQLI_OPT_FOUND_ROWS, false) - Python PyMySQL 的
cursor.rowcount行为与ROW_COUNT()一致,但若autocommit=False且未提交,某些 1.0.x 版本会返回-1(不是错误,是未就绪状态) - Java JDBC 的
PreparedStatement.executeUpdate()返回值 ≡ROW_COUNT(),但若你误用了execute(),返回值不可靠
跨数据库迁移时,ROW_COUNT() 不能直接替换 @@ROWCOUNT 或 GET DIAGNOSTICS
语义根本不同:SQL Server 的 @@ROWCOUNT 和 PostgreSQL 的 GET DIAGNOSTICS row_count = ROW_COUNT 都统计 WHERE 匹配并执行了 SET 的所有行,不管值是否真变。MySQL 的 ROW_COUNT() 统计的是实际发生磁盘/内存变更的行。
这意味着:
- 把 MySQL 应用迁到 SQL Server 时,原来靠
ROW_COUNT() == 0判断“目标不存在”的逻辑会失效 —— 因为即使值没变,@@ROWCOUNT也可能 > 0 - 反之,SQL Server 迁 MySQL 时,原来依赖“匹配即成功”的业务(比如幂等更新日志)可能漏记,因为
ROW_COUNT()为 0 - 真正可靠的判断方式不是只看行数,而是执行后
SELECT回查关键字段值是否符合预期










