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

相关推荐
洛豳枭薰18 分钟前
Innodb一次更新动作
mysql
Σίσυφος190026 分钟前
PCL法向量估计 之 方向约束法向量(Orientation Guided Normal)
数据库
老毛肚29 分钟前
手写mybatis
java·数据库·mybatis
海山数据库35 分钟前
移动云大云海山数据库(He3DB)postgresql_anonymizer插件原理介绍与安装
数据库·he3db·大云海山数据库·移动云数据库
云飞云共享云桌面38 分钟前
高性能图形工作站的资源如何共享给10个SolidWorks研发设计用
linux·运维·服务器·前端·网络·数据库·人工智能
2501_927993531 小时前
SQL Server 2022安装详细教程(图文详解,非常详细)
数据库·sqlserver
星火s漫天1 小时前
第一篇: 使用Docker部署flask项目(Flask + DB 容器化)
数据库·docker·flask
xcLeigh1 小时前
Python 项目实战:用 Flask 实现 MySQL 数据库增删改查 API
数据库·python·mysql·flask·教程·python3
威迪斯特1 小时前
Flask:轻量级Web框架的技术本质与工程实践
前端·数据库·后端·python·flask·开发框架·核心架构
xu_yule1 小时前
Redis存储(15)Redis的应用_分布式锁_Lua脚本/Redlock算法
数据库·redis·分布式