根本原因是insert_batch()对输入数组结构极其敏感,字段数不一致、非法键名或NULL写入NOT NULL字段会导致静默失败且零条写入。

批量插入报错且数据丢失,根本原因不是数据库拒绝写入,而是 insert_batch() 对输入数组结构极其敏感——只要某条记录字段数不一致、含非法键名、或某字段值为 null 且对应列不允许为空,整个批次就会静默失败,不报错也不写入任何一条。
为什么 insert_batch() 会“丢数据”却不报错
CodeIgniter 3 的 insert_batch() 默认不校验单条记录完整性,遇到第一条格式错误的记录就直接中断,且返回 false。但很多开发者没检查返回值,也没开启数据库调试,误以为“执行了”,实际零条写入。
- 常见触发点:
NULL值写入NOT NULL字段(如'updated_at' => null) - 字段名拼写错误(如把
'user_id'写成'userid'),CI 自动忽略该字段,导致该行字段数少于表结构 - 混合了关联数组和索引数组(如部分记录用
['name'=>'a'],部分用['a','b']),CI 无法对齐列 - 某条记录缺失必填字段,但其他记录正常 —— 整个批次被拒
insert_batch() 前必须做的三件事
别急着调用,先做这几步能立刻暴露问题:
- 用
var_dump($data)确认每条记录都是关联数组,且所有键名与数据库列名完全一致(区分大小写) - 手动遍历检查:对每条记录执行
array_key_exists('required_column', $row),确保无缺失 - 在控制器开头加
$this->db->db_debug = TRUE;,让错误直接抛出,而不是返回false
更稳的替代方案:用循环 + insert() 加事务
当数据来源不可控(如用户上传 CSV、第三方 API 返回)时,insert_batch() 风险太高。改用带事务的单条插入,失败可定位具体哪一行:
$this->db->trans_start();
foreach ($data as $row) {
// 强制补全默认值,避免 NULL 写入 NOT NULL 列
$row['created_at'] = $row['created_at'] ?? date('Y-m-d H:i:s');
$row['status'] = $row['status'] ?? 'active';
if ($this->db->insert('your_table', $row) === FALSE) {
log_message('error', 'Insert failed for row: ' . json_encode($row));
break;
}
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
// 处理回滚后的状态,比如返回错误行号
}
注意:trans_complete() 后必须检查 trans_status(),CI 的事务不会自动抛异常。
字段映射不匹配时的硬核修复法
如果表单字段名和数据库列名不一致(如前端传 user_name,库中是 username),别在循环里反复 str_replace()。统一做一次映射:
$field_map = [
'user_name' => 'username',
'full_name' => 'name',
'email_addr' => 'email'
];
$clean_data = [];
foreach ($raw_data as $row) {
$clean_row = [];
foreach ($field_map as $api_key => $db_col) {
$clean_row[$db_col] = $row[$api_key] ?? '';
}
$clean_data[] = $clean_row;
}
$this->db->insert_batch('users', $clean_data);
这种写法比在 insert_batch() 前用 array_map() 更易调试,也避免因键名不存在导致整行被跳过。
真正容易被忽略的是:CI3 的 insert_batch() 不支持 ON DUPLICATE KEY UPDATE,如果业务需要冲突更新,必须手写 INSERT ... ON DUPLICATE KEY UPDATE 原生 SQL —— 这时候连事务控制都要自己重写。


















