浮点数存储存在二进制精度误差,直接用=匹配常失败;应优先使用DECIMAL类型,或采用范围判断(如ABS(price-19.99)<0.005)或显式转定点数(如price::DECIMAL(10,2)=19.99)。

为什么 WHERE price = 19.99 经常查不到数据
浮点数在数据库里存的不是你看到的“19.99”,而是近似二进制表示,比如 19.990000000000002 或 19.989999999999998。直接用 = 匹配几乎必然失败,尤其在 REAL、FLOAT 类型字段上。
常见错误现象:SELECT * FROM orders WHERE amount = 99.99 返回空,但你知道表里有这笔订单;用 CAST(amount AS DECIMAL(10,2)) = 99.99 就能查到。
- 优先把业务中涉及金额、百分比、度量值的字段定义为
DECIMAL(p,s)(如DECIMAL(10,2)),而不是FLOAT或REAL - 如果字段已是浮点类型,匹配前必须做显式舍入或区间判断,不能依赖原始值相等
- 不同数据库对浮点字面量的解析略有差异:PostgreSQL 默认把
19.99当作NUMERIC,MySQL 可能按DOUBLE处理——这会让同样的 SQL 在不同环境行为不一致
ROUND() 的陷阱:四舍五入 ≠ 精确截断
ROUND() 看起来是救星,但它的行为受数据库实现和参数影响很大。比如 ROUND(19.995, 2) 在 PostgreSQL 返回 20.00(银行家舍入),在 MySQL 5.7+ 默认也是银行家舍入,但旧版本可能用传统四舍五入。
更麻烦的是:有些数据库(如 SQLite)的 ROUND() 返回的是整数类型,导致 ROUND(19.99, 2) = 19.99 实际比较的是 20 = 19.99,结果恒为 false。
- 别写
ROUND(price, 2) = 19.99—— 先确认你的数据库ROUND()返回什么类型(用pg_typeof()或SELECT typeof(ROUND(1.1,2))测试) - 稳妥做法是统一转成
DECIMAL:在 PostgreSQL 用price::DECIMAL(10,2) = 19.99;在 MySQL 用CONVERT(price, DECIMAL(10,2)) = 19.99 - 如果必须用
ROUND(),加一层类型强制:例如 PostgreSQL 中ROUND(price, 2)::NUMERIC = 19.99
安全匹配的两种可靠写法
真正能落地的方案就两个:一是用范围判断(容忍微小误差),二是用定点数转换(前提是你清楚业务精度)。
范围判断适合临时查询或无法改表结构的场景;定点数转换适合长期维护、性能敏感的业务逻辑。
- 误差容忍法(通用):
WHERE ABS(price - 19.99) —— 这能覆盖所有四舍五入到小数点后两位的原始值 - 定点数转换法(推荐):
WHERE CAST(price AS DECIMAL(10,2)) = 19.99(PostgreSQL/SQL Server),或WHERE ROUND(price, 2) = 19.99仅当已验证该函数返回DECIMAL类型时才可用 - 注意:范围法在大表上无法利用索引,而
CAST或CONVERT后的表达式通常也不能走索引——如有高频查询需求,应在应用层归一化后写入DECIMAL字段,并为其建索引
从源头避免问题:建表时就选对类型
多数浮点数匹配问题,其根子不在查询写法,而在建表时用了 FLOAT 存价格、折扣率、库存数量这些本该精确的值。
比如 discount_rate FLOAT 存 0.15,实际存的是 0.14999999999999999,后续做 1 - discount_rate 就会漂移;再比如 quantity FLOAT 导致“库存为 1”却查不到 WHERE quantity = 1。
- 金额类一律用
DECIMAL(12,2)或更高精度(如跨境支付用DECIMAL(15,4)) - 百分比类用
DECIMAL(5,4)(支持 0.0001~0.9999),别用FLOAT存 0.15 这种“看起来干净”的字面量 - 如果已有浮点列且无法重构,至少在应用层读取后立刻转成定点数或字符串格式展示,避免前端再拿回来当条件查库
事情说清了就结束。最常被忽略的一点是:开发时本地用 SQLite 跑通的 ROUND() 查询,上线到 MySQL 或 PostgreSQL 就失效——不是语法错,是类型隐式转换规则不同。










