MySQL affectedRows 计算逻辑

对于upsert操作计算特殊一点

  • 如果执行UPDATE会当作2来计算,如果执行INSERT会当作1来算,传给mybatis的返回值也是这样
  • 存在MySQL版本差异,但是都是延续旧版本的计算逻辑
  • 可能会根据JDBC的连接参数发生变化,比如设置 jdbc:mysql://localhost:3306/db?useAffectedRows=true ,执行UPDATE操作时只会返回1(表示"实际更新的行数")

在 MySQL 中执行 UPSERT(INSERT ... ON DUPLICATE KEY UPDATE 操作时,如果出现 更新 1 条数据但返回值(affectedRows)为 2 的情况,这通常与 MySQL 的 affectedRows 计算逻辑有关。以下是详细解释:


1. UPSERT 的 affectedRows 计算规则

MySQL 的 INSERT ... ON DUPLICATE KEY UPDATE 语句的返回值(affectedRows)遵循以下规则:

  • 如果插入成功(无主键/唯一键冲突)
    • affectedRows = 1(表示插入了 1 条新记录)。
  • 如果更新成功(发生主键/唯一键冲突)
    • MySQL 5.7 及以下版本
      • affectedRows = 2(表示"1 条记录被影响,但更新了 2 行")。
      • 这里的 21(原始行) + 1(更新后的行),但实际只更新了 1 条记录。
    • MySQL 8.0+
      • 默认情况下,affectedRows = 2(与 5.7 相同)。
      • 但如果启用 CLIENT_FOUND_ROWS 选项(JDBC 驱动可能配置),则返回 1(表示"找到了 1 行")。

2. 为什么更新 1 条数据却返回 2

(1) MySQL 的 affectedRows 计算方式

  • 对于 ON DUPLICATE KEY UPDATE,MySQL 的 affectedRows 计算逻辑是:

    • 如果发生唯一键冲突
      • 先删除旧行(affectedRows += 1)。
      • 再插入新行(affectedRows += 1)。
      • 因此,即使只更新了 1 条记录,返回值也可能是 2
  • 示例

    复制代码

    sql

    复制代码
    `INSERT INTO table (id, name) VALUES (1, 'A') 
    ON DUPLICATE KEY UPDATE name = 'B';`
    • 如果 id=1 已存在:
      • MySQL 5.7 及以下:返回 2(1 删除 + 1 插入)。
      • MySQL 8.0+:默认返回 2,但某些驱动可能返回 1

(2) JDBC 驱动的影响

  • 不同版本的 MySQL JDBC 驱动(如 mysql-connector-java)可能对 affectedRows 的处理方式不同:
    • 默认情况下,驱动可能返回 MySQL 原生的 2
    • 如果启用了 useAffectedRows=false(默认值),则返回 2
    • 如果启用 useAffectedRows=true,则返回 1(表示"实际更新的行数")。

3. 如何验证?

(1) 直接在 MySQL 命令行测试

复制代码

sql

复制代码
`CREATE TABLE test (
    id INT PRIMARY KEY,
    name VARCHAR(50)
);

-- 第一次插入(affectedRows = 1)
INSERT INTO test (id, name) VALUES (1, 'A');

-- 第二次插入(触发 UPSERT,affectedRows = 2)
INSERT INTO test (id, name) VALUES (1, 'B') ON DUPLICATE KEY UPDATE name = 'B';`
  • 在 MySQL 5.7 及以下,第二次执行返回 2
  • 在 MySQL 8.0+,默认返回 2,但某些配置可能返回 1

(2) 检查 JDBC 驱动配置

  • 在 MyBatis 的 JDBC URL 中,检查是否有 useAffectedRows 参数:

    复制代码

    properties

    复制代码
    `jdbc:mysql://localhost:3306/db?useAffectedRows=true`
    • useAffectedRows=true → 返回 1(实际更新的行数)。
    • useAffectedRows=false(默认) → 返回 2(MySQL 原生逻辑)。

4. 如何解决?

(1) 接受 MySQL 的默认行为

  • 如果业务不依赖 affectedRows 的精确值,可以忽略此差异。

(2) 修改 JDBC 驱动配置

  • 在连接 URL 中添加 useAffectedRows=true

    复制代码

    properties

    复制代码
    `jdbc:mysql://localhost:3306/db?useAffectedRows=true`
    • 这样,ON DUPLICATE KEY UPDATE 返回 1(表示"实际更新的行数")。

(3) 使用 ROW_COUNT() 函数

  • 在 SQL 中显式计算影响的行数:

    复制代码

    sql

    复制代码
    `INSERT INTO test (id, name) VALUES (1, 'A') 
    ON DUPLICATE KEY UPDATE name = 'B';
    SELECT ROW_COUNT(); -- 返回 1(MySQL 8.0+)或 2(5.7 及以下)`

(4) 升级 MySQL 或 JDBC 驱动

  • MySQL 8.0+ 的某些驱动版本可能优化了 affectedRows 的计算方式。

5. 总结

场景 MySQL 5.7 及以下 MySQL 8.0+ useAffectedRows=true
INSERT ON DUPLICATE KEY UPDATE 返回 2 默认返回 2 返回 1
业务影响 可能误导 可能优化 更符合预期

关键结论

  1. ON DUPLICATE KEY UPDATE 返回 2 是 MySQL 的默认行为,表示"1 行被删除 + 1 行被插入"。
  2. JDBC 驱动配置(useAffectedRows)可以改变返回值 ,使其更符合直觉(返回 1)。
  3. 如果业务依赖 affectedRows 的精确值 ,建议显式配置 useAffectedRows=true 或调整逻辑。

6. 最佳实践

  • 在 MyBatis 中配置 JDBC URL

    复制代码

    properties

    复制代码
    `jdbc:mysql://localhost:3306/db?useAffectedRows=true`
  • 在代码中处理可能的差异

    复制代码

    java

    复制代码
    `int affectedRows = sqlSession.update("upsertData", params);
    if (affectedRows == 1 || affectedRows == 2) {
        // 视为成功(1=更新,2=MySQL 默认行为)
    }`
  • 使用 MySQL 8.0+ 并检查驱动版本,确保行为一致。

这样,即使执行 UPSERT 时返回 2,也能正确处理业务逻辑。


ON DUPLICATE KEY UPDATE 的实际执行逻辑并非先删除后插入,而是直接更新已存在的记录。以下是详细分析:

1. 执行逻辑解析

  • 无冲突时
    • 执行标准 INSERT 操作,插入新行,affectedRows = 1
  • 存在冲突时 (主键或唯一键重复):
    • 直接更新指定字段,而非删除旧记录再插入新记录。

    • 例如:

      复制代码

      sql

      复制代码
      `INSERT INTO table (id, name) VALUES (1, 'A') 
      ON DUPLICATE KEY UPDATE name = 'B';`
      • 如果 id=1 已存在,MySQL 会直接更新 name 字段为 'B'不会删除旧记录

2. 与 REPLACE INTO 的区别

  • REPLACE INTO
    • 先删除旧记录(触发 DELETE 操作),再插入新记录。
    • 可能导致自增主键不连续,且可能触发外键约束或触发器。
  • ON DUPLICATE KEY UPDATE
    • 直接更新字段,不删除旧记录,因此不会影响自增主键或触发额外操作。

3. 为什么返回值可能是 2

  • MySQL 5.7 及以下版本
    • 返回 2 是历史遗留行为,表示"1 行被影响(更新)"。
    • 这里的 21(原始行) + 1(更新后的行) 的逻辑计算,但实际只更新了 1 条记录。
  • MySQL 8.0+
    • 默认仍返回 2,但可通过 JDBC 驱动配置(useAffectedRows=true)改为返回 1(表示实际更新的行数)。

4. 关键优势

  • 数据完整性
    • 不会因删除操作触发外键约束或触发器。
  • 性能优化
    • 避免不必要的删除和插入操作,减少锁竞争。
  • 自增主键友好
    • 不会因更新操作导致自增 ID 不连续。

5. 示例验证

复制代码

sql

复制代码
`-- 创建测试表
CREATE TABLE test (
    id INT PRIMARY KEY,
    name VARCHAR(50)
);

-- 插入初始数据
INSERT INTO test (id, name) VALUES (1, 'A');

-- 执行 UPSERT
INSERT INTO test (id, name) VALUES (1, 'B') 
ON DUPLICATE KEY UPDATE name = 'B';

-- 查询结果
SELECT * FROM test; -- 输出: id=1, name='B'(旧记录未被删除)`
  • 结果证明:ON DUPLICATE KEY UPDATE 直接更新了字段,未删除旧记录。

6. 总结

特性 ON DUPLICATE KEY UPDATE REPLACE INTO
执行逻辑 直接更新字段 先删除后插入
数据完整性 保留旧记录 可能破坏完整性
自增主键影响 无影响 导致 ID 不连续
返回值(MySQL 5.7) 可能为 2 可能为 2
返回值(MySQL 8.0+) 默认 2(可配置为 1 默认 2

结论ON DUPLICATE KEY UPDATE 的核心逻辑是 直接更新已存在的记录 ,而非先删除后插入。其返回值 2 是 MySQL 的历史行为,可通过配置优化。该特性在数据完整性、性能和自增主键管理上优于 REPLACE INTO

相关推荐
是小章啊13 小时前
MySQL 之SQL 执行规则及索引详解
数据库·sql·mysql
富士康质检员张全蛋13 小时前
JDBC 连接池
数据库
yangminlei13 小时前
集成Camunda到Spring Boot项目
数据库·oracle
ChineHe14 小时前
Redis数据类型篇002_详解Strings核心命令与存储结构
数据库·redis·缓存
清水白石00815 小时前
《从零到进阶:Pydantic v1 与 v2 的核心差异与零成本校验实现原理》
数据库·python
电商API&Tina15 小时前
京东 API 数据采集接口接入与行业分析
运维·服务器·网络·数据库·django·php
柠檬叶子C15 小时前
PostgreSQL 忘记 postgres 密码怎么办?(已解决)
数据库·postgresql
864记忆16 小时前
Qt创建连接注意事项
数据库·qt·nginx
小小bugbug16 小时前
mysql查询的原始返回顺序与limit分页优化
mysql·adb