matrixorigin / matrixorigin/matrixone
[Bug]: FLOAT and DOUBLE secondary-index equality omits SQL-equal signed zero
- Dominant language
- Go
- Stars
- 1.9k
- Forks
- 311
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 768
Description
## 问题
FLOAT/DOUBLE 二级索引把 `-0.0` 与 `+0.0` 编码为不同的索引键,但 SQL 比较、分组和唯一键均把它们视为相等。因此等值谓词使用二级索引时漏行:结果随访问路径变化。
## 环境
* MatrixOne `main`:`7013fbbd6e152ba31da2d3dbba7012ac7f8b8462`,新启动的 2 CN 环境。
* 对照:MySQL 8.3。
## 复现
```sql
create table d(id int primary key, v double, key idx_v(v));
insert into d values
(1, cast('-0.0' as double)),
(2, cast('0.0' as double)),
(3, 1.0);
select group_concat(id order by id) from d
where v = cast('-0.0' as double);
-- MO: 1
select group_concat(id order by id) from d
where v = cast('0.0' as double);
-- MO: 2
select group_concat(id order by id) from d ignore index(idx_v)
where v = cast('-0.0' as double);
-- MO: 1,2
explain select id from d where v = cast('-0.0' as double);
-- Index Table Scan on d.idx_v
```
同样的 FLOAT 表得到相同行为。
MO 中 `-0.0 = +0.0` 和 `-0.0 <=> +0.0` 都为 true;`GROUP BY v` 合并为一组,且向 DOUBLE UNIQUE KEY 插入 `-0.0` 后插入 `+0.0` 会正确报重复键。因此索引谓词的结果与同一实例的 SQL 相等语义不一致。
在 MySQL 8.3,DOUBLE/FLOAT 的索引访问和 `IGNORE INDEX` 扫描对正、负零谓词均返回 `1,2`。
## 稳定性与范围
* DOUBLE 使用三个独立表重复三轮,每轮 `-0.0` 索引谓词返回 `1`、`+0.0` 返回 `2`,对应扫描均为 `1,2`。
* FLOAT 使用相同数据与二级索引,索引和扫描差分相同。
* SQL `PREPARE/EXECUTE` 复用这一索引路径时也只返回一行;无索引表返回两行。
## 代码线索
`pkg/container/types/compare.go` 已将 signed zero 定义为 SQL ORDER BY peer;`pkg/common/hashmap/keycodec/keycodec.go` 的 `CanonicalFloat32Bits` / `CanonicalFloat64Bits` 也会将两种零归一化。相对地,二级索引的物理元组键保留浮点原始位表示(`Float32TupleAscCompare` / `Float64TupleAscCompare` 的注释明确说明会区分 signed zero)。当前 `prefix_eq(__mo_index_idx_col)` 没有将 SQL 的零等值谓词扩展到两个物理键,导致漏行。
## 预期
二级索引只能作为访问路径,不能改变 SQL 等值语义。FLOAT/DOUBLE 对 `-0.0` 或 `+0.0` 的 `=`, `<=>`, `IN` 等谓词走索引时应覆盖两种物理零表示,或索引键应在写入/查找时按 SQL 等值规则归一化。
Contributor guide
Assessment
This issue has not been assessed yet.