【mysql】BIGINT UNSIGNED字段被表示为float科学计数法 & 丢失精度问题

发布于:2025-06-04 ⋅ 阅读:(21) ⋅ 点赞:(0)

1. 问题

我有如下 sql 查询:

rows, err := db.Query("SELECT COALESCE(creator, ?) FROM table1 LIMIT 1;", "")
if err != nil {
   return err
}
defer rows.Close()

for rows.Next() {
   var dest string
   if err = rows.Scan(&dest); err != nil {
      return err
   }
   fmt.Println("dest: ", dest)  // 值为 6.743046165522305e+16 ?
}

发现数据库存储的 creator 内容是 67430461655223049但是查询结果却被变成了一个科学计数法的 string: 6.743046165522305e+16 

creator 的类型明明是一个 BIGINT UNSIGNED 无符号整型:

`creator` BIGINT UNSIGNED NOT NULL DEFAULT 0

为什么会被扫描成一个 float 格式的字符串的呢?

2. sql 分析

增加一段打印,查看 go-sql-driver 驱动解析出的类型:

colTypes, _ := rows.ColumnTypes()
for _, ct := range colTypes {
   fmt.Printf("DB Type: %s, Scan Type: %v\n", ct.DatabaseTypeName(), ct.ScanType())
}

发现这个字段真的被驱动解析成了 float 类型:

DB Type: DOUBLE, Scan Type: float64

(1) 使用原本字段

尝试修改 sql 语句:

# sql
SELECT creator FROM table1 LIMIT 1;

# 打印
DB Type: UNSIGNED BIGINT, Scan Type: uint64

原始 creator 类型是 uint 没错,那问题就出在了 COALESCE 上

(2) 使用 CAST(COALESCE(creator, ?) 

尝试使用 CAST 函数,将类型手动转换为 uint:

# sql
SELECT CAST(COALESCE(creator, ?) AS UNSIGNED) FROM table1 LIMIT 1;

# 打印
DB Type: UNSIGNED BIGINT, Scan Type: uint64

# 查询结果
67430461655223048

类型对了,但是发现读出的数据不对了!

应该是 67430461655223049,查询出来变成了 67430461655223048,牙白!丢精度了!

因为:驱动想用 float64 去接收的,但我强制将它转成了 uint,在某些环境下可能会丢失精度。

float64 无法精确表示所有 uint64 范围内的整数,特别是超过 2^53 的数字,就会发生舍入误差。

(3) 使用 COALESCE(CAST(creator AS CHAR), ?)

也就是说,它想用 float,我不能强制转为 uint,那我可以强制转为 string 啊!

# sql
SELECT COALESCE(CAST(creator AS CHAR), ?) FROM table1 LIMIT 1;

# 打印
DB Type: VARCHAR, Scan Type: sql.NullString

# 查询结果
67430461655223049

ok 结果正确

(4) 使用 COALESCE(creator, '')

在尝试不同的 sql 语句过程中我发现,当我使用 COALESCE(creator, ?) 并传入空字符串作为参数,和直接写死 COALESCE(creator, ‘’) 的行为是不同的,会直接影响字段类型的解析结果:

# sql
SELECT COALESCE(creator, ?) FROM ..

# 被解析为float
DB Type: DOUBLE, Scan Type: float64


# sql
SELECT COALESCE(creator, '') FROM ..

# 被解析为string
DB Type: VARCHAR, Scan Type: string

这是由于: MySQL 在执行查询时会为每个字段生成元信息(metadata),包括字段名、字段类型、是否可能为 NULL、是否是数字类型等。

当使用 COALESCE(creator, ?) 并传参 "" 时,驱动不知道 ? 的实际类型是什么,它会尝试根据 creator(BIGINT UNSIGNED)和参数的默认类型进行推断。go-sql-driver 就会把参数当作 float64 来处理(因为它是数值型),整个 COALESCE(...) 表达式被推断为 DOUBLE 类型。

BUT!这种写法存在隐患:

  • 类型推导不稳定:MySQL可能在某些版本/配置下将其推导为DOUBLE,导致科学计数法(6.743046165522305e+16)
  • 依赖驱动行为:不同版本的go-sql-driver/mysql可能解析出不同Go类型(string/float64)

(5) 使用  IFNULL(CAST(creator AS CHAR), '')

还有一种写法:

# sql
SELECT IFNULL(CAST(creator AS CHAR), ?) FROM table1 LIMIT 1;

# 打印
DB Type: VARCHAR, Scan Type: sql.NullString

# 查询结果
67430461655223049

也是可以的。

但是 IFNULL 不是标准 SQL,仅限于 MySQL 环境下使用。如果你希望代码兼容其他数据库(如 PostgreSQL),应该优先使用 COALESCE。

3. 解决

最佳写法:

rows, err := db.Query("SELECT COALESCE(CAST(creator AS CHAR), ?) FROM table1 LIMIT 1;", "", "89448245134135417")

# 打印
DB Type: VARCHAR, Scan Type: sql.NullString

CAST 直接将字段按映射成了 string 类型,当其值为 NULL 时,被表示为一个空字符串~

4. 几种 sql 写法对比

方案
类型安全
精度保证
推荐
SELECT creator
✅uint64
❌不能处理NULL值
COALESCE( creator , ?)
❌ 变为float64
❌可能出现科学计数法表示
COALESCE(creator, '')
❌ 可能string也可能变为float64
❌依赖驱动版本
CAST(COALESCE(creator, ?) AS UNSIGNED)
✅uint64
❌ 会将float转为uint丢失精度
❌丢失精度
COALESCE(CAST(creator AS CHAR), ?)
✅sql.NullString
IFNULL(CAST(creator AS CHAR), ?)
✅sql.NullString
不是标准SQL不够通用


网站公告

今日签到

点亮在社区的每一天
去签到