decimal字段的precision是总位数、scale是小数位数,须满足scale≤precision;填反或超限会导致截断或报错,金额等精确场景必须用decimal而非float。

decimal 字段的 precision 和 scale 怎么填才不丢精度
填反了就存不进小数,或者直接被截断成整数——decimal(8,2) 不是“总共 8 位,小数占 2 位”,而是“总共最多 8 位数字,其中小数点后固定占 2 位”。precision 是总位数(含小数点前+后),scale 是小数点后位数。
常见错误现象:DB::table('orders')->insert(['amount' => 123.456]) 存进去变成 123.45,甚至报错 Out of range value for column 'amount'。
-
decimal(10,2):最大能存99999999.99(8 位整数 + 小数点 + 2 位小数) -
decimal(5,3):最大只能存99.999(2 位整数 + 3 位小数),不是“任意 5 位数字” - Laravel 迁移里用
$table->decimal('price', 10, 2),顺序不能颠倒;写成decimal('price', 2, 10)会创建出decimal(2,10),MySQL 直接拒绝(scale 不能大于 precision)
迁移中改 decimal 字段精度会丢失数据吗
会。MySQL 在 ALTER TABLE ... MODIFY COLUMN 时,如果新定义的 precision 或 scale 缩小了,已有数据可能被截断或四舍五入,且 Laravel 默认不提示。
使用场景:上线后发现价格字段要支持分以下单位(如厘),需要从 decimal(10,2) 升级到 decimal(12,4)。
- 安全做法:先加新字段(如
price_new),用 PHP 迁移旧值(round($old * 100) / 10000),再删旧字段、重命名 - 直接改:用
$table->decimal('price', 12, 4)->change(),但必须确认当前所有值都能无损映射——比如原值123.45改成decimal(12,4)没问题,但若原字段是decimal(5,2)存过1234.56,改完就会报错 - 执行前务必在测试库跑
SELECT MAX(LENGTH(SUBSTRING_INDEX(price, '.', 1))) AS int_len, MAX(LENGTH(SUBSTRING_INDEX(price, '.', -1))) AS dec_len FROM orders;算出实际整数/小数长度
float/double 和 decimal 在 Laravel 迁移里怎么选
除非你存的是科学计算中间值,否则别用 float 或 double——它们是二进制浮点,0.1 + 0.2 !== 0.3,Laravel 的 Eloquent 取出来可能变成 0.30000000000000004。
使用场景:金额、重量、税率等要求精确十进制运算的字段。
-
$table->decimal('fee', 10, 2)→ 安全,Eloquent 返回原样字符串或 float(取决于casts),但数据库层面无误差 -
$table->float('score')→ 适合评分、相似度等允许微小误差的场景 - 注意:MySQL 的
decimal在查询时不会自动转成 PHP float,但如果你在模型里写了protected $casts = ['price' => 'float'],PHP 浮点精度问题就又回来了
decimal 字段在 Seeder 或工厂里怎么避免精度丢失
PHP 的 rand() 或 mt_rand() 生成整数再除,容易引入浮点误差;number_format() 返回字符串,但 Eloquent 插入时若没设 casts,可能被当成字符串处理导致 MySQL 警告。
正确做法:用整数存储单位(如“分”),或用 bcadd()/bcmul() 控制精度。
- 推荐方式:工厂里写
'price' => fake()->randomFloat(2, 10, 1000)(Laravel 9+),它内部用字符串生成,避免 PHP 浮点污染 - 手写时用
'price' => sprintf('%.2f', mt_rand(1000, 100000) / 100),确保输出是格式化后的字符串 - Seeder 中批量插入大量数据时,避免用
collect([...])->each(...)触发模型事件,直接用DB::table()->insert()更快,也绕过 Eloquent 的 cast 处理
最易被忽略的一点:MySQL 的 sql_mode 如果包含 STRICT_TRANS_TABLES(默认开启),任何精度超限都会报错;而关闭它只会警告,数据静默截断——线上环境千万别关 strict mode 来“绕过”问题。











