Skip to content

Commit 698f90e

Browse files
authored
import: check table empty skips using index (pingcap#39604) (pingcap#40669)
close pingcap#39477
1 parent 97bc4ef commit 698f90e

File tree

8 files changed

+96
-16
lines changed

8 files changed

+96
-16
lines changed

br/pkg/lightning/restore/check_info_test.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -493,11 +493,11 @@ func TestCheckTableEmpty(t *testing.T) {
493493
require.NoError(t, err)
494494
mock.MatchExpectationsInOrder(false)
495495
targetInfoGetter.targetDBGlue = glue.NewExternalTiDBGlue(db, mysql.ModeNone)
496-
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl1` LIMIT 1").
496+
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl1` USE INDEX\\(\\) LIMIT 1").
497497
WillReturnRows(sqlmock.NewRows([]string{""}).RowError(0, sql.ErrNoRows))
498-
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl2` LIMIT 1").
498+
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl2` USE INDEX\\(\\) LIMIT 1").
499499
WillReturnRows(sqlmock.NewRows([]string{""}).RowError(0, sql.ErrNoRows))
500-
mock.ExpectQuery("SELECT 1 FROM `test2`.`tbl1` LIMIT 1").
500+
mock.ExpectQuery("SELECT 1 FROM `test2`.`tbl1` USE INDEX\\(\\) LIMIT 1").
501501
WillReturnRows(sqlmock.NewRows([]string{""}).RowError(0, sql.ErrNoRows))
502502
rc.checkTemplate = NewSimpleTemplate()
503503
err = rc.checkTableEmpty(ctx)
@@ -510,13 +510,13 @@ func TestCheckTableEmpty(t *testing.T) {
510510
targetInfoGetter.targetDBGlue = glue.NewExternalTiDBGlue(db, mysql.ModeNone)
511511
mock.MatchExpectationsInOrder(false)
512512
// test auto retry retryable error
513-
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl1` LIMIT 1").
513+
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl1` USE INDEX\\(\\) LIMIT 1").
514514
WillReturnError(&gmysql.MySQLError{Number: errno.ErrPDServerTimeout})
515-
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl1` LIMIT 1").
515+
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl1` USE INDEX\\(\\) LIMIT 1").
516516
WillReturnRows(sqlmock.NewRows([]string{""}).RowError(0, sql.ErrNoRows))
517-
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl2` LIMIT 1").
517+
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl2` USE INDEX\\(\\) LIMIT 1").
518518
WillReturnRows(sqlmock.NewRows([]string{""}).RowError(0, sql.ErrNoRows))
519-
mock.ExpectQuery("SELECT 1 FROM `test2`.`tbl1` LIMIT 1").
519+
mock.ExpectQuery("SELECT 1 FROM `test2`.`tbl1` USE INDEX\\(\\) LIMIT 1").
520520
WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(1))
521521
rc.checkTemplate = NewSimpleTemplate()
522522
err = rc.checkTableEmpty(ctx)
@@ -532,11 +532,11 @@ func TestCheckTableEmpty(t *testing.T) {
532532
require.NoError(t, err)
533533
targetInfoGetter.targetDBGlue = glue.NewExternalTiDBGlue(db, mysql.ModeNone)
534534
mock.MatchExpectationsInOrder(false)
535-
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl1` LIMIT 1").
535+
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl1` USE INDEX\\(\\) LIMIT 1").
536536
WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(1))
537-
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl2` LIMIT 1").
537+
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl2` USE INDEX\\(\\) LIMIT 1").
538538
WillReturnRows(sqlmock.NewRows([]string{""}).RowError(0, sql.ErrNoRows))
539-
mock.ExpectQuery("SELECT 1 FROM `test2`.`tbl1` LIMIT 1").
539+
mock.ExpectQuery("SELECT 1 FROM `test2`.`tbl1` USE INDEX\\(\\) LIMIT 1").
540540
WillReturnRows(sqlmock.NewRows([]string{""}).AddRow(1))
541541
rc.checkTemplate = NewSimpleTemplate()
542542
err = rc.checkTableEmpty(ctx)
@@ -576,7 +576,7 @@ func TestCheckTableEmpty(t *testing.T) {
576576
require.NoError(t, err)
577577
targetInfoGetter.targetDBGlue = glue.NewExternalTiDBGlue(db, mysql.ModeNone)
578578
// only need to check the one that is not in checkpoint
579-
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl2` LIMIT 1").
579+
mock.ExpectQuery("SELECT 1 FROM `test1`.`tbl2` USE INDEX\\(\\) LIMIT 1").
580580
WillReturnRows(sqlmock.NewRows([]string{""}).RowError(0, sql.ErrNoRows))
581581
err = rc.checkTableEmpty(ctx)
582582
require.NoError(t, err)

br/pkg/lightning/restore/get_pre_info.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,12 @@ func (g *TargetInfoGetterImpl) IsTableEmpty(ctx context.Context, schemaName stri
189189
}
190190
var dump int
191191
err = exec.QueryRow(ctx, "check table empty",
192-
fmt.Sprintf("SELECT 1 FROM %s LIMIT 1", common.UniqueTable(schemaName, tableName)),
192+
// Here we use the `USE INDEX()` hint to skip fetch the record from index.
193+
// In Lightning, if previous importing is halted half-way, it is possible that
194+
// the data is partially imported, but the index data has not been imported.
195+
// In this situation, if no hint is added, the SQL executor might fetch the record from index,
196+
// which is empty. This will result in missing check.
197+
fmt.Sprintf("SELECT 1 FROM %s USE INDEX() LIMIT 1", common.UniqueTable(schemaName, tableName)),
193198
&dump,
194199
)
195200

br/pkg/lightning/restore/get_pre_info_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ func TestGetPreInfoIsTableEmpty(t *testing.T) {
762762
require.NoError(t, err)
763763
require.Equal(t, lnConfig, targetGetter.cfg)
764764

765-
mock.ExpectQuery("SELECT 1 FROM `test_db`.`test_tbl` LIMIT 1").
765+
mock.ExpectQuery("SELECT 1 FROM `test_db`.`test_tbl` USE INDEX\\(\\) LIMIT 1").
766766
WillReturnError(&mysql_sql_driver.MySQLError{
767767
Number: errno.ErrNoSuchTable,
768768
Message: "Table 'test_db.test_tbl' doesn't exist",
@@ -772,7 +772,7 @@ func TestGetPreInfoIsTableEmpty(t *testing.T) {
772772
require.NotNil(t, pIsEmpty)
773773
require.Equal(t, true, *pIsEmpty)
774774

775-
mock.ExpectQuery("SELECT 1 FROM `test_db`.`test_tbl` LIMIT 1").
775+
mock.ExpectQuery("SELECT 1 FROM `test_db`.`test_tbl` USE INDEX\\(\\) LIMIT 1").
776776
WillReturnRows(
777777
sqlmock.NewRows([]string{"1"}).
778778
RowError(0, sql.ErrNoRows),
@@ -782,7 +782,7 @@ func TestGetPreInfoIsTableEmpty(t *testing.T) {
782782
require.NotNil(t, pIsEmpty)
783783
require.Equal(t, true, *pIsEmpty)
784784

785-
mock.ExpectQuery("SELECT 1 FROM `test_db`.`test_tbl` LIMIT 1").
785+
mock.ExpectQuery("SELECT 1 FROM `test_db`.`test_tbl` USE INDEX\\(\\) LIMIT 1").
786786
WillReturnRows(
787787
sqlmock.NewRows([]string{"1"}).AddRow(1),
788788
)
@@ -791,7 +791,7 @@ func TestGetPreInfoIsTableEmpty(t *testing.T) {
791791
require.NotNil(t, pIsEmpty)
792792
require.Equal(t, false, *pIsEmpty)
793793

794-
mock.ExpectQuery("SELECT 1 FROM `test_db`.`test_tbl` LIMIT 1").
794+
mock.ExpectQuery("SELECT 1 FROM `test_db`.`test_tbl` USE INDEX\\(\\) LIMIT 1").
795795
WillReturnError(errors.New("some dummy error"))
796796
_, err = targetGetter.IsTableEmpty(ctx, "test_db", "test_tbl")
797797
require.Error(t, err)

br/pkg/lightning/restore/table_restore.go

+5
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,11 @@ func (tr *TableRestore) restoreEngines(pCtx context.Context, rc *Controller, cp
384384
if cp.Status < checkpoints.CheckpointStatusIndexImported {
385385
var err error
386386
if indexEngineCp.Status < checkpoints.CheckpointStatusImported {
387+
failpoint.Inject("FailBeforeStartImportingIndexEngine", func() {
388+
errMsg := "fail before importing index KV data"
389+
tr.logger.Warn(errMsg)
390+
failpoint.Return(errors.New(errMsg))
391+
})
387392
err = tr.importKV(ctx, closedIndexEngine, rc, indexEngineID)
388393
failpoint.Inject("FailBeforeIndexEngineImported", func() {
389394
finished := rc.status.FinishedFileSize.Load()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[tikv-importer]
2+
backend = "local"
3+
4+
[mydumper.csv]
5+
header = true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CREATE TABLE tbl01 (
2+
`id` INTEGER,
3+
`val` VARCHAR(64),
4+
`aaa` CHAR(66) DEFAULT NULL,
5+
`bbb` CHAR(10) NOT NULL,
6+
`ccc` CHAR(42) DEFAULT NULL,
7+
`ddd` CHAR(42) DEFAULT NULL,
8+
`eee` CHAR(66) DEFAULT NULL,
9+
`fff` VARCHAR(128) DEFAULT NULL,
10+
KEY `aaa` (`aaa`),
11+
PRIMARY KEY (`id`)
12+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
id,val,aaa,bbb,ccc,ddd,eee,fff
2+
1,"v01","a01","b01","c01","d01","e01","f01"
3+
2,"v02","a02","b02","c02","d02","e02","f02"
4+
3,"v03","a03","b03","c03","d03","e03","f03"
5+
4,"v04","a04","b04","c04","d04","e04","f04"
6+
5,"v05","a05","b05","c05","d05","e05","f05"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/bin/bash
2+
#
3+
# Copyright 2022 PingCAP, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
MYDIR=$(dirname "${BASH_SOURCE[0]}")
18+
set -eux
19+
20+
check_cluster_version 4 0 0 'local backend' || exit 0
21+
22+
export GO_FAILPOINTS="github.com/pingcap/tidb/br/pkg/lightning/restore/FailBeforeStartImportingIndexEngine=return"
23+
set +e
24+
if run_lightning; then
25+
echo "The first import doesn't fail as expected" >&2
26+
exit 1
27+
fi
28+
set -e
29+
30+
data_records=$(tail -n +2 "${MYDIR}/data/db01.tbl01.csv" | wc -l | xargs echo )
31+
run_sql "SELECT COUNT(*) FROM db01.tbl01 USE INDEX();"
32+
check_contains "${data_records}"
33+
34+
export GO_FAILPOINTS=""
35+
set +e
36+
if run_lightning --check-requirements=1; then
37+
echo "The pre-check doesn't find out the non-empty table problem"
38+
exit 2
39+
fi
40+
set -e
41+
42+
run_sql "TRUNCATE TABLE db01.tbl01;"
43+
run_lightning --check-requirements=1
44+
run_sql "SELECT COUNT(*) FROM db01.tbl01;"
45+
check_contains "${data_records}"
46+
run_sql "SELECT COUNT(*) FROM db01.tbl01 USE INDEX();"
47+
check_contains "${data_records}"

0 commit comments

Comments
 (0)