CodeIgniter:如何分批查询大表数据避免内存泄漏且不遗漏行
解决CodeIgniter大表分批查询不遗漏的方案
我之前处理过类似的大表批量操作场景,一次性加载80万+行数据必然会吃爆内存,拆分查询是正确的思路,但绝对不能用基于偏移量的LIMIT来拆分——因为如果查询过程中有数据插入/删除,偏移量会导致重复或遗漏。下面给你一套可靠的拆分方案:
核心思路:用唯一有序字段做拆分依据
最稳妥的方式是依赖表的主键(比如自增id),因为主键唯一且有序,能完美覆盖所有行,不会出现重叠或空隙。如果主键不是自增整数(比如UUID),可以用其他有序唯一的字段(比如带主键的时间戳)。
步骤1:计算拆分的分界值
首先要找到能准确划分前50%和后50%数据的分界主键值:
// 1. 获取表的基础统计数据 $this->db->select('MIN(id) as min_id, MAX(id) as max_id, COUNT(*) as total_rows'); $table_stats = $this->db->get('your_large_table')->row(); // 2. 计算基于行数的中位数主键(最准确对应50%拆分) $this->db->select('id'); $this->db->from('your_large_table'); $this->db->order_by('id', 'ASC'); // 偏移量取总行数的一半,取第offset+1行的id作为分界 $this->db->limit(1, floor($table_stats->total_rows / 2)); $median_row = $this->db->get()->row(); $split_id = $median_row->id;
步骤2:分两次查询并处理数据
用分界值split_id拆分查询,确保两次查询完全覆盖所有行,无重复无遗漏:
// 处理前50%数据:id <= split_id $first_query = $this->db->where('id <=', $split_id)->get('your_large_table'); // 用unbuffered_result逐行处理,避免一次性加载所有数据到内存(CodeIgniter 3+支持) while ($row = $first_query->unbuffered_row('array')) { // 执行INSERT操作,这里可以根据需求调整字段 $this->db->insert('target_table', $row); } $first_query->free_result(); // 释放内存 // 处理后50%数据:id > split_id $second_query = $this->db->where('id >', $split_id)->get('your_large_table'); while ($row = $second_query->unbuffered_row('array')) { $this->db->insert('target_table', $row); } $second_query->free_result();
关键注意事项
- 避免用
LIMIT offset, count:如果在查询过程中有数据新增或删除,偏移量会失效,导致某些行被重复处理或遗漏。 - 非自增主键的处理:如果主键是UUID,可结合
created_at时间戳拆分,比如:// 先找到中间时间点,再结合主键拆分 $this->db->select('created_at, id'); $this->db->order_by('created_at', 'ASC')->order_by('id', 'ASC'); $this->db->limit(1, floor($table_stats->total_rows / 2)); $median_row = $this->db->get()->row(); // 前半部分:created_at < 中间时间,或者时间相等且id <= 分界id $this->db->where('created_at <', $median_row->created_at) ->or_group_start() ->where('created_at', $median_row->created_at) ->where('id <=', $median_row->id) ->group_end(); - 数据一致性保障:如果你的表是读写频繁的,拆分前可以加读锁(InnoDB用
SELECT ... FOR SHARE),防止查询过程中数据被修改导致拆分逻辑失效。 - 验证拆分正确性:测试时可以统计两次查询的行数总和,和总行数对比,确保
count(first_rows) + count(second_rows) == total_rows。 - 批量INSERT优化:如果单条INSERT效率低,可以攒1000条数据做一次批量插入,减少数据库交互次数:
$batch_data = []; while ($row = $first_query->unbuffered_row('array')) { $batch_data[] = $row; if (count($batch_data) >= 1000) { $this->db->insert_batch('target_table', $batch_data); $batch_data = []; } } // 插入剩余的数据 if (!empty($batch_data)) { $this->db->insert_batch('target_table', $batch_data); }
内容的提问来源于stack exchange,提问作者naspy971
相关产品推荐
相关产品推荐

