Repository: gogf/gf

Issue:
database/gdb: performance improvement in fields grouping when inserting batch data
### Description

There might be some performance improvement should be done here in the loop:
![QQ_1730899526409](https://github.com/user-attachments/assets/4a17f44d-cccf-4179-a59e-4953d5808bbe)


### Additional

_No response_

Discussion:
@github-actions: Hello @gqcn. We like your proposal/feedback and would appreciate a contribution via a Pull Request by you or another community member. We thank you in advance for your contribution and are looking forward to reviewing it!
你好 @gqcn。我们喜欢您的提案/反馈，并希望您或其他社区成员通过拉取请求做出贡献。我们提前感谢您的贡献，并期待对其进行审查。

Solution patch:
diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go
index 725935265..7520e42ab 100644
--- a/database/gdb/gdb_core.go
+++ b/database/gdb/gdb_core.go
@@ -12,6 +12,7 @@ import (
 	"database/sql"
 	"fmt"
 	"reflect"
+	"sort"
 	"strings"
 
 	"github.com/gogf/gf/v2/container/gmap"
@@ -446,25 +447,30 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
 	// It here uses ListMap to keep sequence for data inserting.
 	// ============================================================================================
 	var keyListMap = gmap.NewListMap()
+	var tmpkeyListMap = make(map[string]List)
 	for _, item := range list {
-		var (
-			tmpKeys              = make([]string, 0)
-			tmpKeysInSequenceStr string
-		)
+		mapLen := len(item)
+		if mapLen == 0 {
+			continue
+		}
+		tmpKeys := make([]string, 0, mapLen)
 		for k := range item {
 			tmpKeys = append(tmpKeys, k)
 		}
-		keys, err = c.fieldsToSequence(ctx, table, tmpKeys)
-		if err != nil {
-			return nil, err
+		if mapLen > 1 {
+			sort.Strings(tmpKeys)
 		}
-		tmpKeysInSequenceStr = gstr.Join(keys, ",")
-		if !keyListMap.Contains(tmpKeysInSequenceStr) {
-			keyListMap.Set(tmpKeysInSequenceStr, make(List, 0))
+		keys = tmpKeys // for fieldsToSequence
+
+		tmpKeysInSequenceStr := gstr.Join(tmpKeys, ",")
+		if tmpkeyListMapItem, ok := tmpkeyListMap[tmpKeysInSequenceStr]; ok {
+			tmpkeyListMap[tmpKeysInSequenceStr] = append(tmpkeyListMapItem, item)
+		} else {
+			tmpkeyListMap[tmpKeysInSequenceStr] = List{item}
 		}
-		tmpKeysInSequenceList := keyListMap.Get(tmpKeysInSequenceStr).(List)
-		tmpKeysInSequenceList = append(tmpKeysInSequenceList, item)
-		keyListMap.Set(tmpKeysInSequenceStr, tmpKeysInSequenceList)
+	}
+	for tmpKeysInSequenceStr, itemList := range tmpkeyListMap {
+		keyListMap.Set(tmpKeysInSequenceStr, itemList)
 	}
 	if keyListMap.Size() > 1 {
 		var (
@@ -488,6 +494,15 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
 		return &sqlResult, err
 	}
 
+	keys, err = c.fieldsToSequence(ctx, table, keys)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(keys) == 0 {
+		return nil, gerror.NewCode(gcode.CodeInvalidParameter, "no valid data fields found in table")
+	}
+
 	// Prepare the batch result pointer.
 	var (
 		charL, charR = c.db.GetChars()



Test patch:
diff --git a/contrib/drivers/mysql/mysql_z_unit_core_bench_test.go b/contrib/drivers/mysql/mysql_z_unit_core_bench_test.go
new file mode 100644
index 000000000..6c1b2fc53
--- /dev/null
+++ b/contrib/drivers/mysql/mysql_z_unit_core_bench_test.go
@@ -0,0 +1,46 @@
+// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
+//
+// This Source Code Form is subject to the terms of the MIT License.
+// If a copy of the MIT was not distributed with this file,
+// You can obtain one at https://github.com/gogf/gf.
+
+// go test *.go -bench=".*"
+
+package mysql_test
+
+import (
+	"testing"
+
+	"github.com/gogf/gf/v2/os/gtime"
+	"github.com/gogf/gf/v2/util/grand"
+)
+
+func Benchmark_BatchInsert(b *testing.B) {
+	table := createTable()
+	defer dropTable(table)
+	type User struct {
+		Id         int         `c:"id"`
+		Passport   string      `c:"passport"`
+		Password   string      `c:"password"`
+		NickName   string      `c:"nickname"`
+		CreateTime *gtime.Time `c:"create_time"`
+	}
+	var users []*User
+	for i := 0; i < 10000; i++ {
+		users = append(users, &User{
+			Passport:   grand.S(10),
+			Password:   grand.S(10),
+			NickName:   grand.S(10),
+			CreateTime: gtime.Now(),
+		})
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		result, err := db.Insert(ctx, table, users)
+		if err != nil {
+			b.Fatalf("insert error: %v", err)
+		}
+		n, _ := result.RowsAffected()
+		b.Logf("insert %d rows", n)
+	}
+}
diff --git a/contrib/drivers/mysql/mysql_z_unit_core_test.go b/contrib/drivers/mysql/mysql_z_unit_core_test.go
index 604740606..b9334e445 100644
--- a/contrib/drivers/mysql/mysql_z_unit_core_test.go
+++ b/contrib/drivers/mysql/mysql_z_unit_core_test.go
@@ -466,6 +466,31 @@ func Test_DB_BatchInsert(t *testing.T) {
 		n, _ := result.RowsAffected()
 		t.Assert(n, 1)
 	})
+
+	// Batch insert with different fields
+	gtest.C(t, func(t *gtest.T) {
+		table := createTable()
+		defer dropTable(table)
+		r, err := db.Insert(ctx, table, g.List{
+			{
+				"id":          2,
+				"passport":    "t2",
+				"password":    "25d55ad283aa400af464c76d713c07ac",
+				"create_time": gtime.Now().String(),
+			},
+			{
+				"id":          3,
+				"passport":    "user_3",
+				"password":    "25d55ad283aa400af464c76d713c07ad",
+				"nickname":    "name_3",
+				"create_time": gtime.Now().String(),
+			},
+		}, 1)
+		t.AssertNil(err)
+		n, err := r.RowsAffected()
+		t.AssertNil(err)
+		t.Assert(n, 2)
+	})
 }
 
 func Test_DB_BatchInsert_Struct(t *testing.T) {