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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]