PostgreSQL ERROR: invalid input syntax for type numeric: "XXXX"
问题描述
最近在项目中遇到了一个诡异的 SQL 查询错误,同样的查询结构,只是参数不同,有的能正常执行,有的却报错
错误现象
sql
-- 查询1:正常执行
SELECT * FROM order_product_rela
WHERE order_id = 321 AND product_id = 2113;
-- 查询2:报错
SELECT * FROM order_product_rela
WHERE order_id = 334 AND product_id = 2216;
-- ERROR: invalid input syntax for type numeric: "PRODUCT_A"
两个查询的结构完全相同,只是参数不同,为什么一个正常一个报错?
背景
order_product_rela 表是订单产品关联表,product_id 字段最初设计为 int8 类型,只存储产品ID。后来业务需求变更,需要同时支持产品ID和产品编码,于是将字段类型改为 varchar,兼容了之前的数字ID和新的字母编码。(注:不建议这样设计表,这是之前别人设计的)
问题分析
查看执行计划
首先,我查看了查询的执行计划:
sql
EXPLAIN SELECT * FROM order_product_rela
WHERE order_id = 334 AND product_id = 2216;
执行计划如下:
QUERY PLAN |
-------------------------------------------------------------------------+
Seq Scan on order_product_rela (cost=0.00..4.12 rows=3 width=415) |
Filter: ((order_id = 334) AND ((product_id)::bigint = 2216)) |
关键信息:
- Seq Scan:全表扫描,没有使用索引
- Filter :
(product_id)::bigint = 2216,PostgreSQL 将product_id字段转换为bigint类型进行比较
为什么会报错?
由于是全表扫描,PostgreSQL 需要对每一行数据执行过滤条件:
对于每一行数据:
- 检查 order_id = 334
- 如果满足,则检查 product_id = 2216
- 在检查 product_id 时,需要将字段值转换为 bigint
- 如果遇到 "PRODUCT_A" 这样的字符串,转换失败,报错
为什么查询1不报错?
sql
SELECT * FROM order_product_rela
WHERE order_id = 321 AND product_id = 2113;
这个查询不报错的原因是:
order_id = 321的行中,product_id都是数字字符串(如 "2113")- 类型转换成功:
CAST("2113" AS bigint) = 2113✅ - 所有类型转换都成功,查询正常执行
为什么查询2报错?
sql
SELECT * FROM order_product_rela
WHERE order_id = 334 AND product_id = 2216;
这个查询报错的原因是:
order_id = 334的行中,存在product_id = "PRODUCT_A"的数据- 类型转换失败:
CAST("PRODUCT_A" AS bigint) = 2216❌ - 遇到无法转换的值,查询报错
PostgreSQL 的类型转换机制
隐式类型转换
当 SQL 查询中字段类型和参数类型不匹配时,PostgreSQL 会尝试进行隐式类型转换:
sql
-- product_id 是 varchar 类型,参数 2216 是数字类型
WHERE product_id = 2216
-- PostgreSQL 可能会尝试:
1. 将参数转换为字符串:product_id = '2216' ✅ 推荐
2. 将字段值转换为数字:CAST(product_id AS bigint) = 2216 ❌ 可能失败
PostgreSQL 的类型转换规则:
- 如果没有明确的类型转换规则,可能会选择将字段值转换为参数类型
- 这就导致了将 "PRODUCT_A" 转换为数字时报错
短路求值(Short-circuit Evaluation)
PostgreSQL 在执行 WHERE 条件时,会采用短路求值策略:
sql
WHERE order_id = 334 AND product_id = 2216
执行过程(对每一行数据):
第1行:order_id = 321
↓
检查 order_id = 334? → false
↓
停止,不检查 product_id(短路)
第2行:order_id = 334, product_id = "2216"
↓
检查 order_id = 334? → true
↓
检查 product_id = 2216? → 需要类型转换
↓
CAST("2216" AS bigint) = 2216 → true
第3行:order_id = 334, product_id = "PRODUCT_A"
↓
检查 order_id = 334? → true
↓
检查 product_id = 2216? → 需要类型转换
↓
CAST("PRODUCT_A" AS bigint) = 2216 → ❌ 报错!
短路求值 = 从左向右执行 + 提前停止
- 从左向右:按照条件在 SQL 中出现的顺序执行
- 提前停止:当可以确定最终结果时,就不再检查后面的条件
解决方案
使用字符串参数,避免类型转换