批量插入的sql
在实际场景中,为了保证幂等,或者其他约束,往往会有唯一键,在批量插入的时候,往往需要唯一键冲突的时候,用新的值替换老的值,sql如下
INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6)
ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);
不同的公司内部框架实现都不一样,但底层都是一样的sql,只是上层的语法不一样而已
批量插入控制一次批量的size
如果一次批量的数据太大,执行时间过长,容易导致失败,而且耗时高。所以,一般都是每次批量固定的个数,然后并发的执行,具体的批量个数可以根据实际场景决定。
var batchCnt = 200
group, errCtx := errgroup.WithContext(ctx)
for len(convInfos) > batchCnt {
batchList := convInfos[:batchCnt]
convInfos = convInfos[batchCnt:]
group.Go(
func() error {
return batchFunc(errCtx, batchList)
})
}
group.Go(func() error {
return batchFunc(errCtx, convInfos)
})
err := group.Wait()
if err != nil {
log.Errorf(ctx, "BatchAddXXXX errGroup failed, err %v", err)
return err
}
也可以写一个切分的函数,然后遍历结果并发执行
func Split(s []int, size int) [][]int {
res := make([][]int, 0, len(s)/size+1)
for len(s) > size {
res = append(res, s[0:size])
s = s[size:]
}
if len(s) > 0 {
res = append(res, s[0:])
}
return res
}
errgroup的坑
代码一中:errgroup的Go方法,内部有一个闭包,这个闭包执行的时候,取的值是在另一个goroutine里面取了for这个goroutine的值,所以,代码一打印出来的值,不是完整的1-9。
代码二中:直接go p(i),对p方法的参数赋值这个操作,还是for这个goroutine来做的,所以能完整的打印出1-9
代码三中:对i进行了一个for循环内的引用,每次生成的闭包读的值都不一样,所以能完整的打印出1-9
代码一:
func TestErrgroup(t *testing.T) {
group, _ := errgroup.WithContext(context.Background())
for i := 0; i < 10; i++ {
group.Go(func() error {
fmt.Println(i)
return nil
})
}
group.Wait()
}
执行结果
=== RUN TestErrgroup
10
10
10
10
10
10
10
10
10
--- PASS: TestErrgroup (0.00s)
10
代码二:
func TestErrgroup(t *testing.T) {
for i := 0; i < 10; i++ {
go p(i)
}
}
func p(i int) {
fmt.Println(i)
}
=== RUN TestErrgroup
3
6
4
5
7
8
1
9
0
2
--- PASS: TestErrgroup (0.00s)
PASS
代码三:
func TestErrgroup(t *testing.T) {
group, _ := errgroup.WithContext(context.Background())
for i := 0; i < 10; i++ {
k := i
group.Go(func() error {
fmt.Println(k)
return nil
})
}
group.Wait()
}
输出:
=== RUN TestErrgroup
3
1
9
2
7
0
8
4
5
6
--- PASS: TestErrgroup (0.00s)
errgroup的取消
errgroup默认会话有一个任务返回err的时候,取消这个context,如果想要在一个任务报错的时候,取消其他并发执行的任务,则把这个context传进去。
func TestErrgroup(t *testing.T) {
group, ctx := errgroup.WithContext(context.Background())
for i := 0; i < 10; i++ {
k := i
group.Go(func() error {
return p(ctx, k) //一个任务失败,其他并发任务会取消
})
}
group.Wait()
}
func p(ctx context.Context, i int) error {
select {
case <-ctx.Done():
return errors.New("err")
default:
}
fmt.Println(i)
return nil
}
如果不想取消,则重新生成一个context
func TestErrgroup(t *testing.T) {
group, ctx := errgroup.WithContext(context.Background())
for i := 0; i < 10; i++ {
k := i
group.Go(func() error {
return p(context.Background(), k) //重新生成ctx,互不影响
})
}
group.Wait()
}
func p(ctx context.Context, i int) error {
select {
case <-ctx.Done():
return errors.New("err")
default:
}
fmt.Println(i)
return nil
}