Laravel迁移中decimal字段必须按decimal($precision,$scale)顺序定义且满足$scale≤$precision,否则MySQL报错或数据截断;decimal(10,2)表示共10位有效数字、小数固定2位,整数最多8位;float/double因二进制精度问题严禁用于金额。

直接说结论:Laravel 迁移中 decimal 字段的精度必须按 decimal($precision, $scale) 顺序填写,且严格满足 $scale ≤ $precision;填反、超限或误用 float 都会导致数据截断、报错或计算失真——这不是配置问题,是 MySQL 底层约束。
decimal(10,2) 到底能存什么数
不是“最多 10 位数字,小数占 2 位”,而是“总共最多 10 位有效数字,其中小数点后固定占 2 位”。整数部分最多占 8 位(因为小数点本身不占位)。
-
decimal(10,2)可存99999999.99,但存不下100000000.00(整数超 8 位)或123.456(小数超 2 位 → 自动截断为123.45) -
decimal(5,3)表示最多 5 位总长、小数占 3 位 → 整数最多 2 位,如99.999;写成decimal(5,10)会直接被 MySQL 拒绝(SQLSTATE[42000]: Syntax error or access violation: 1427 Too-big scale specified) - 常见错误现象:
DB::table('orders')->insert(['amount' => 123.456])存进去变成123.45,甚至触发Out of range value for column 'amount'
迁移里写错 decimal 参数顺序的后果
Laravel 不校验参数逻辑,它原样传给 MySQL。写成 $table->decimal('price', 2, 10) 会生成 DECIMAL(2,10),MySQL 立即报错并中断迁移。
- 正确写法永远是
$table->decimal('price', 10, 2)—— 第二个参数是precision(总位数),第三个是scale(小数位数) - 如果已误提交并执行过该迁移,不能靠改文件重跑:
migrate:rollback会删表或字段,但历史记录已污染;应新建迁移用Schema::table()->change()修正(前提是已装doctrine/dbal) - 上线前务必在测试库验证:运行
SELECT MAX(LENGTH(SUBSTRING_INDEX(price, '.', 1))) AS int_len, MAX(LENGTH(SUBSTRING_INDEX(price, '.', -1))) AS dec_len FROM orders;查出当前数据实际占用的整数/小数长度
从 decimal(10,2) 升级到 decimal(12,4) 安全操作步骤
直接 $table->decimal('price', 12, 4)->change() 看似简单,但一旦现有值的小数位超过 4 位(比如存过 123.45678),ALTER COLUMN 时 MySQL 会静默四舍五入或截断——Laravel 不提示,Eloquent 也读不出异常。
- 安全做法:先加新字段
price_new,用 PHP 脚本迁移旧值(如round($old * 100) / 10000),再删旧字段、重命名 - 若坚持直接改,必须确认所有现存值都满足新约束:用
SELECT * FROM orders WHERE LENGTH(SUBSTRING_INDEX(price, '.', -1)) > 4 OR LENGTH(SUBSTRING_INDEX(price, '.', 1)) > 8;扫描越界数据 - 生产环境执行前加锁备份:
php artisan migrate --force必须配合mysqldump或主从同步暂停,避免 ALTER 期间写入丢失
float/double 为什么绝对不该用于金额字段
它们是二进制浮点类型,和十进制小数存在固有表示误差。哪怕只是做加减,0.1 + 0.2 在 MySQL 和 PHP 层都可能变成 0.30000000000000004,Eloquent 取出来就是错的。
-
float和double在 Laravel 迁移里只适用于科学计算中间值(如 AI 模型权重),业务系统中金额、库存、比例等一切需精确比较或累加的场景,必须用decimal - 别信“PHP round() 能修好”——round 是应用层补救,数据库底层存储已失真;且并发更新时,两次 round 结果可能不一致
- 检查存量代码:
DB::table('xxx')->selectRaw('SUM(amount)')->get()如果amount是float,结果不可信;改字段前先确认所有下游报表、对账逻辑是否兼容 decimal
最易被忽略的一点:decimal 的精度约束是 MySQL 强制执行的,不是 Laravel 校验的。你写的迁移脚本只要通过语法检查就能跑,但数据是否真能无损存取,得看线上真实值分布和 MySQL 的实际报错日志——别等财务对不上账才查。


















