使用Goroutine批量导入CSV至PostgreSQL时遇切片越界Panic
嘿,这个切片拆分的坑我之前也踩过!咱们先搞清楚问题出在哪,再给你解决办法。
首先,你遇到的runtime error: slice bounds out of range本质是整数除法的向下取整特性导致的。假设你有13个CSV文件要拆成10份:len(fis)/10等于1(Go里整数除法会直接丢掉小数部分),如果按固定每份1个来拆分,前9份各拿1个,剩下的3个文件没被处理;但如果你的代码逻辑是强制拆成10份,每份都按len(fis)/10的长度去取,当最后一份的结束索引超过切片总长度时,就会触发panic——比如总文件数是9、goroutine数是10时,第10个goroutine的切片范围会超出原切片的长度。
正确的拆分方式
核心思路是:先计算每份的基础长度,再把无法整除的余数均匀分配给前几份(每份多1个文件),确保所有文件都被覆盖,且不会超出切片范围。
给你一段修正后的代码示例:
package main import ( "os" "sync" ) func main() { gophers := 10 // 假设你已经获取到了所有CSV文件的FileInfo列表 fis, err := os.ReadDir("/path/to/csvs") if err != nil { panic(err) } fileInfos := make([]os.FileInfo, len(fis)) for i, f := range fis { fileInfos[i], _ = f.Info() } totalFiles := len(fileInfos) chunkSize := totalFiles / gophers remainder := totalFiles % gophers // 无法整除的剩余文件数 var wg sync.WaitGroup for i := 0; i < gophers; i++ { start := i * chunkSize // 给前remainder个goroutine多分配1个文件 end := start + chunkSize if i < remainder { end++ } // 保险起见,确保end不会超过总文件数(其实上面的逻辑已经避免了) if end > totalFiles { end = totalFiles } // 跳过空的chunk(比如总文件数小于goroutine数时) if start >= end { continue } chunk := fileInfos[start:end] wg.Add(1) go func(files []os.FileInfo) { defer wg.Done() // 这里写你的CSV读取+PostgreSQL导入逻辑 for _, fi := range files { filePath := "/path/to/csvs/" + fi.Name() // 1. 读取CSV文件内容到对象 // 2. 批量插入PostgreSQL(建议用事务提高性能) } }(chunk) } // 等待所有goroutine完成导入 wg.Wait() }
额外的优化建议
- 数据库批量插入:不要每个CSV文件单独开事务,尽量把多个文件的数据攒成一批再插入,减少数据库连接开销,提高导入速度。
- 错误处理:上面的示例简化了错误处理,实际代码中要捕获CSV读取、数据库操作的错误,避免某个goroutine出错导致整个导入流程静默失败。
- 资源限制:如果CSV文件非常大,要注意内存占用,必要时可以分批次读取文件内容,而不是一次性加载到内存。
这样拆分的话,不管总文件数能不能被10整除,都不会出现切片越界的问题,所有文件都会被均匀分配给各个goroutine处理。
内容的提问来源于stack exchange,提问作者MarcoW
相关产品推荐
相关产品推荐

