写写冲突日志问题

TiDB 发生了写写冲突,但是无法定位是哪个表的哪个索引导致的,参考了这篇文章但是日志输出的并不是文档中提及的,文档中的

[2020/05/12 15:17:01.568 +08:00] [WARN] [session.go:446] ["commit failed"] [conn=3] ["finished txn"="Txn{state=invalid}"] [error="[kv:9007]Write conflict, txnStartTS=416617006551793665, conflictStartTS=416617018650001409, conflictCommitTS=416617023093080065, key={tableID=47, indexID=1, indexValues={string, }} primary={tableID=47, indexID=1, indexValues={string, }} [try again later]"]

生产中的

[txn.go:83] [RunInNewTxn] ["retry txn"=442462672668854215] ["original txn"=442462672668854215] [error="[kv:9007]Write conflict, txnStartTS=442462672668854215, conflictStartTS=442462672668854858, conflictCommitTS=442462672668855267, key=[]byte{0x6d, 0x4e, 0x65, 0x78, 0x74, 0x47, 0x6c, 0x6f, 0x62, 0xff, 0x61, 0x6c, 0x49, 0x44, 0x0, 0x0, 0x0, 0x0, 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x73} primary=[]byte{0x6d, 0x4e, 0x65, 0x78, 0x74, 0x47, 0x6c, 0x6f, 0x62, 0xff, 0x61, 0x6c, 0x49, 0x44, 0x0, 0x0, 0x0, 0x0, 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x73} [try again later]"]

又翻看了代码,貌似是走到了 prettyWriteKey 函数中的这句话 mKey, mField, err := tablecodec.DecodeMetaKey(key) ,请问这串 16 进制编码是什么意思?
[]byte{0x6d, 0x4e, 0x65, 0x78, 0x74, 0x47, 0x6c, 0x6f, 0x62, 0xff, 0x61, 0x6c, 0x49, 0x44, 0x0, 0x0, 0x0, 0x0, 0xfb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x73}

func newWriteConflictError(conflict *kvrpcpb.WriteConflict) error {
	if conflict == nil {
		return kv.ErrWriteConflict
	}
	var buf bytes.Buffer
	prettyWriteKey(&buf, conflict.Key)
	buf.WriteString(" primary=")
	prettyWriteKey(&buf, conflict.Primary)
	return kv.ErrWriteConflict.FastGenByArgs(conflict.StartTs, conflict.ConflictTs, conflict.ConflictCommitTs, buf.String())
}

func prettyWriteKey(buf *bytes.Buffer, key []byte) {
	tableID, indexID, indexValues, err := tablecodec.DecodeIndexKey(key)
	if err == nil {
		_, err1 := fmt.Fprintf(buf, "{tableID=%d, indexID=%d, indexValues={", tableID, indexID)
		if err1 != nil {
			logutil.BgLogger().Error("error", zap.Error(err1))
		}
		for _, v := range indexValues {
			_, err2 := fmt.Fprintf(buf, "%s, ", v)
			if err2 != nil {
				logutil.BgLogger().Error("error", zap.Error(err2))
			}
		}
		buf.WriteString("}}")
		return
	}

	tableID, handle, err := tablecodec.DecodeRecordKey(key)
	if err == nil {
		_, err3 := fmt.Fprintf(buf, "{tableID=%d, handle=%d}", tableID, handle)
		if err3 != nil {
			logutil.BgLogger().Error("error", zap.Error(err3))
		}
		return
	}

	mKey, mField, err := tablecodec.DecodeMetaKey(key)
	if err == nil {
		_, err3 := fmt.Fprintf(buf, "{metaKey=true, key=%s, field=%s}", string(mKey), string(mField))
		if err3 != nil {
			logutil.Logger(context.Background()).Error("error", zap.Error(err3))
		}
		return
	}

	_, err4 := fmt.Fprintf(buf, "%#v", key)
	if err4 != nil {
		logutil.BgLogger().Error("error", zap.Error(err4))
	}
}

参考文档啊,通过 tableID=47 ,先识别 table,在看看业务上有没有合适的处理方案

https://docs.pingcap.com/zh/tidb/stable/troubleshoot-write-conflicts#如何解决写写冲突问题

问题就是从日志中无法辨别 tableID

TiDB文档中的:
key={tableID=47, indexID=1, indexValues={string, }}

实际的log
key=[]byte{0x6d, 0x4e, 0x65, 0x78, 0x74, 0x47, 0x6c, xxx

代码我没仔细看,不过我有个猜测。
我把你上面这个key从数组里面组装出来,碰到0x0就按照00处理。
结果组装出来的key如下:

6d4e657874476c6f62ff616c494400000000fb0000000000000073

我又把它和INFORMATION_SCHEMA.TIKV_REGION_STATUS表里的key对比了一下,位数能对的上。

那是否可以用这个key尝试在start_key和end_key之间定位到region_id ,table_id进而找到对应的表是什么。

这个日志有可能精准的表示了某个特定的key值发生了写写冲突。只不过翻译到表id的时候,发生了意外。

如果猜测是正确的,那执行下面这个sql,应该能找到点东西:

select * from INFORMATION_SCHEMA.TIKV_REGION_STATUS trs where trs.START_KEY like '6D4E657874476C6F62FF616C494400000000%'

如果没找到再缩小一下范围,当然如果开头几个字母都匹配不到,那基本就是猜测不靠谱了。 :sweat_smile:

生产中的日志,上下文里有。找找

我拿Java 的Hex 给你做了个翻译,
6D4E657874476C6F62FF616C494400000000FB0000000000000073

@有猫万事足 可以验证你的猜想…

你可以试试看…

运行结果:
image

1 个赞

AI 小助手给了一些回复,可以参考下:


在 TiDB 中,table id 是一个 uint64 类型的数字,但在日志中,它通常以 16 进制的形式出现。如果您需要将 16 进制的 table id 转换为十进制的数字,可以使用编程语言中的函数或在线工具进行转换。

例如,在 Go 语言中,可以使用 strconv.ParseUint() 函数将 16 进制的字符串转换为 uint64 类型的数字。示例代码如下:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	hexStr := "3807"
	tableID, err := strconv.ParseUint(hexStr, 16, 64)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Println(tableID)
}

输出结果为:

14343

其中,strconv.ParseUint() 函数的第一个参数是要转换的 16 进制字符串,第二个参数是要转换的进制数(这里是 16 进制),第三个参数是转换后的数字类型(这里是 uint64)。


在 TiDB 中,primary key 是一个字节数组([]byte)类型,但在日志中,它通常以 JSON 格式的字符串形式出现。如果您需要将日志中的 primary key 转换为字节数组,或者将字节数组转换为 JSON 格式的字符串,可以使用编程语言中的函数进行转换。

例如,在 Go 语言中,可以使用 json.Marshal() 函数将字节数组转换为 JSON 格式的字符串,使用 json.Unmarshal() 函数将 JSON 格式的字符串转换为字节数组。示例代码如下:

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	// 将字节数组转换为 JSON 格式的字符串
	primaryKey := []byte{0x6d, 0x79, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79}
	primaryKeyJSON, err := json.Marshal(primaryKey)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Println(string(primaryKeyJSON)) // 输出:"[109,121,80,114,105,109,97,114,121]"

	// 将 JSON 格式的字符串转换为字节数组
	primaryKeyJSON = []byte(`[109,121,80,114,105,109,97,114,121]`)
	var primaryKey2 []byte
	err = json.Unmarshal(primaryKeyJSON, &primaryKey2)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	fmt.Println(primaryKey2) // 输出:[109 121 80 114 105 109 97 114 121]
}

其中,json.Marshal() 函数将字节数组转换为 JSON 格式的字符串,json.Unmarshal() 函数将 JSON 格式的字符串转换为字节数组。在 json.Unmarshal() 函数中,第二个参数是一个指向目标变量的指针,用于存储解析后的结果。

1 个赞

https://github.com/pingcap/docs-cn/blob/release-7.1/troubleshoot-write-conflicts.md