This is an automated email from the ASF dual-hosted git repository.

zfeng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-seata-go.git


The following commit(s) were added to refs/heads/master by this push:
     new 3b1ebc92 Optimize/at build lock key performance  (#837)
3b1ebc92 is described below

commit 3b1ebc92fe4698f84c7555df877cbb4b4fc2d923
Author: Wiggins <125641755+minat...@users.noreply.github.com>
AuthorDate: Thu Jun 19 10:04:11 2025 +0800

    Optimize/at build lock key performance  (#837)
    
    * Refer to buildlockkey2 optimization #829
    
    * Time complexity O(NM)-> O(NK) about buildlockkey and buildlockkey2  
Increased readability  #829
    
    * update import sort #829
    
    * update Encapsulation into util packages #829
---
 pkg/datasource/sql/exec/at/base_executor.go        |  35 +------
 .../at/base_executor_test.go}                      | 104 +++++++++++++--------
 .../sql/undo/builder/basic_undo_log_builder.go     |  46 +--------
 .../undo/builder/basic_undo_log_builder_test.go    |  19 ++++
 pkg/datasource/sql/util/lockkey.go                 |  75 +++++++++++++++
 5 files changed, 165 insertions(+), 114 deletions(-)

diff --git a/pkg/datasource/sql/exec/at/base_executor.go 
b/pkg/datasource/sql/exec/at/base_executor.go
index 05f44057..cda7d1ac 100644
--- a/pkg/datasource/sql/exec/at/base_executor.go
+++ b/pkg/datasource/sql/exec/at/base_executor.go
@@ -18,7 +18,6 @@
 package at
 
 import (
-       "bytes"
        "context"
        "database/sql"
        "database/sql/driver"
@@ -359,37 +358,5 @@ func (b *baseExecutor) buildPKParams(rows 
[]types.RowImage, pkNameList []string)
 
 // the string as local key. the local key example(multi pk): "t_user:1_a,2_b"
 func (b *baseExecutor) buildLockKey(records *types.RecordImage, meta 
types.TableMeta) string {
-       var (
-               lockKeys      bytes.Buffer
-               filedSequence int
-       )
-       lockKeys.WriteString(meta.TableName)
-       lockKeys.WriteString(":")
-
-       keys := meta.GetPrimaryKeyOnlyName()
-
-       for _, row := range records.Rows {
-               if filedSequence > 0 {
-                       lockKeys.WriteString(",")
-               }
-               pkSplitIndex := 0
-               for _, column := range row.Columns {
-                       var hasKeyColumn bool
-                       for _, key := range keys {
-                               if column.ColumnName == key {
-                                       hasKeyColumn = true
-                                       if pkSplitIndex > 0 {
-                                               lockKeys.WriteString("_")
-                                       }
-                                       lockKeys.WriteString(fmt.Sprintf("%v", 
column.Value))
-                                       pkSplitIndex++
-                               }
-                       }
-                       if hasKeyColumn {
-                               filedSequence++
-                       }
-               }
-       }
-
-       return lockKeys.String()
+       return util.BuildLockKey(records, meta)
 }
diff --git a/pkg/datasource/sql/undo/builder/basic_undo_log_builder_test.go 
b/pkg/datasource/sql/exec/at/base_executor_test.go
similarity index 63%
copy from pkg/datasource/sql/undo/builder/basic_undo_log_builder_test.go
copy to pkg/datasource/sql/exec/at/base_executor_test.go
index 465bf516..0caffe4e 100644
--- a/pkg/datasource/sql/undo/builder/basic_undo_log_builder_test.go
+++ b/pkg/datasource/sql/exec/at/base_executor_test.go
@@ -15,42 +15,16 @@
  * limitations under the License.
  */
 
-package builder
+package at
 
 import (
-       "testing"
-
        "github.com/stretchr/testify/assert"
-
        "seata.apache.org/seata-go/pkg/datasource/sql/types"
+       "testing"
 )
 
-func TestBuildWhereConditionByPKs(t *testing.T) {
-       builder := BasicUndoLogBuilder{}
-       tests := []struct {
-               name       string
-               pkNameList []string
-               rowSize    int
-               maxInSize  int
-               expectSQL  string
-       }{
-               {"test1", []string{"id", "name"}, 1, 1, "(`id`,`name`) IN 
((?,?))"},
-               {"test1", []string{"id", "name"}, 3, 2, "(`id`,`name`) IN 
((?,?),(?,?)) OR (`id`,`name`) IN ((?,?))"},
-               {"test1", []string{"id", "name"}, 3, 1, "(`id`,`name`) IN 
((?,?)) OR (`id`,`name`) IN ((?,?)) OR (`id`,`name`) IN ((?,?))"},
-               {"test1", []string{"id", "name"}, 4, 2, "(`id`,`name`) IN 
((?,?),(?,?)) OR (`id`,`name`) IN ((?,?),(?,?))"},
-       }
-
-       for _, test := range tests {
-               t.Run(test.name, func(t *testing.T) {
-                       // todo add dbType param
-                       sql := 
builder.buildWhereConditionByPKs(test.pkNameList, test.rowSize, "", 
test.maxInSize)
-                       assert.Equal(t, test.expectSQL, sql)
-               })
-       }
-}
-
-func TestBuildLockKey(t *testing.T) {
-       var builder BasicUndoLogBuilder
+func TestBaseExecBuildLockKey(t *testing.T) {
+       var exec baseExecutor
 
        columnID := types.ColumnMeta{
                ColumnName: "id",
@@ -69,6 +43,7 @@ func TestBuildLockKey(t *testing.T) {
        }
 
        columnsTwoPk := []types.ColumnMeta{columnID, columnUserId}
+       columnsThreePk := []types.ColumnMeta{columnID, columnUserId, columnAge}
        columnsMixPk := []types.ColumnMeta{columnName, columnAge}
 
        getColumnImage := func(columnName string, value interface{}) 
types.ColumnImage {
@@ -92,11 +67,29 @@ func TestBuildLockKey(t *testing.T) {
                        types.RecordImage{
                                TableName: "test_name",
                                Rows: []types.RowImage{
-                                       
{[]types.ColumnImage{getColumnImage("id", 1), getColumnImage("userId", "one")}},
-                                       
{[]types.ColumnImage{getColumnImage("id", 2), getColumnImage("userId", "two")}},
+                                       
{[]types.ColumnImage{getColumnImage("id", 1), getColumnImage("userId", 
"user1")}},
+                                       
{[]types.ColumnImage{getColumnImage("id", 2), getColumnImage("userId", 
"user2")}},
+                               },
+                       },
+                       "test_name:1_user1,2_user2",
+               },
+               {
+                       "Three Primary Keys",
+                       types.TableMeta{
+                               TableName: "test2_name",
+                               Indexs: map[string]types.IndexMeta{
+                                       "PRIMARY_KEY": {IType: 
types.IndexTypePrimaryKey, Columns: columnsThreePk},
+                               },
+                       },
+                       types.RecordImage{
+                               TableName: "test2_name",
+                               Rows: []types.RowImage{
+                                       
{[]types.ColumnImage{getColumnImage("id", 1), getColumnImage("userId", "one"), 
getColumnImage("age", "11")}},
+                                       
{[]types.ColumnImage{getColumnImage("id", 2), getColumnImage("userId", "two"), 
getColumnImage("age", "22")}},
+                                       
{[]types.ColumnImage{getColumnImage("id", 3), getColumnImage("userId", 
"three"), getColumnImage("age", "33")}},
                                },
                        },
-                       "test_name:1_one,2_two",
+                       "test2_name:1_one_11,2_two_22,3_three_33",
                },
                {
                        name: "Single Primary Key",
@@ -125,10 +118,10 @@ func TestBuildLockKey(t *testing.T) {
                        records: types.RecordImage{
                                TableName: "mixed_key",
                                Rows: []types.RowImage{
-                                       {Columns: 
[]types.ColumnImage{getColumnImage("name", "Alice"), getColumnImage("age", 
25)}},
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("name", "mike"), getColumnImage("age", 25)}},
                                },
                        },
-                       expected: "mixed_key:Alice_25",
+                       expected: "mixed_key:mike_25",
                },
                {
                        name: "Empty Records",
@@ -152,10 +145,10 @@ func TestBuildLockKey(t *testing.T) {
                        records: types.RecordImage{
                                TableName: "special",
                                Rows: []types.RowImage{
-                                       {Columns: 
[]types.ColumnImage{getColumnImage("id", "a,b_c")}},
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("id", "A,b_c")}},
                                },
                        },
-                       expected: "special:a,b_c",
+                       expected: "special:A,b_c",
                },
                {
                        name: "Non-existent Key Name",
@@ -173,11 +166,46 @@ func TestBuildLockKey(t *testing.T) {
                        },
                        expected: "error_key:",
                },
+               {
+                       name: "Multiple Rows With Nil PK Value",
+                       metaData: types.TableMeta{
+                               TableName: "nil_pk",
+                               Indexs: map[string]types.IndexMeta{
+                                       "PRIMARY_KEY": {IType: 
types.IndexTypePrimaryKey, Columns: []types.ColumnMeta{columnID}},
+                               },
+                       },
+                       records: types.RecordImage{
+                               TableName: "nil_pk",
+                               Rows: []types.RowImage{
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("id", nil)}},
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("id", 123)}},
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("id", nil)}},
+                               },
+                       },
+                       expected: "nil_pk:,123,",
+               },
+               {
+                       name: "PK As Bool And Float",
+                       metaData: types.TableMeta{
+                               TableName: "type_pk",
+                               Indexs: map[string]types.IndexMeta{
+                                       "PRIMARY_KEY": {IType: 
types.IndexTypePrimaryKey, Columns: []types.ColumnMeta{columnName, columnAge}},
+                               },
+                       },
+                       records: types.RecordImage{
+                               TableName: "type_pk",
+                               Rows: []types.RowImage{
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("name", true), getColumnImage("age", 3.14)}},
+                                       {Columns: 
[]types.ColumnImage{getColumnImage("name", false), getColumnImage("age", 0.0)}},
+                               },
+                       },
+                       expected: "type_pk:true_3.14,false_0",
+               },
        }
 
        for _, tt := range tests {
                t.Run(tt.name, func(t *testing.T) {
-                       lockKeys := builder.buildLockKey2(&tt.records, 
tt.metaData)
+                       lockKeys := exec.buildLockKey(&tt.records, tt.metaData)
                        assert.Equal(t, tt.expected, lockKeys)
                })
        }
diff --git a/pkg/datasource/sql/undo/builder/basic_undo_log_builder.go 
b/pkg/datasource/sql/undo/builder/basic_undo_log_builder.go
index 8c5f20f2..d02604b8 100644
--- a/pkg/datasource/sql/undo/builder/basic_undo_log_builder.go
+++ b/pkg/datasource/sql/undo/builder/basic_undo_log_builder.go
@@ -22,12 +22,12 @@ import (
        "database/sql"
        "database/sql/driver"
        "fmt"
-       "io"
-       "strings"
-
        "github.com/arana-db/parser/ast"
        "github.com/arana-db/parser/test_driver"
        gxsort "github.com/dubbogo/gost/sort"
+       "io"
+       "seata.apache.org/seata-go/pkg/datasource/sql/util"
+       "strings"
 
        "seata.apache.org/seata-go/pkg/datasource/sql/types"
 )
@@ -276,43 +276,5 @@ func (b *BasicUndoLogBuilder) buildLockKey(rows 
driver.Rows, meta types.TableMet
 
 // the string as local key. the local key example(multi pk): "t_user:1_a,2_b"
 func (b *BasicUndoLogBuilder) buildLockKey2(records *types.RecordImage, meta 
types.TableMeta) string {
-       var lockKeys bytes.Buffer
-       lockKeys.WriteString(meta.TableName)
-       lockKeys.WriteString(":")
-
-       keys := meta.GetPrimaryKeyOnlyName()
-       keyIndexMap := make(map[string]int, len(keys))
-
-       for idx, columnName := range keys {
-               keyIndexMap[columnName] = idx
-       }
-
-       primaryKeyRows := make([][]interface{}, len(records.Rows))
-
-       for i, row := range records.Rows {
-               primaryKeyValues := make([]interface{}, len(keys))
-               for _, column := range row.Columns {
-                       if idx, exist := keyIndexMap[column.ColumnName]; exist {
-                               primaryKeyValues[idx] = column.Value
-                       }
-               }
-               primaryKeyRows[i] = primaryKeyValues
-       }
-
-       for i, primaryKeyValues := range primaryKeyRows {
-               if i > 0 {
-                       lockKeys.WriteString(",")
-               }
-               for j, pkVal := range primaryKeyValues {
-                       if j > 0 {
-                               lockKeys.WriteString("_")
-                       }
-                       if pkVal == nil {
-                               continue
-                       }
-                       lockKeys.WriteString(fmt.Sprintf("%v", pkVal))
-               }
-       }
-
-       return lockKeys.String()
+       return util.BuildLockKey(records, meta)
 }
diff --git a/pkg/datasource/sql/undo/builder/basic_undo_log_builder_test.go 
b/pkg/datasource/sql/undo/builder/basic_undo_log_builder_test.go
index 465bf516..744f725f 100644
--- a/pkg/datasource/sql/undo/builder/basic_undo_log_builder_test.go
+++ b/pkg/datasource/sql/undo/builder/basic_undo_log_builder_test.go
@@ -69,6 +69,7 @@ func TestBuildLockKey(t *testing.T) {
        }
 
        columnsTwoPk := []types.ColumnMeta{columnID, columnUserId}
+       columnsThreePk := []types.ColumnMeta{columnID, columnUserId, columnAge}
        columnsMixPk := []types.ColumnMeta{columnName, columnAge}
 
        getColumnImage := func(columnName string, value interface{}) 
types.ColumnImage {
@@ -98,6 +99,24 @@ func TestBuildLockKey(t *testing.T) {
                        },
                        "test_name:1_one,2_two",
                },
+               {
+                       "Three Primary Keys",
+                       types.TableMeta{
+                               TableName: "test2_name",
+                               Indexs: map[string]types.IndexMeta{
+                                       "PRIMARY_KEY": {IType: 
types.IndexTypePrimaryKey, Columns: columnsThreePk},
+                               },
+                       },
+                       types.RecordImage{
+                               TableName: "test2_name",
+                               Rows: []types.RowImage{
+                                       
{[]types.ColumnImage{getColumnImage("id", 1), getColumnImage("userId", "one"), 
getColumnImage("age", "11")}},
+                                       
{[]types.ColumnImage{getColumnImage("id", 2), getColumnImage("userId", "two"), 
getColumnImage("age", "22")}},
+                                       
{[]types.ColumnImage{getColumnImage("id", 3), getColumnImage("userId", 
"three"), getColumnImage("age", "33")}},
+                               },
+                       },
+                       "test2_name:1_one_11,2_two_22,3_three_33",
+               },
                {
                        name: "Single Primary Key",
                        metaData: types.TableMeta{
diff --git a/pkg/datasource/sql/util/lockkey.go 
b/pkg/datasource/sql/util/lockkey.go
new file mode 100644
index 00000000..02992982
--- /dev/null
+++ b/pkg/datasource/sql/util/lockkey.go
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Copyright 2016 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package util
+
+import (
+       "fmt"
+       "seata.apache.org/seata-go/pkg/datasource/sql/types"
+       "strings"
+)
+
+func BuildLockKey(records *types.RecordImage, meta types.TableMeta) string {
+       var lockKeys strings.Builder
+       type ColMapItem struct {
+               pkIndex  int
+               colIndex int
+       }
+
+       lockKeys.WriteString(meta.TableName)
+       lockKeys.WriteString(":")
+
+       keys := meta.GetPrimaryKeyOnlyName()
+       keyIndexMap := make(map[string]int, len(keys))
+       for idx, columnName := range keys {
+               keyIndexMap[columnName] = idx
+       }
+
+       columns := make([]ColMapItem, 0, len(keys))
+       if len(records.Rows) > 0 {
+               for colIdx, column := range records.Rows[0].Columns {
+                       if pkIdx, ok := keyIndexMap[column.ColumnName]; ok {
+                               columns = append(columns, ColMapItem{pkIndex: 
pkIdx, colIndex: colIdx})
+                       }
+               }
+               for i, row := range records.Rows {
+                       if i > 0 {
+                               lockKeys.WriteString(",")
+                       }
+                       primaryKeyValues := make([]interface{}, len(keys))
+                       for _, mp := range columns {
+                               if mp.colIndex < len(row.Columns) {
+                                       primaryKeyValues[mp.pkIndex] = 
row.Columns[mp.colIndex].Value
+                               }
+                       }
+                       for j, pkVal := range primaryKeyValues {
+                               if j > 0 {
+                                       lockKeys.WriteString("_")
+                               }
+                               if pkVal == nil {
+                                       continue
+                               }
+                               lockKeys.WriteString(fmt.Sprintf("%v", pkVal))
+                       }
+               }
+       }
+       return lockKeys.String()
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscr...@seata.apache.org
For additional commands, e-mail: notifications-h...@seata.apache.org

Reply via email to