matrixorigin / matrixorigin/matrixone

[Bug]: Single-row ODKU no-op ignores CLIENT_FOUND_ROWS while UPDATE honors it

Open
#28,180 0 comments 0 reactions 1 assignee Claimed by @XuPeng-SH View on GitHub
kind/bug needs-triage
Dominant language
Go
Stars
1.9k
Forks
311
Avg merge
1d 3h
Merged PRs (30d)
768

Description

## 问题描述

客户端协商了 `CLIENT_FOUND_ROWS` 后,普通 UPDATE 能将“匹配但未改变”的行计为 1,但单行 `INSERT ... ON DUPLICATE KEY UPDATE` 对同一行的 no-op 仍返回 0。OK packet affected rows、SQL `ROW_COUNT()` 和 JDBC 批处理计数都受影响。

这不是批内重复键问题:只需一条输入行即可出现,且没有 AUTO_INCREMENT 或多个唯一键。

## 环境

- 官方 main:`e7bb0572235ec4bac81eb098eb0ad8900f0065ab`,2026-09-05 实测及发单前核对。
- macOS arm64,本地独立 CN/TN/LogService,源码构建未修改产品代码。
- 对照 MySQL Community Server 8.3.0;PyMySQL 显式设置 `client_flag=CLIENT.FOUND_ROWS`。
- Connector/J 8.3.0 另行验证,`useAffectedRows=false`,分别使用客户端和服务端 prepare。

## 最小复现

连接时启用 `CLIENT_FOUND_ROWS`(PyMySQL 的 `pymysql.constants.CLIENT.FOUND_ROWS`,或 Connector/J 的 `useAffectedRows=false`),执行:

```sql
CREATE TABLE t(id INT PRIMARY KEY,v INT);
INSERT INTO t VALUES(1,10);

UPDATE t SET v=10 WHERE id=1;
SELECT ROW_COUNT();

INSERT INTO t VALUES(1,10)
ON DUPLICATE KEY UPDATE v=VALUES(v);
SELECT ROW_COUNT();
SELECT * FROM t;
```

| 操作(CLIENT_FOUND_ROWS 开启) | MO affected / ROW_COUNT | MySQL affected / ROW_COUNT |
|---|---:|---:|
| 普通 UPDATE no-op | 1 / 1 | 1 / 1 |
| 单行 ODKU no-op | 0 / 0 | 1 / 1 |
| ODKU 将 v 从 10 改为 11 | 2 / 2 | 2 / 2 |
| 已有 `(1,11)`,ODKU 输入 `(1,11),(2,20)` | 1 / 1 | 2 / 2 |

这些场景两端各执行 3 次,计数差异均相同;完整底表一致。

关闭 CLIENT_FOUND_ROWS 后,两端普通 UPDATE no-op 和 ODKU no-op 都为 0;实际变化为 2;上述混合输入为 1。正常 UPDATE 能区分开关,也证明不是客户端未发送标志。

## JDBC 可观察影响

初始数据 `(1,10),(2,20)`,依次向同一 PreparedStatement 添加 `(1,11),(2,20),(3,30)`,ODKU 为 `v=VALUES(v)`:

```text
rewriteBatchedStatements=false
useAffectedRows=false
useServerPrepStmts=false / true

MO executeBatch(): [2, 0, 1]
MySQL executeBatch(): [2, 1, 1]
```

两端最终数据都是 `(1,11),(2,20),(3,30)`,同连接之后仍可继续写入。问题是匹配行计数,不是行丢失。

## 预期与排查方向

ODKU 应遵守已协商的 CLIENT_FOUND_ROWS:已有行保持原值时计 1,而不是 0。[MySQL 官方 ODKU 说明](https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html)明确列出这一规则。

[`frontend.countUpdateChangedRows`](https://github.com/matrixorigin/matrixone/blob/e7bb0572235ec4bac81eb098eb0ad8900f0065ab/pkg/frontend/mysql_cmd_executor.go#L5428)已经读取该能力位,普通 UPDATE 的实测也符合开关语义。ODKU 的 matched/no-op 计数是否将该能力位传递到执行器,是进一步排查方向;此处不把未定位的内部路径写成已证实根因。

## 关联与证据

- #28178 是**默认客户端标志下批内重复键漏计动作**;本问题是**FOUND_ROWS 开启后单行 no-op 漏计匹配行**,输入与预期合同不同。
- 已关闭的 #27334 涉及普通 UPDATE 的 `useAffectedRows`,当前普通 UPDATE 对照正常,不重新打开旧问题。
- 检索了 LAST_INSERT_ID、FOUND_ROWS 和 ODKU no-op 的开放/关闭 issue;没有重复提交已有的自增 ID 问题 #28162。
- 本地证据:`/private/tmp/mo-odku-remaining.yuVtbx/` 下 `found_rows.py`、`mo_found.stdout`、`mysql_found.stdout`、`OdkuJdbc.java`、`mo_jdbc.jsonl`、`mysql_jdbc.jsonl`。上述关键 SQL、字面结果和驱动配置已直接列出,本地路径不是公开下载链接。
- 小数据和真实客户端足够复现,不依赖 big-data 或 Chaos;本轮仅探索,未新增 BVT/motr。

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.