Laravel事务中向SQL Server插入多类型数值失败,求全局解决方案
首先,这个问题确实是Laravel结合SQL Server驱动时的参数绑定推断问题——在事务内批量插入时,驱动会根据数组中第一个元素的字段值类型,错误地统一后续所有同名字段的参数类型。比如你这里第一个price是13.52(浮点型),第二个是35(整型),驱动错误地把所有price参数都按整型处理,导致13.52被当成字符串尝试转int失败。
下面是几个无需临时修复的全局处理方案:
方案1:开启PDO模拟预处理(全局生效)
在config/database.php的SQL Server连接配置中,添加PDO::ATTR_EMULATE_PREPARES => true选项,让PDO模拟预处理语句,这样驱动会正确识别每个参数的类型:
'connections' => [ 'sqlsrv' => [ 'driver' => 'sqlsrv', 'host' => env('DB_HOST', 'localhost'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, 'options' => [ PDO::ATTR_EMULATE_PREPARES => true, // 添加这一行 ], ], ],
这个方案是全局的,所有使用该连接的批量插入都会受益,不需要修改业务代码。
方案2:全局查询构建器宏(灵活处理特定字段)
如果不想修改PDO配置,可以在AppServiceProvider的boot方法中定义一个全局宏,专门处理批量插入时的numeric字段类型转换:
use Illuminate\Database\Query\Builder; use Illuminate\Support\Facades\DB; public function boot() { Builder::macro('insertNumericSafe', function (array $values) { if (empty($values)) { return true; } // 获取当前表的字段类型(可缓存避免重复查询) $table = $this->from; $columns = array_keys($values[0]); $schema = DB::getSchemaBuilder(); $numericColumns = []; foreach ($columns as $col) { if ($schema->getColumnType($table, $col) === 'numeric') { $numericColumns[] = $col; } } // 转换所有numeric字段的整型值为浮点型 foreach ($values as &$item) { foreach ($numericColumns as $col) { if (isset($item[$col]) && is_int($item[$col])) { $item[$col] = (float)$item[$col]; } } } return $this->insert($values); }); }
之后在业务代码中,用insertNumericSafe替代insert即可:
DB::transaction(function() { Line::insertNumericSafe([ ['price' => 13.52], ['price' => 35] ]); });
方案3:模型层统一处理(针对特定模型)
如果只需要处理特定模型的字段,可以在Line模型中重写insert方法,统一转换price字段的格式:
class Line extends \Illuminate\Database\Eloquent\Model { protected $table = 'lines'; protected $guarded = ['id']; public static function insert(array $values) { // 将所有price字段的整型值转为带三位小数的格式(匹配表结构的NUMERIC_SCALE=3) $values = array_map(function ($item) { if (isset($item['price'])) { $item['price'] = number_format($item['price'], 3, '.', ''); } return $item; }, $values); return parent::insert($values); } }
这样你原来的业务代码不需要任何修改,直接调用Line::insert即可自动处理类型问题。
问题根源补充
你提到手动在SQL客户端执行该语句(包括事务内)正常,是因为SQL Server会自动解析13.52和35为numeric类型;但Laravel在事务内批量插入时,底层驱动会尝试绑定参数并推断类型,当参数类型混合时出现了推断错误,导致类型转换失败。
内容的提问来源于stack exchange,提问作者Daniel

