Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: Check index number in txn #29453

Closed
wants to merge 14 commits into from
1 change: 1 addition & 0 deletions errno/errcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,7 @@ const (
ErrAsOf = 8135
ErrVariableNoLongerSupported = 8136
ErrAnalyzeMissColumn = 8137
ErrIncorrectIndexCount = 8140

// Error codes used by TiDB ddl package
ErrUnsupportedDDLOperation = 8200
Expand Down
1 change: 1 addition & 0 deletions errno/errname.go
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,7 @@ var MySQLErrName = map[uint16]*mysql.ErrMessage{
ErrInvalidIncrementAndOffset: mysql.Message("Invalid auto_increment settings: auto_increment_increment: %d, auto_increment_offset: %d, both of them must be in range [1..65535]", nil),
ErrDataInconsistentMismatchCount: mysql.Message("data inconsistency in table: %s, index: %s, index-count:%d != record-count:%d", nil),
ErrDataInconsistentMismatchIndex: mysql.Message("data inconsistency in table: %s, index: %s, col: %s, handle: %#v, index-values:%#v != record-values:%#v, compare err:%#v", []int{3, 4, 5, 6}),
ErrIncorrectIndexCount: mysql.Message("incorrect index count in txn, row insertions: %d, index insertions: %d", nil),

ErrWarnOptimizerHintInvalidInteger: mysql.Message("integer value is out of range in '%s'", nil),
ErrWarnOptimizerHintUnsupportedHint: mysql.Message("Optimizer hint %s is not supported by TiDB and is ignored", nil),
Expand Down
5 changes: 5 additions & 0 deletions errors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1801,6 +1801,11 @@ error = '''
column %s can't be in none state
'''

["table:8140"]
error = '''
incorrect index count in txn, row insertions: %d, index insertions: %d
'''

["tikv:1105"]
error = '''
Unknown error
Expand Down
11 changes: 11 additions & 0 deletions session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import (
"github.com/pingcap/tidb/statistics"
"github.com/pingcap/tidb/statistics/handle"
storeerr "github.com/pingcap/tidb/store/driver/error"
"github.com/pingcap/tidb/table/tables"
"github.com/pingcap/tidb/tablecodec"
"github.com/pingcap/tidb/telemetry"
"github.com/pingcap/tidb/types"
Expand Down Expand Up @@ -564,6 +565,16 @@ func (s *session) doCommit(ctx context.Context) error {

func (s *session) commitTxnWithTemporaryData(ctx context.Context, txn kv.Transaction) error {
sessVars := s.sessionVars

// Note: ignore auto-commit transactions because auto-commit transactions don't write untouched index mutations
// into the mem buffer, which invalidates the check.
// Only applies to user DML transactions.
if sessVars.ConnectionID != 0 && sessVars.EnableMutationChecker && sessVars.TxnCtx.IsExplicit {
if err := tables.CheckTxnConsistency(txn); err != nil {
ekexium marked this conversation as resolved.
Show resolved Hide resolved
return errors.Trace(err)
}
}

txnTempTables := sessVars.TxnCtx.TemporaryTables
if len(txnTempTables) == 0 {
return txn.Commit(ctx)
Expand Down
38 changes: 38 additions & 0 deletions table/tables/mutation_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@ import (
"fmt"

"github.com/pingcap/errors"
"github.com/pingcap/tidb/errno"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/parser/model"
"github.com/pingcap/tidb/sessionctx/stmtctx"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/table"
"github.com/pingcap/tidb/tablecodec"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/dbterror"
"github.com/pingcap/tidb/util/logutil"
"github.com/pingcap/tidb/util/rowcodec"
"go.uber.org/zap"
)

// ErrIncorrectIndexCount indicates that #(index_insertion) is not a multiple of #(row_insertion).
var ErrIncorrectIndexCount = dbterror.ClassTable.NewStd(errno.ErrIncorrectIndexCount)

type mutation struct {
key kv.Key
flags kv.KeyFlags
Expand Down Expand Up @@ -375,3 +380,36 @@ func getOrBuildColumnMaps(
}
return maps
}

// CheckTxnConsistency checks the number of row/index mutations before the txn commits,
// to prevent some inconsistent transactions.
func CheckTxnConsistency(txn kv.Transaction) error {
memBuffer := txn.GetMemBuffer()
if memBuffer == nil {
return nil
}

// count for each table
indexInsertionCount := make(map[int64]int)
rowInsertionCount := make(map[int64]int)
f := func(k kv.Key, v []byte) error {
if len(v) > 0 {
tableID := tablecodec.DecodeTableID(k)
if rowcodec.IsRowKey(k) {
rowInsertionCount[tableID] += 1
} else {
indexInsertionCount[tableID] += 1
}
}
return nil
}
if err := kv.WalkMemBuffer(memBuffer, f); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it process entries that's not modified but pessimistic-locked? Can it work with this change?

Copy link
Contributor

@cfzjywxk cfzjywxk Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it will process the locked but not changed keys, as each unique index key used to fetch rowkey will be turned into a PUT record, seems they are not compatibe, consider this:

create table t1(c1 int key, c2 int, c3 int, unique key uk(c2), key k1(c3));
insert into t1 values(1, 1, 1),(2, 2, 2),(3, 3, 3);
update t1 set c3 = c3 + 1 where c1 in (2, 3); // Use the batch point get executor.
here the to be committed keys are:
PUT uk 1->1 uk 2->2 uk 3->3
PUT sk  3_2 -> 2
PUT sk  4_3 -> 3
DEL sk   3_3 -> 3
DEL sk   2_2  -> 2
PUT rowkey  3-> 3, 3, 4
PUT rowkey  2-> 2, 2, 3

The number of PUT on row is 2 and the number of PUT on index is 5?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ekexium
Seems @MyonKeminta has found a key point.

Copy link
Member Author

@ekexium ekexium Dec 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right.. it's an intentional special case. It seems to me we can't simply tell whether it comes from the optimization. A workaround might be adding a flag to indicate the special usage so they can be ignored in the check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is actually not expected and it's a temoprary solution to workaround the performance issues with many LOCK records in the write cf. Maybe we could put back this check after solving the LOCK record issue so that these tricky optimization or transformation could be removed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking if there is a method to distinguish the usage and won't block removing it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have to add some new flags to membuffer to handle this, I would suspect whether it worths...

return errors.Trace(err)
}
for tableID, count := range indexInsertionCount {
if rowInsertionCount[tableID] > 0 && count%rowInsertionCount[tableID] != 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add some comments to explain the proof or invariant here.

return ErrIncorrectIndexCount.GenWithStackByArgs(rowInsertionCount[tableID], count)
}
}
return nil
}
1 change: 1 addition & 0 deletions util/admin/admin_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func TestAdminCheckTableCorrupted(t *testing.T) {
store, clean := testkit.CreateMockStore(t)
defer clean()
tk := testkit.NewTestKit(t, store)
tk.MustExec("set @@tidb_enable_mutation_checker = 0;")
tk.MustExec("use test")
tk.MustExec("drop table if exists t")
tk.MustExec("create table t(id int, v int, UNIQUE KEY i1(id, v))")
Expand Down