|
| 1 | +// Copyright 2025 PingCAP, Inc. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +package tests |
| 16 | + |
| 17 | +import ( |
| 18 | + "fmt" |
| 19 | + "testing" |
| 20 | + "time" |
| 21 | + |
| 22 | + "github.com/pingcap/tidb/pkg/bindinfo" |
| 23 | + "github.com/pingcap/tidb/pkg/testkit" |
| 24 | + "github.com/stretchr/testify/require" |
| 25 | +) |
| 26 | + |
| 27 | +func TestBindUsageInfo(t *testing.T) { |
| 28 | + checklist := []string{ |
| 29 | + "5ce1df6eadf8b24222668b1bd2e995b72d4c88e6fe9340d8b13e834703e28c32", |
| 30 | + "5d3975ef2160c1e0517353798dac90a9914095d82c025e7cd97bd55aeb804798", |
| 31 | + "9d3995845aef70ba086d347f38a4e14c9705e966f7c5793b9fa92194bca2bbef", |
| 32 | + "aa3c510b94b9d680f729252ca88415794c8a4f52172c5f9e06c27bee57d08329", |
| 33 | + } |
| 34 | + bindinfo.UpdateBindingUsageInfoBatchSize = 2 |
| 35 | + bindinfo.MaxWriteInterval = 100 * time.Microsecond |
| 36 | + store, dom := testkit.CreateMockStoreAndDomain(t) |
| 37 | + bindingHandle := dom.BindingHandle() |
| 38 | + tk := testkit.NewTestKit(t, store) |
| 39 | + |
| 40 | + tk.MustExec(`use test`) |
| 41 | + tk.MustExec(`set @@tidb_opt_enable_fuzzy_binding=1`) |
| 42 | + tk.MustExec("create table t1(a int, b int, c int, key idx_b(b), key idx_c(c))") |
| 43 | + tk.MustExec("create table t2(a int, b int, c int, key idx_b(b), key idx_c(c))") |
| 44 | + tk.MustExec("create table t3(a int, b int, c int, key idx_b(b), key idx_c(c))") |
| 45 | + tk.MustExec("create table t4(a int, b int, c int, key idx_b(b), key idx_c(c))") |
| 46 | + tk.MustExec("create table t5(a int, b int, c int, key idx_b(b), key idx_c(c))") |
| 47 | + |
| 48 | + tk.MustExec("prepare stmt1 from 'delete from t1 where b = 1 and c > 1';") |
| 49 | + tk.MustExec("prepare stmt2 from 'delete t1, t2 from t1 inner join t2 on t1.b = t2.b';") |
| 50 | + tk.MustExec("prepare stmt3 from 'update t1 set a = 1 where b = 1 and c > 1';") |
| 51 | + tk.MustExec("execute stmt1;") |
| 52 | + tk.MustExec("create global binding for delete from t1 where b = 1 and c > 1 using delete /*+ use_index(t1,idx_c) */ from t1 where b = 1 and c > 1") |
| 53 | + tk.MustExec("create global binding for delete t1, t2 from t1 inner join t2 on t1.b = t2.b using delete /*+ inl_join(t1) */ t1, t2 from t1 inner join t2 on t1.b = t2.b") |
| 54 | + tk.MustExec("create global binding for update t1 set a = 1 where b = 1 and c > 1 using update /*+ use_index(t1,idx_c) */ t1 set a = 1 where b = 1 and c > 1") |
| 55 | + // cross database binding |
| 56 | + tk.MustExec(`create global binding using select /*+ leading(t1, t2, t3, t4, t5) */ * from *.t1, *.t2, *.t3, *.t4, *.t5`) |
| 57 | + tk.MustExec("select * from t1, t2, t3, t4, t5") |
| 58 | + tk.MustExec("execute stmt1;") |
| 59 | + origin := tk.MustQuery(fmt.Sprintf(`select sql_digest,last_used_date from mysql.bind_info where original_sql != '%s' order by sql_digest`, bindinfo.BuiltinPseudoSQL4BindLock)) |
| 60 | + origin.Check(testkit.Rows( |
| 61 | + "5ce1df6eadf8b24222668b1bd2e995b72d4c88e6fe9340d8b13e834703e28c32 <nil>", |
| 62 | + "5d3975ef2160c1e0517353798dac90a9914095d82c025e7cd97bd55aeb804798 <nil>", |
| 63 | + "9d3995845aef70ba086d347f38a4e14c9705e966f7c5793b9fa92194bca2bbef <nil>", |
| 64 | + "aa3c510b94b9d680f729252ca88415794c8a4f52172c5f9e06c27bee57d08329 <nil>")) |
| 65 | + time.Sleep(50 * time.Microsecond) |
| 66 | + require.NoError(t, bindingHandle.UpdateBindingUsageInfoToStorage()) |
| 67 | + result := tk.MustQuery(fmt.Sprintf(`select sql_digest,last_used_date from mysql.bind_info where original_sql != '%s' order by sql_digest`, bindinfo.BuiltinPseudoSQL4BindLock)) |
| 68 | + t.Log("result:", result.Rows()) |
| 69 | + // The last_used_date should be updated. |
| 70 | + require.True(t, !origin.Equal(result.Rows())) |
| 71 | + var first *testkit.Result |
| 72 | + for range 5 { |
| 73 | + tk.MustExec("execute stmt1;") |
| 74 | + tk.MustExec("execute stmt2;") |
| 75 | + tk.MustExec("execute stmt3;") |
| 76 | + tk.MustExec("select * from t1, t2, t3, t4, t5") |
| 77 | + time.Sleep(1 * time.Second) |
| 78 | + // Set all last_used_date to null to simulate that the bindinfo in storage is not updated. |
| 79 | + resetAllLastUsedData(tk) |
| 80 | + require.NoError(t, bindingHandle.UpdateBindingUsageInfoToStorage()) |
| 81 | + checkBindinfoInMemory(t, bindingHandle, checklist) |
| 82 | + tk.MustQuery(fmt.Sprintf(`select last_used_date from mysql.bind_info where original_sql != '%s' and last_used_date is null`, bindinfo.BuiltinPseudoSQL4BindLock)).Check(testkit.Rows()) |
| 83 | + result := tk.MustQuery(fmt.Sprintf(`select sql_digest,last_used_date from mysql.bind_info where original_sql != '%s' order by sql_digest`, bindinfo.BuiltinPseudoSQL4BindLock)) |
| 84 | + t.Log("result:", result.Rows()) |
| 85 | + if first == nil { |
| 86 | + first = result |
| 87 | + } else { |
| 88 | + // in fact, The result of each for-loop should be the same. |
| 89 | + require.True(t, first.Equal(result.Rows())) |
| 90 | + } |
| 91 | + } |
| 92 | + // Set all last_used_date to null to simulate that the bindinfo in storage is not updated. |
| 93 | + resetAllLastUsedData(tk) |
| 94 | + for range 5 { |
| 95 | + time.Sleep(1 * time.Second) |
| 96 | + // No used, so last_used_date should not be updated. |
| 97 | + require.NoError(t, bindingHandle.UpdateBindingUsageInfoToStorage()) |
| 98 | + tk.MustQuery(`select last_used_date from mysql.bind_info where last_used_date is not null`).Check(testkit.Rows()) |
| 99 | + } |
| 100 | + tk.MustExec("execute stmt1;") |
| 101 | + tk.MustExec("execute stmt2;") |
| 102 | + tk.MustExec("execute stmt3;") |
| 103 | + time.Sleep(1 * time.Second) |
| 104 | + require.NoError(t, bindingHandle.UpdateBindingUsageInfoToStorage()) |
| 105 | + // it has been updated again. |
| 106 | + rows := tk.MustQuery( |
| 107 | + fmt.Sprintf(`select * from mysql.bind_info where original_sql != '%s' and last_used_date is not null`, bindinfo.BuiltinPseudoSQL4BindLock)).Rows() |
| 108 | + require.Len(t, rows, 3) |
| 109 | +} |
| 110 | + |
| 111 | +func resetAllLastUsedData(tk *testkit.TestKit) { |
| 112 | + tk.MustExec(fmt.Sprintf(`update mysql.bind_info set last_used_date = null where original_sql != '%s'`, bindinfo.BuiltinPseudoSQL4BindLock)) |
| 113 | +} |
| 114 | + |
| 115 | +func checkBindinfoInMemory(t *testing.T, bindingHandle bindinfo.BindingHandle, checklist []string) { |
| 116 | + for _, digest := range checklist { |
| 117 | + binding := bindingHandle.GetBinding(digest) |
| 118 | + require.NotNil(t, binding) |
| 119 | + lastSaved := binding.UsageInfo.LastSavedAt.Load() |
| 120 | + if lastSaved != nil { |
| 121 | + require.GreaterOrEqual(t, *binding.UsageInfo.LastSavedAt.Load(), *binding.UsageInfo.LastUsedAt.Load()) |
| 122 | + } |
| 123 | + } |
| 124 | +} |
0 commit comments