1267 lines
44 KiB
Go
1267 lines
44 KiB
Go
// Copyright 2017 PingCAP, Inc.
|
|
//
|
|
// Licensed 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.
|
|
|
|
package core_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
. "github.com/pingcap/check"
|
|
"github.com/pingcap/errors"
|
|
"github.com/pingcap/tidb/config"
|
|
"github.com/pingcap/tidb/domain"
|
|
"github.com/pingcap/tidb/executor"
|
|
"github.com/pingcap/tidb/kv"
|
|
"github.com/pingcap/tidb/parser/model"
|
|
"github.com/pingcap/tidb/planner"
|
|
"github.com/pingcap/tidb/planner/core"
|
|
"github.com/pingcap/tidb/session"
|
|
"github.com/pingcap/tidb/sessionctx"
|
|
"github.com/pingcap/tidb/sessionctx/variable"
|
|
"github.com/pingcap/tidb/statistics"
|
|
"github.com/pingcap/tidb/statistics/handle"
|
|
"github.com/pingcap/tidb/store/mockstore"
|
|
"github.com/pingcap/tidb/util/testkit"
|
|
"github.com/pingcap/tidb/util/testleak"
|
|
"github.com/pingcap/tidb/util/testutil"
|
|
)
|
|
|
|
var _ = Suite(&testAnalyzeSuite{})
|
|
|
|
type testAnalyzeSuite struct {
|
|
testData testutil.TestData
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) SetUpSuite(c *C) {
|
|
var err error
|
|
s.testData, err = testutil.LoadTestSuiteData("testdata", "analyze_suite")
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TearDownSuite(c *C) {
|
|
c.Assert(s.testData.GenerateOutputIfNeeded(), IsNil)
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) loadTableStats(fileName string, dom *domain.Domain) error {
|
|
statsPath := filepath.Join("testdata", fileName)
|
|
bytes, err := os.ReadFile(statsPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
statsTbl := &handle.JSONTable{}
|
|
err = json.Unmarshal(bytes, statsTbl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
statsHandle := dom.StatsHandle()
|
|
err = statsHandle.LoadStatsFromJSON(dom.InfoSchema(), statsTbl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestExplainAnalyze(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
tk.MustExec("use test")
|
|
tk.MustExec("set sql_mode='STRICT_TRANS_TABLES'") // disable only full group by
|
|
tk.MustExec("create table t1(a int, b int, c int, key idx(a, b))")
|
|
tk.MustExec("create table t2(a int, b int)")
|
|
tk.MustExec("insert into t1 values (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5)")
|
|
tk.MustExec("insert into t2 values (2, 22), (3, 33), (5, 55), (233, 2), (333, 3), (3434, 5)")
|
|
tk.MustExec("analyze table t1, t2")
|
|
rs := tk.MustQuery("explain analyze select t1.a, t1.b, sum(t1.c) from t1 join t2 on t1.a = t2.b where t1.a > 1")
|
|
c.Assert(len(rs.Rows()), Equals, 10)
|
|
for _, row := range rs.Rows() {
|
|
c.Assert(len(row), Equals, 9)
|
|
execInfo := row[5].(string)
|
|
c.Assert(strings.Contains(execInfo, "time"), Equals, true)
|
|
c.Assert(strings.Contains(execInfo, "loops"), Equals, true)
|
|
if strings.Contains(row[0].(string), "Reader") || strings.Contains(row[0].(string), "IndexLookUp") {
|
|
c.Assert(strings.Contains(execInfo, "cop_task"), Equals, true)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCBOWithoutAnalyze tests the plan with stats that only have count info.
|
|
func (s *testAnalyzeSuite) TestCBOWithoutAnalyze(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("create table t1 (a int)")
|
|
testKit.MustExec("create table t2 (a int)")
|
|
h := dom.StatsHandle()
|
|
c.Assert(h.HandleDDLEvent(<-h.DDLEventCh()), IsNil)
|
|
c.Assert(h.HandleDDLEvent(<-h.DDLEventCh()), IsNil)
|
|
testKit.MustExec("insert into t1 values (1), (2), (3), (4), (5), (6)")
|
|
testKit.MustExec("insert into t2 values (1), (2), (3), (4), (5), (6)")
|
|
c.Assert(h.DumpStatsDeltaToKV(handle.DumpAll), IsNil)
|
|
c.Assert(h.Update(dom.InfoSchema()), IsNil)
|
|
var input []string
|
|
var output []struct {
|
|
SQL string
|
|
Plan []string
|
|
}
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, sql := range input {
|
|
plan := testKit.MustQuery(sql)
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = sql
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(plan.Rows())
|
|
})
|
|
plan.Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestStraightJoin(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("use test")
|
|
h := dom.StatsHandle()
|
|
for _, tblName := range []string{"t1", "t2", "t3", "t4"} {
|
|
testKit.MustExec(fmt.Sprintf("create table %s (a int)", tblName))
|
|
c.Assert(h.HandleDDLEvent(<-h.DDLEventCh()), IsNil)
|
|
}
|
|
var input []string
|
|
var output [][]string
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, tt := range input {
|
|
s.testData.OnRecord(func() {
|
|
output[i] = s.testData.ConvertRowsToStrings(testKit.MustQuery(tt).Rows())
|
|
})
|
|
testKit.MustQuery(tt).Check(testkit.Rows(output[i]...))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestTableDual(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
|
|
testKit := testkit.NewTestKit(c, store)
|
|
testKit.MustExec(`use test`)
|
|
h := dom.StatsHandle()
|
|
testKit.MustExec(`create table t(a int)`)
|
|
testKit.MustExec("insert into t values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)")
|
|
c.Assert(h.HandleDDLEvent(<-h.DDLEventCh()), IsNil)
|
|
|
|
c.Assert(h.DumpStatsDeltaToKV(handle.DumpAll), IsNil)
|
|
c.Assert(h.Update(dom.InfoSchema()), IsNil)
|
|
var input []string
|
|
var output []struct {
|
|
SQL string
|
|
Plan []string
|
|
}
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, sql := range input {
|
|
plan := testKit.MustQuery(sql)
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = sql
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(plan.Rows())
|
|
})
|
|
plan.Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestEstimation(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
statistics.RatioOfPseudoEstimate.Store(0.7)
|
|
}()
|
|
statistics.RatioOfPseudoEstimate.Store(10.0)
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("create table t (a int)")
|
|
testKit.MustExec("insert into t values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)")
|
|
testKit.MustExec("insert into t select * from t")
|
|
testKit.MustExec("insert into t select * from t")
|
|
h := dom.StatsHandle()
|
|
err = h.HandleDDLEvent(<-h.DDLEventCh())
|
|
c.Assert(err, IsNil)
|
|
c.Assert(h.DumpStatsDeltaToKV(handle.DumpAll), IsNil)
|
|
testKit.MustExec("analyze table t")
|
|
for i := 1; i <= 8; i++ {
|
|
testKit.MustExec("delete from t where a = ?", i)
|
|
}
|
|
c.Assert(h.DumpStatsDeltaToKV(handle.DumpAll), IsNil)
|
|
c.Assert(h.Update(dom.InfoSchema()), IsNil)
|
|
var input []string
|
|
var output []struct {
|
|
SQL string
|
|
Plan []string
|
|
}
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, sql := range input {
|
|
plan := testKit.MustQuery(sql)
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = sql
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(plan.Rows())
|
|
})
|
|
plan.Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
|
|
func constructInsertSQL(i, n int) string {
|
|
sql := "insert into t (a,b,c,e)values "
|
|
for j := 0; j < n; j++ {
|
|
sql += fmt.Sprintf("(%d, %d, '%d', %d)", i*n+j, i, i+j, i*n+j)
|
|
if j != n-1 {
|
|
sql += ", "
|
|
}
|
|
}
|
|
return sql
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestIndexRead(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("set @@session.tidb_executor_concurrency = 4;")
|
|
testKit.MustExec("set @@session.tidb_hash_join_concurrency = 5;")
|
|
testKit.MustExec("set @@session.tidb_distsql_scan_concurrency = 15;")
|
|
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("drop table if exists t, t1")
|
|
testKit.MustExec("create table t (a int primary key, b int, c varchar(200), d datetime DEFAULT CURRENT_TIMESTAMP, e int, ts timestamp DEFAULT CURRENT_TIMESTAMP)")
|
|
testKit.MustExec("create index b on t (b)")
|
|
testKit.MustExec("create index d on t (d)")
|
|
testKit.MustExec("create index e on t (e)")
|
|
testKit.MustExec("create index b_c on t (b,c)")
|
|
testKit.MustExec("create index ts on t (ts)")
|
|
testKit.MustExec("create table t1 (a int, b int, index idx(a), index idxx(b))")
|
|
|
|
// This stats is generated by following format:
|
|
// fill (a, b, c, e) as (i*100+j, i, i+j, i*100+j), i and j is dependent and range of this two are [0, 99].
|
|
err = s.loadTableStats("analyzesSuiteTestIndexReadT.json", dom)
|
|
c.Assert(err, IsNil)
|
|
for i := 1; i < 16; i++ {
|
|
testKit.MustExec(fmt.Sprintf("insert into t1 values(%v, %v)", i, i))
|
|
}
|
|
testKit.MustExec("analyze table t1")
|
|
ctx := testKit.Se.(sessionctx.Context)
|
|
var input, output []string
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
|
|
for i, tt := range input {
|
|
stmts, err := session.Parse(ctx, tt)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(stmts, HasLen, 1)
|
|
stmt := stmts[0]
|
|
ret := &core.PreprocessorReturn{}
|
|
err = core.Preprocess(ctx, stmt, core.WithPreprocessorReturn(ret))
|
|
c.Assert(err, IsNil)
|
|
p, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
|
|
c.Assert(err, IsNil)
|
|
planString := core.ToString(p)
|
|
s.testData.OnRecord(func() {
|
|
output[i] = planString
|
|
})
|
|
c.Assert(planString, Equals, output[i], Commentf("for %s", tt))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestEmptyTable(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("drop table if exists t, t1")
|
|
testKit.MustExec("create table t (c1 int)")
|
|
testKit.MustExec("create table t1 (c1 int)")
|
|
testKit.MustExec("analyze table t, t1")
|
|
var input, output []string
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, tt := range input {
|
|
ctx := testKit.Se.(sessionctx.Context)
|
|
stmts, err := session.Parse(ctx, tt)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(stmts, HasLen, 1)
|
|
stmt := stmts[0]
|
|
ret := &core.PreprocessorReturn{}
|
|
err = core.Preprocess(ctx, stmt, core.WithPreprocessorReturn(ret))
|
|
c.Assert(err, IsNil)
|
|
p, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
|
|
c.Assert(err, IsNil)
|
|
planString := core.ToString(p)
|
|
s.testData.OnRecord(func() {
|
|
output[i] = planString
|
|
})
|
|
c.Assert(planString, Equals, output[i], Commentf("for %s", tt))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestAnalyze(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("drop table if exists t, t1, t2, t3")
|
|
testKit.MustExec("create table t (a int, b int)")
|
|
testKit.MustExec("create index a on t (a)")
|
|
testKit.MustExec("create index b on t (b)")
|
|
testKit.MustExec("insert into t (a,b) values (1,1),(1,2),(1,3),(1,4),(2,5),(2,6),(2,7),(2,8)")
|
|
testKit.MustExec("analyze table t")
|
|
|
|
testKit.MustExec("create table t1 (a int, b int)")
|
|
testKit.MustExec("create index a on t1 (a)")
|
|
testKit.MustExec("create index b on t1 (b)")
|
|
testKit.MustExec("insert into t1 (a,b) values (1,1),(1,2),(1,3),(1,4),(2,5),(2,6),(2,7),(2,8)")
|
|
|
|
testKit.MustExec("create table t2 (a int, b int)")
|
|
testKit.MustExec("create index a on t2 (a)")
|
|
testKit.MustExec("create index b on t2 (b)")
|
|
testKit.MustExec("insert into t2 (a,b) values (1,1),(1,2),(1,3),(1,4),(2,5),(2,6),(2,7),(2,8)")
|
|
testKit.MustExec("analyze table t2 index a")
|
|
|
|
testKit.MustExec("create table t3 (a int, b int)")
|
|
testKit.MustExec("create index a on t3 (a)")
|
|
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'static';")
|
|
testKit.MustExec("create table t4 (a int, b int) partition by range (a) (partition p1 values less than (2), partition p2 values less than (3))")
|
|
testKit.MustExec("create index a on t4 (a)")
|
|
testKit.MustExec("create index b on t4 (b)")
|
|
testKit.MustExec("insert into t4 (a,b) values (1,1),(1,2),(1,3),(1,4),(2,5),(2,6),(2,7),(2,8)")
|
|
testKit.MustExec("analyze table t4")
|
|
|
|
testKit.MustExec("create view v as select * from t")
|
|
_, err = testKit.Exec("analyze table v")
|
|
c.Assert(err.Error(), Equals, "analyze view v is not supported now.")
|
|
testKit.MustExec("drop view v")
|
|
|
|
testKit.MustExec("create sequence seq")
|
|
_, err = testKit.Exec("analyze table seq")
|
|
c.Assert(err.Error(), Equals, "analyze sequence seq is not supported now.")
|
|
testKit.MustExec("drop sequence seq")
|
|
|
|
var input, output []string
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
|
|
for i, tt := range input {
|
|
ctx := testKit.Se.(sessionctx.Context)
|
|
stmts, err := session.Parse(ctx, tt)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(stmts, HasLen, 1)
|
|
stmt := stmts[0]
|
|
err = executor.ResetContextOfStmt(ctx, stmt)
|
|
c.Assert(err, IsNil)
|
|
ret := &core.PreprocessorReturn{}
|
|
err = core.Preprocess(ctx, stmt, core.WithPreprocessorReturn(ret))
|
|
c.Assert(err, IsNil)
|
|
p, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
|
|
c.Assert(err, IsNil)
|
|
planString := core.ToString(p)
|
|
s.testData.OnRecord(func() {
|
|
output[i] = planString
|
|
})
|
|
c.Assert(planString, Equals, output[i], Commentf("for %s", tt))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestOutdatedAnalyze(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("create table t (a int, b int, index idx(a))")
|
|
for i := 0; i < 10; i++ {
|
|
testKit.MustExec(fmt.Sprintf("insert into t values (%d,%d)", i, i))
|
|
}
|
|
h := dom.StatsHandle()
|
|
err = h.HandleDDLEvent(<-h.DDLEventCh())
|
|
c.Assert(err, IsNil)
|
|
c.Assert(h.DumpStatsDeltaToKV(handle.DumpAll), IsNil)
|
|
testKit.MustExec("analyze table t")
|
|
testKit.MustExec("insert into t select * from t")
|
|
testKit.MustExec("insert into t select * from t")
|
|
testKit.MustExec("insert into t select * from t")
|
|
c.Assert(h.DumpStatsDeltaToKV(handle.DumpAll), IsNil)
|
|
c.Assert(h.Update(dom.InfoSchema()), IsNil)
|
|
var input []struct {
|
|
SQL string
|
|
EnablePseudoForOutdatedStats bool
|
|
RatioOfPseudoEstimate float64
|
|
}
|
|
var output []struct {
|
|
SQL string
|
|
EnablePseudoForOutdatedStats bool
|
|
RatioOfPseudoEstimate float64
|
|
Plan []string
|
|
}
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, tt := range input {
|
|
testKit.Se.GetSessionVars().SetEnablePseudoForOutdatedStats(tt.EnablePseudoForOutdatedStats)
|
|
statistics.RatioOfPseudoEstimate.Store(tt.RatioOfPseudoEstimate)
|
|
plan := testKit.MustQuery(tt.SQL)
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = tt.SQL
|
|
output[i].EnablePseudoForOutdatedStats = tt.EnablePseudoForOutdatedStats
|
|
output[i].RatioOfPseudoEstimate = tt.RatioOfPseudoEstimate
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(plan.Rows())
|
|
})
|
|
plan.Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestPreparedNullParam(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
|
|
defer config.RestoreFunc()()
|
|
flags := []bool{false, true}
|
|
for _, flag := range flags {
|
|
config.UpdateGlobal(func(conf *config.Config) {
|
|
conf.PreparedPlanCache.Enabled = flag
|
|
conf.PreparedPlanCache.Capacity = 100
|
|
})
|
|
testKit := testkit.NewTestKit(c, store)
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("drop table if exists t")
|
|
testKit.MustExec("create table t (id int not null, KEY id (id))")
|
|
testKit.MustExec("insert into t values (1), (2), (3)")
|
|
|
|
sql := "select * from t where id = ?"
|
|
best := "Dual"
|
|
|
|
ctx := testKit.Se.(sessionctx.Context)
|
|
stmts, err := session.Parse(ctx, sql)
|
|
c.Assert(err, IsNil)
|
|
stmt := stmts[0]
|
|
|
|
ret := &core.PreprocessorReturn{}
|
|
err = core.Preprocess(ctx, stmt, core.InPrepare, core.WithPreprocessorReturn(ret))
|
|
c.Assert(err, IsNil)
|
|
p, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
|
|
c.Assert(err, IsNil)
|
|
|
|
c.Assert(core.ToString(p), Equals, best, Commentf("for %s", sql))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestNullCount(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("drop table if exists t")
|
|
testKit.MustExec("create table t (a int, b int, index idx(a))")
|
|
testKit.MustExec("insert into t values (null, null), (null, null)")
|
|
testKit.MustExec("analyze table t")
|
|
var input []string
|
|
var output [][]string
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i := 0; i < 2; i++ {
|
|
s.testData.OnRecord(func() {
|
|
output[i] = s.testData.ConvertRowsToStrings(testKit.MustQuery(input[i]).Rows())
|
|
})
|
|
testKit.MustQuery(input[i]).Check(testkit.Rows(output[i]...))
|
|
}
|
|
h := dom.StatsHandle()
|
|
h.Clear()
|
|
c.Assert(h.Update(dom.InfoSchema()), IsNil)
|
|
for i := 2; i < 4; i++ {
|
|
s.testData.OnRecord(func() {
|
|
output[i] = s.testData.ConvertRowsToStrings(testKit.MustQuery(input[i]).Rows())
|
|
})
|
|
testKit.MustQuery(input[i]).Check(testkit.Rows(output[i]...))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestCorrelatedEstimation(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
tk.MustExec("use test")
|
|
tk.MustExec("set sql_mode='STRICT_TRANS_TABLES'") // disable only full group by
|
|
tk.MustExec("create table t(a int, b int, c int, index idx(c,b,a))")
|
|
tk.MustExec("insert into t values(1,1,1), (2,2,2), (3,3,3), (4,4,4), (5,5,5), (6,6,6), (7,7,7), (8,8,8), (9,9,9),(10,10,10)")
|
|
tk.MustExec("analyze table t")
|
|
var (
|
|
input []string
|
|
output [][]string
|
|
)
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, tt := range input {
|
|
rs := tk.MustQuery(tt)
|
|
s.testData.OnRecord(func() {
|
|
output[i] = s.testData.ConvertRowsToStrings(rs.Rows())
|
|
})
|
|
rs.Check(testkit.Rows(output[i]...))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestInconsistentEstimation(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t(a int, b int, c int, index ab(a,b), index ac(a,c))")
|
|
tk.MustExec("insert into t values (1,1,1), (1000,1000,1000)")
|
|
for i := 0; i < 10; i++ {
|
|
tk.MustExec("insert into t values (5,5,5), (10,10,10)")
|
|
}
|
|
tk.MustExec("set @@tidb_analyze_version=1")
|
|
tk.MustExec("analyze table t with 2 buckets")
|
|
// Force using the histogram to estimate.
|
|
tk.MustExec("update mysql.stats_histograms set stats_ver = 0")
|
|
dom.StatsHandle().Clear()
|
|
err = dom.StatsHandle().Update(dom.InfoSchema())
|
|
c.Assert(err, IsNil)
|
|
var input []string
|
|
var output []struct {
|
|
SQL string
|
|
Plan []string
|
|
}
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, sql := range input {
|
|
plan := tk.MustQuery(sql)
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = sql
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(plan.Rows())
|
|
})
|
|
plan.Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
|
|
func newStoreWithBootstrap() (kv.Storage, *domain.Domain, error) {
|
|
store, err := mockstore.NewMockStore()
|
|
if err != nil {
|
|
return nil, nil, errors.Trace(err)
|
|
}
|
|
|
|
session.SetSchemaLease(0)
|
|
session.DisableStats4Test()
|
|
|
|
dom, err := session.BootstrapSession(store)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
dom.SetStatsUpdating(true)
|
|
return store, dom, errors.Trace(err)
|
|
}
|
|
|
|
func BenchmarkOptimize(b *testing.B) {
|
|
c := &C{}
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
|
|
testKit := testkit.NewTestKit(c, store)
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("drop table if exists t")
|
|
testKit.MustExec("create table t (a int primary key, b int, c varchar(200), d datetime DEFAULT CURRENT_TIMESTAMP, e int, ts timestamp DEFAULT CURRENT_TIMESTAMP)")
|
|
testKit.MustExec("create index b on t (b)")
|
|
testKit.MustExec("create index d on t (d)")
|
|
testKit.MustExec("create index e on t (e)")
|
|
testKit.MustExec("create index b_c on t (b,c)")
|
|
testKit.MustExec("create index ts on t (ts)")
|
|
for i := 0; i < 100; i++ {
|
|
testKit.MustExec(constructInsertSQL(i, 100))
|
|
}
|
|
testKit.MustExec("analyze table t")
|
|
tests := []struct {
|
|
sql string
|
|
best string
|
|
}{
|
|
{
|
|
sql: "select count(*) from t group by e",
|
|
best: "IndexReader(Index(t.e)[[NULL,+inf]])->StreamAgg",
|
|
},
|
|
{
|
|
sql: "select count(*) from t where e <= 10 group by e",
|
|
best: "IndexReader(Index(t.e)[[-inf,10]])->StreamAgg",
|
|
},
|
|
{
|
|
sql: "select count(*) from t where e <= 50",
|
|
best: "IndexReader(Index(t.e)[[-inf,50]]->HashAgg)->HashAgg",
|
|
},
|
|
{
|
|
sql: "select count(*) from t where c > '1' group by b",
|
|
best: "IndexReader(Index(t.b_c)[[NULL,+inf]]->Sel([gt(test.t.c, 1)]))->StreamAgg",
|
|
},
|
|
{
|
|
sql: "select count(*) from t where e = 1 group by b",
|
|
best: "IndexLookUp(Index(t.e)[[1,1]], Table(t)->HashAgg)->HashAgg",
|
|
},
|
|
{
|
|
sql: "select count(*) from t where e > 1 group by b",
|
|
best: "TableReader(Table(t)->Sel([gt(test.t.e, 1)])->HashAgg)->HashAgg",
|
|
},
|
|
{
|
|
sql: "select count(e) from t where t.b <= 20",
|
|
best: "IndexLookUp(Index(t.b)[[-inf,20]], Table(t)->HashAgg)->HashAgg",
|
|
},
|
|
{
|
|
sql: "select count(e) from t where t.b <= 30",
|
|
best: "IndexLookUp(Index(t.b)[[-inf,30]], Table(t)->HashAgg)->HashAgg",
|
|
},
|
|
{
|
|
sql: "select count(e) from t where t.b <= 40",
|
|
best: "IndexLookUp(Index(t.b)[[-inf,40]], Table(t)->HashAgg)->HashAgg",
|
|
},
|
|
{
|
|
sql: "select count(e) from t where t.b <= 50",
|
|
best: "TableReader(Table(t)->Sel([le(test.t.b, 50)])->HashAgg)->HashAgg",
|
|
},
|
|
{
|
|
sql: "select * from t where t.b <= 40",
|
|
best: "IndexLookUp(Index(t.b)[[-inf,40]], Table(t))",
|
|
},
|
|
{
|
|
sql: "select * from t where t.b <= 50",
|
|
best: "TableReader(Table(t)->Sel([le(test.t.b, 50)]))",
|
|
},
|
|
// test panic
|
|
{
|
|
sql: "select * from t where 1 and t.b <= 50",
|
|
best: "TableReader(Table(t)->Sel([le(test.t.b, 50)]))",
|
|
},
|
|
{
|
|
sql: "select * from t where t.b <= 100 order by t.a limit 1",
|
|
best: "TableReader(Table(t)->Sel([le(test.t.b, 100)])->Limit)->Limit",
|
|
},
|
|
{
|
|
sql: "select * from t where t.b <= 1 order by t.a limit 10",
|
|
best: "IndexLookUp(Index(t.b)[[-inf,1]]->TopN([test.t.a],0,10), Table(t))->TopN([test.t.a],0,10)",
|
|
},
|
|
{
|
|
sql: "select * from t use index(b) where b = 1 order by a",
|
|
best: "IndexLookUp(Index(t.b)[[1,1]], Table(t))->Sort",
|
|
},
|
|
// test datetime
|
|
{
|
|
sql: "select * from t where d < cast('1991-09-05' as datetime)",
|
|
best: "IndexLookUp(Index(t.d)[[-inf,1991-09-05 00:00:00)], Table(t))",
|
|
},
|
|
// test timestamp
|
|
{
|
|
sql: "select * from t where ts < '1991-09-05'",
|
|
best: "IndexLookUp(Index(t.ts)[[-inf,1991-09-05 00:00:00)], Table(t))",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
ctx := testKit.Se.(sessionctx.Context)
|
|
stmts, err := session.Parse(ctx, tt.sql)
|
|
c.Assert(err, IsNil)
|
|
c.Assert(stmts, HasLen, 1)
|
|
stmt := stmts[0]
|
|
ret := &core.PreprocessorReturn{}
|
|
err = core.Preprocess(ctx, stmt, core.WithPreprocessorReturn(ret))
|
|
c.Assert(err, IsNil)
|
|
|
|
b.Run(tt.sql, func(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
b.ReportAllocs()
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestIssue9562(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
|
|
tk.MustExec("use test")
|
|
var input [][]string
|
|
var output []struct {
|
|
SQL []string
|
|
Plan []string
|
|
}
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, ts := range input {
|
|
for j, tt := range ts {
|
|
if j != len(ts)-1 {
|
|
tk.MustExec(tt)
|
|
}
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = ts
|
|
if j == len(ts)-1 {
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(tk.MustQuery(tt).Rows())
|
|
}
|
|
})
|
|
if j == len(ts)-1 {
|
|
tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestIssue9805(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t1, t2")
|
|
tk.MustExec(`
|
|
create table t1 (
|
|
id bigint primary key,
|
|
a bigint not null,
|
|
b varchar(100) not null,
|
|
c varchar(10) not null,
|
|
d bigint as (a % 30) not null,
|
|
key (d, b, c)
|
|
)
|
|
`)
|
|
tk.MustExec(`
|
|
create table t2 (
|
|
id varchar(50) primary key,
|
|
a varchar(100) unique,
|
|
b datetime,
|
|
c varchar(45),
|
|
d int not null unique auto_increment
|
|
)
|
|
`)
|
|
// Test when both tables are empty, EXPLAIN ANALYZE for IndexLookUp would not panic.
|
|
tk.MustQuery("explain analyze select /*+ TIDB_INLJ(t2) */ t1.id, t2.a from t1 join t2 on t1.a = t2.d where t1.b = 't2' and t1.d = 4")
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestLimitCrossEstimation(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
|
|
tk.MustExec("set @@session.tidb_executor_concurrency = 4;")
|
|
tk.MustExec("set @@session.tidb_hash_join_concurrency = 5;")
|
|
tk.MustExec("set @@session.tidb_distsql_scan_concurrency = 15;")
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int primary key, b int not null, c int not null default 0, index idx_bc(b, c))")
|
|
var input [][]string
|
|
var output []struct {
|
|
SQL []string
|
|
Plan []string
|
|
}
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, ts := range input {
|
|
for j, tt := range ts {
|
|
if j != len(ts)-1 {
|
|
tk.MustExec(tt)
|
|
}
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = ts
|
|
if j == len(ts)-1 {
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(tk.MustQuery(tt).Rows())
|
|
}
|
|
})
|
|
if j == len(ts)-1 {
|
|
tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestLowSelIndexGreedySearch(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec(`set tidb_opt_limit_push_down_threshold=0`)
|
|
testKit.MustExec("drop table if exists t")
|
|
testKit.MustExec("create table t (a varchar(32) default null, b varchar(10) default null, c varchar(12) default null, d varchar(32) default null, e bigint(10) default null, key idx1 (d,a), key idx2 (a,c), key idx3 (c,b), key idx4 (e))")
|
|
err = s.loadTableStats("analyzeSuiteTestLowSelIndexGreedySearchT.json", dom)
|
|
c.Assert(err, IsNil)
|
|
var input []string
|
|
var output []struct {
|
|
SQL string
|
|
Plan []string
|
|
}
|
|
// The test purposes are:
|
|
// - index `idx2` runs much faster than `idx4` experimentally;
|
|
// - estimated row count of IndexLookUp should be 0;
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, tt := range input {
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = tt
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(testKit.MustQuery(tt).Rows())
|
|
})
|
|
testKit.MustQuery(tt).Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestUpdateProjEliminate(c *C) {
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, b int)")
|
|
tk.MustExec("explain update t t1, (select distinct b from t) t2 set t1.b = t2.b")
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestTiFlashCostModel(c *C) {
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t (a int, b int, c int, primary key(a))")
|
|
tk.MustExec("insert into t values(1,1,1), (2,2,2), (3,3,3)")
|
|
|
|
tbl, err := dom.InfoSchema().TableByName(model.CIStr{O: "test", L: "test"}, model.CIStr{O: "t", L: "t"})
|
|
c.Assert(err, IsNil)
|
|
// Set the hacked TiFlash replica for explain tests.
|
|
tbl.Meta().TiFlashReplica = &model.TiFlashReplicaInfo{Count: 1, Available: true}
|
|
|
|
var input, output [][]string
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, ts := range input {
|
|
for j, tt := range ts {
|
|
if j != len(ts)-1 {
|
|
tk.MustExec(tt)
|
|
}
|
|
s.testData.OnRecord(func() {
|
|
if j == len(ts)-1 {
|
|
output[i] = s.testData.ConvertRowsToStrings(tk.MustQuery(tt).Rows())
|
|
}
|
|
})
|
|
if j == len(ts)-1 {
|
|
tk.MustQuery(tt).Check(testkit.Rows(output[i]...))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestIndexEqualUnknown(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("drop table if exists t, t1")
|
|
testKit.Se.GetSessionVars().EnableClusteredIndex = variable.ClusteredIndexDefModeIntOnly
|
|
testKit.MustExec("CREATE TABLE t(a bigint(20) NOT NULL, b bigint(20) NOT NULL, c bigint(20) NOT NULL, PRIMARY KEY (a,c,b), KEY (b))")
|
|
err = s.loadTableStats("analyzeSuiteTestIndexEqualUnknownT.json", dom)
|
|
c.Assert(err, IsNil)
|
|
var input []string
|
|
var output []struct {
|
|
SQL string
|
|
Plan []string
|
|
}
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, tt := range input {
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = tt
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(testKit.MustQuery(tt).Rows())
|
|
})
|
|
testKit.MustQuery(tt).Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestLimitIndexEstimation(c *C) {
|
|
defer testleak.AfterTest(c)()
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
|
|
tk.MustExec("use test")
|
|
tk.MustExec("drop table if exists t")
|
|
tk.MustExec("create table t(a int, b int, key idx_a(a), key idx_b(b))")
|
|
tk.MustExec("set session tidb_enable_extended_stats = on")
|
|
// Values in column a are from 1 to 1000000, values in column b are from 1000000 to 1,
|
|
// these 2 columns are strictly correlated in reverse order.
|
|
err = s.loadTableStats("analyzeSuiteTestLimitIndexEstimationT.json", dom)
|
|
c.Assert(err, IsNil)
|
|
var input []string
|
|
var output []struct {
|
|
SQL string
|
|
Plan []string
|
|
}
|
|
s.testData.GetTestCases(c, &input, &output)
|
|
for i, tt := range input {
|
|
s.testData.OnRecord(func() {
|
|
output[i].SQL = tt
|
|
output[i].Plan = s.testData.ConvertRowsToStrings(tk.MustQuery(tt).Rows())
|
|
})
|
|
tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...))
|
|
}
|
|
}
|
|
|
|
func (s *testAnalyzeSuite) TestBatchPointGetTablePartition(c *C) {
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
testKit := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
testKit.MustExec("use test")
|
|
testKit.MustExec("drop table if exists t1,t2,t3,t4,t5,t6")
|
|
|
|
testKit.MustExec("create table t1(a int, b int, primary key(a,b)) partition by hash(b) partitions 2")
|
|
testKit.MustExec("insert into t1 values(1,1),(1,2),(2,1),(2,2)")
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'static'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t1 where a in (1,2) and b = 1").Check(testkit.Rows(
|
|
"Batch_Point_Get 2.00 root table:t1, index:PRIMARY(a, b) keep order:false, desc:false",
|
|
))
|
|
testKit.MustQuery("select * from t1 where a in (1,2) and b = 1").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"2 1",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t1 where a = 1 and b in (1,2)").Check(testkit.Rows(
|
|
"PartitionUnion 4.00 root ",
|
|
"├─Batch_Point_Get 2.00 root table:t1, index:PRIMARY(a, b) keep order:false, desc:false",
|
|
"└─Batch_Point_Get 2.00 root table:t1, index:PRIMARY(a, b) keep order:false, desc:false",
|
|
))
|
|
testKit.MustQuery("select * from t1 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"1 2",
|
|
))
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t1 where a in (1,2) and b = 1").Check(testkit.Rows(
|
|
"IndexReader 2.00 root partition:p1 index:IndexRangeScan",
|
|
"└─IndexRangeScan 2.00 cop[tikv] table:t1, index:PRIMARY(a, b) range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t1 where a in (1,2) and b = 1").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"2 1",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t1 where a = 1 and b in (1,2)").Check(testkit.Rows(
|
|
"IndexReader 2.00 root partition:p0,p1 index:IndexRangeScan",
|
|
"└─IndexRangeScan 2.00 cop[tikv] table:t1, index:PRIMARY(a, b) range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t1 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"1 2",
|
|
))
|
|
|
|
testKit.MustExec("create table t2(a int, b int, primary key(a,b)) partition by range(b) (partition p0 values less than (2), partition p1 values less than maxvalue)")
|
|
testKit.MustExec("insert into t2 values(1,1),(1,2),(2,1),(2,2)")
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'static'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t2 where a in (1,2) and b = 1").Check(testkit.Rows(
|
|
"IndexReader 2.00 root index:IndexRangeScan",
|
|
"└─IndexRangeScan 2.00 cop[tikv] table:t2, partition:p0, index:PRIMARY(a, b) range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t2 where a in (1,2) and b = 1").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"2 1",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t2 where a = 1 and b in (1,2)").Check(testkit.Rows(
|
|
"PartitionUnion 4.00 root ",
|
|
"├─IndexReader 2.00 root index:IndexRangeScan",
|
|
"│ └─IndexRangeScan 2.00 cop[tikv] table:t2, partition:p0, index:PRIMARY(a, b) range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo",
|
|
"└─IndexReader 2.00 root index:IndexRangeScan",
|
|
" └─IndexRangeScan 2.00 cop[tikv] table:t2, partition:p1, index:PRIMARY(a, b) range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t2 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"1 2",
|
|
))
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t2 where a in (1,2) and b = 1").Check(testkit.Rows(
|
|
"IndexReader 2.00 root partition:p0 index:IndexRangeScan",
|
|
"└─IndexRangeScan 2.00 cop[tikv] table:t2, index:PRIMARY(a, b) range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t2 where a in (1,2) and b = 1").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"2 1",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t2 where a = 1 and b in (1,2)").Check(testkit.Rows(
|
|
"IndexReader 2.00 root partition:all index:IndexRangeScan",
|
|
"└─IndexRangeScan 2.00 cop[tikv] table:t2, index:PRIMARY(a, b) range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t2 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"1 2",
|
|
))
|
|
|
|
testKit.Se.GetSessionVars().EnableClusteredIndex = variable.ClusteredIndexDefModeOn
|
|
|
|
testKit.MustExec("create table t3(a int, b int, primary key(a,b)) partition by hash(b) partitions 2")
|
|
testKit.MustExec("insert into t3 values(1,1),(1,2),(2,1),(2,2)")
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'static'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t3 where a in (1,2) and b = 1").Check(testkit.Rows(
|
|
"Batch_Point_Get 2.00 root table:t3, clustered index:PRIMARY(a, b) keep order:false, desc:false",
|
|
))
|
|
testKit.MustQuery("select * from t3 where a in (1,2) and b = 1").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"2 1",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t3 where a = 1 and b in (1,2)").Check(testkit.Rows(
|
|
"PartitionUnion 0.04 root ",
|
|
"├─Batch_Point_Get 2.00 root table:t3, clustered index:PRIMARY(a, b) keep order:false, desc:false",
|
|
"└─Batch_Point_Get 2.00 root table:t3, clustered index:PRIMARY(a, b) keep order:false, desc:false",
|
|
))
|
|
testKit.MustQuery("select * from t3 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"1 2",
|
|
))
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t3 where a in (1,2) and b = 1").Check(testkit.Rows(
|
|
"TableReader 2.00 root partition:p1 data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t3 range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t3 where a in (1,2) and b = 1").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"2 1",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t3 where a = 1 and b in (1,2)").Check(testkit.Rows(
|
|
"TableReader 2.00 root partition:p0,p1 data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t3 range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t3 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"1 2",
|
|
))
|
|
|
|
testKit.MustExec("create table t4(a int, b int, primary key(a,b)) partition by range(b) (partition p0 values less than (2), partition p1 values less than maxvalue)")
|
|
testKit.MustExec("insert into t4 values(1,1),(1,2),(2,1),(2,2)")
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'static'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t4 where a in (1,2) and b = 1").Check(testkit.Rows(
|
|
"TableReader 2.00 root data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t4, partition:p0 range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t4 where a in (1,2) and b = 1").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"2 1",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t4 where a = 1 and b in (1,2)").Check(testkit.Rows(
|
|
"PartitionUnion 0.04 root ",
|
|
"├─TableReader 2.00 root data:TableRangeScan",
|
|
"│ └─TableRangeScan 2.00 cop[tikv] table:t4, partition:p0 range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo",
|
|
"└─TableReader 2.00 root data:TableRangeScan",
|
|
" └─TableRangeScan 2.00 cop[tikv] table:t4, partition:p1 range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t4 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"1 2",
|
|
))
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t4 where a in (1,2) and b = 1").Check(testkit.Rows(
|
|
"TableReader 2.00 root partition:p0 data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t4 range:[1 1,1 1], [2 1,2 1], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t4 where a in (1,2) and b = 1").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"2 1",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t4 where a = 1 and b in (1,2)").Check(testkit.Rows(
|
|
"TableReader 2.00 root partition:all data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t4 range:[1 1,1 1], [1 2,1 2], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t4 where a = 1 and b in (1,2)").Sort().Check(testkit.Rows(
|
|
"1 1",
|
|
"1 2",
|
|
))
|
|
|
|
testKit.MustExec("create table t5(a int, b int, primary key(a)) partition by hash(a) partitions 2")
|
|
testKit.MustExec("insert into t5 values(1,0),(2,0),(3,0),(4,0)")
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'static'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t5 where a in (1,2) and 1 = 1").Check(testkit.Rows(
|
|
"PartitionUnion 4.00 root ",
|
|
"├─Batch_Point_Get 2.00 root table:t5 handle:[1 2], keep order:false, desc:false",
|
|
"└─Batch_Point_Get 2.00 root table:t5 handle:[1 2], keep order:false, desc:false",
|
|
))
|
|
testKit.MustQuery("select * from t5 where a in (1,2) and 1 = 1").Sort().Check(testkit.Rows(
|
|
"1 0",
|
|
"2 0",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t5 where a in (1,3) and 1 = 1").Check(testkit.Rows(
|
|
"Batch_Point_Get 2.00 root table:t5 handle:[1 3], keep order:false, desc:false",
|
|
))
|
|
testKit.MustQuery("select * from t5 where a in (1,3) and 1 = 1").Sort().Check(testkit.Rows(
|
|
"1 0",
|
|
"3 0",
|
|
))
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t5 where a in (1,2) and 1 = 1").Check(testkit.Rows(
|
|
"TableReader 2.00 root partition:p0,p1 data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t5 range:[1,1], [2,2], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t5 where a in (1,2) and 1 = 1").Sort().Check(testkit.Rows(
|
|
"1 0",
|
|
"2 0",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t5 where a in (1,3) and 1 = 1").Check(testkit.Rows(
|
|
"TableReader 2.00 root partition:p1 data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t5 range:[1,1], [3,3], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t5 where a in (1,3) and 1 = 1").Sort().Check(testkit.Rows(
|
|
"1 0",
|
|
"3 0",
|
|
))
|
|
|
|
testKit.MustExec("create table t6(a int, b int, primary key(a)) partition by range(a) (partition p0 values less than (3), partition p1 values less than maxvalue)")
|
|
testKit.MustExec("insert into t6 values(1,0),(2,0),(3,0),(4,0)")
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'static'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t6 where a in (1,2) and 1 = 1").Check(testkit.Rows(
|
|
"TableReader 2.00 root data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t6, partition:p0 range:[1,1], [2,2], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t6 where a in (1,2) and 1 = 1").Sort().Check(testkit.Rows(
|
|
"1 0",
|
|
"2 0",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t6 where a in (1,3) and 1 = 1").Check(testkit.Rows(
|
|
"PartitionUnion 4.00 root ",
|
|
"├─TableReader 2.00 root data:TableRangeScan",
|
|
"│ └─TableRangeScan 2.00 cop[tikv] table:t6, partition:p0 range:[1,1], [3,3], keep order:false, stats:pseudo",
|
|
"└─TableReader 2.00 root data:TableRangeScan",
|
|
" └─TableRangeScan 2.00 cop[tikv] table:t6, partition:p1 range:[1,1], [3,3], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t6 where a in (1,3) and 1 = 1").Sort().Check(testkit.Rows(
|
|
"1 0",
|
|
"3 0",
|
|
))
|
|
testKit.MustExec("set @@tidb_partition_prune_mode = 'dynamic'")
|
|
testKit.MustQuery("explain format = 'brief' select * from t6 where a in (1,2) and 1 = 1").Check(testkit.Rows(
|
|
"TableReader 2.00 root partition:p0 data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t6 range:[1,1], [2,2], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t6 where a in (1,2) and 1 = 1").Sort().Check(testkit.Rows(
|
|
"1 0",
|
|
"2 0",
|
|
))
|
|
testKit.MustQuery("explain format = 'brief' select * from t6 where a in (1,3) and 1 = 1").Check(testkit.Rows(
|
|
"TableReader 2.00 root partition:all data:TableRangeScan",
|
|
"└─TableRangeScan 2.00 cop[tikv] table:t6 range:[1,1], [3,3], keep order:false, stats:pseudo",
|
|
))
|
|
testKit.MustQuery("select * from t6 where a in (1,3) and 1 = 1").Sort().Check(testkit.Rows(
|
|
"1 0",
|
|
"3 0",
|
|
))
|
|
}
|
|
|
|
// TestAppendIntPkToIndexTailForRangeBuilding tests for issue25219 https://github.com/pingcap/tidb/issues/25219.
|
|
func (s *testAnalyzeSuite) TestAppendIntPkToIndexTailForRangeBuilding(c *C) {
|
|
store, dom, err := newStoreWithBootstrap()
|
|
c.Assert(err, IsNil)
|
|
tk := testkit.NewTestKit(c, store)
|
|
defer func() {
|
|
dom.Close()
|
|
store.Close()
|
|
}()
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t25219(a int primary key, col3 int, col1 int, index idx(col3))")
|
|
tk.MustExec("insert into t25219 values(1, 1, 1)")
|
|
tk.MustExec("analyze table t25219")
|
|
tk.MustQuery("select * from t25219 WHERE (col3 IS NULL OR col1 IS NOT NULL AND col3 <= 6659) AND col3 = 1;").Check(testkit.Rows("1 1 1"))
|
|
}
|