Files
tidb/planner/core/plan_stats_test.go

274 lines
10 KiB
Go

// Copyright 2021 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"
"fmt"
"testing"
"time"
"github.com/pingcap/tidb/config"
"github.com/pingcap/tidb/executor"
"github.com/pingcap/tidb/parser"
"github.com/pingcap/tidb/parser/model"
"github.com/pingcap/tidb/planner"
plannercore "github.com/pingcap/tidb/planner/core"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/statistics"
"github.com/pingcap/tidb/testkit"
"github.com/stretchr/testify/require"
)
func TestPlanStatsLoad(t *testing.T) {
p := parser.New()
store, dom, clean := testkit.CreateMockStoreAndDomain(t)
defer clean()
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
ctx := tk.Session().(sessionctx.Context)
tk.MustExec("drop table if exists t")
tk.MustExec("set @@session.tidb_analyze_version=2")
tk.MustExec("set @@session.tidb_partition_prune_mode = 'static'")
tk.MustExec("set @@session.tidb_stats_load_sync_wait = 60000")
tk.MustExec("create table t(a int, b int, c int, d int, primary key(a), key idx(b))")
tk.MustExec("insert into t values (1,1,1,1),(2,2,2,2),(3,3,3,3)")
tk.MustExec("create table pt(a int, b int, c int) partition by range(a) (partition p0 values less than (10), partition p1 values less than (20), partition p2 values less than maxvalue)")
tk.MustExec("insert into pt values (1,1,1),(2,2,2),(13,13,13),(14,14,14),(25,25,25),(36,36,36)")
oriLease := dom.StatsHandle().Lease()
dom.StatsHandle().SetLease(1)
defer func() {
dom.StatsHandle().SetLease(oriLease)
}()
tk.MustExec("analyze table t")
tk.MustExec("analyze table pt")
testCases := []struct {
sql string
skip bool
check func(p plannercore.Plan, tableInfo *model.TableInfo)
}{
{ // DataSource
sql: "select * from t where c>1",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
switch pp := p.(type) {
case *plannercore.PhysicalTableReader:
stats := pp.Stats().HistColl
require.Equal(t, 0, countFullStats(stats, tableInfo.Columns[1].ID))
require.Greater(t, countFullStats(stats, tableInfo.Columns[2].ID), 0)
default:
t.Error("unexpected plan:", pp)
}
},
},
{ // PartitionTable
sql: "select * from pt where a < 15 and c > 1",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
pua, ok := p.(*plannercore.PhysicalUnionAll)
require.True(t, ok)
for _, child := range pua.Children() {
require.Greater(t, countFullStats(child.Stats().HistColl, tableInfo.Columns[2].ID), 0)
}
},
},
{ // Join
sql: "select * from t t1 inner join t t2 on t1.b=t2.b where t1.d=3",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
pp, ok := p.(plannercore.PhysicalPlan)
require.True(t, ok)
require.Greater(t, countFullStats(pp.Children()[0].Stats().HistColl, tableInfo.Columns[3].ID), 0)
require.Greater(t, countFullStats(pp.Children()[1].Stats().HistColl, tableInfo.Columns[3].ID), 0)
},
},
{ // Apply
sql: "select * from t t1 where t1.b > (select count(*) from t t2 where t2.c > t1.a and t2.d>1) and t1.c>2",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
pp, ok := p.(*plannercore.PhysicalProjection)
require.True(t, ok)
pa, ok := pp.Children()[0].(*plannercore.PhysicalApply)
require.True(t, ok)
left := pa.PhysicalHashJoin.Children()[0]
right := pa.PhysicalHashJoin.Children()[0]
require.Greater(t, countFullStats(left.Stats().HistColl, tableInfo.Columns[2].ID), 0)
require.Greater(t, countFullStats(right.Stats().HistColl, tableInfo.Columns[3].ID), 0)
},
},
{ // > Any
sql: "select * from t where t.b > any(select d from t where t.c > 2)",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
ph, ok := p.(*plannercore.PhysicalHashJoin)
require.True(t, ok)
ptr, ok := ph.Children()[0].(*plannercore.PhysicalTableReader)
require.True(t, ok)
require.Greater(t, countFullStats(ptr.Stats().HistColl, tableInfo.Columns[2].ID), 0)
},
},
{ // in
sql: "select * from t where t.b in (select d from t where t.c > 2)",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
ph, ok := p.(*plannercore.PhysicalHashJoin)
require.True(t, ok)
ptr, ok := ph.Children()[1].(*plannercore.PhysicalTableReader)
require.True(t, ok)
require.Greater(t, countFullStats(ptr.Stats().HistColl, tableInfo.Columns[2].ID), 0)
},
},
{ // not in
sql: "select * from t where t.b not in (select d from t where t.c > 2)",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
ph, ok := p.(*plannercore.PhysicalHashJoin)
require.True(t, ok)
ptr, ok := ph.Children()[1].(*plannercore.PhysicalTableReader)
require.True(t, ok)
require.Greater(t, countFullStats(ptr.Stats().HistColl, tableInfo.Columns[2].ID), 0)
},
},
{ // exists
sql: "select * from t t1 where exists (select * from t t2 where t1.b > t2.d and t2.c>1)",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
ph, ok := p.(*plannercore.PhysicalHashJoin)
require.True(t, ok)
ptr, ok := ph.Children()[1].(*plannercore.PhysicalTableReader)
require.True(t, ok)
require.Greater(t, countFullStats(ptr.Stats().HistColl, tableInfo.Columns[2].ID), 0)
},
},
{ // not exists
sql: "select * from t t1 where not exists (select * from t t2 where t1.b > t2.d and t2.c>1)",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
ph, ok := p.(*plannercore.PhysicalHashJoin)
require.True(t, ok)
ptr, ok := ph.Children()[1].(*plannercore.PhysicalTableReader)
require.True(t, ok)
require.Greater(t, countFullStats(ptr.Stats().HistColl, tableInfo.Columns[2].ID), 0)
},
},
{ // CTE
sql: "with cte(x, y) as (select d + 1, b from t where c > 1) select * from cte where x < 3",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
ps, ok := p.(*plannercore.PhysicalSelection)
require.True(t, ok)
pc, ok := ps.Children()[0].(*plannercore.PhysicalCTE)
require.True(t, ok)
pp, ok := pc.SeedPlan.(*plannercore.PhysicalProjection)
require.True(t, ok)
reader, ok := pp.Children()[0].(*plannercore.PhysicalTableReader)
require.True(t, ok)
require.Greater(t, countFullStats(reader.Stats().HistColl, tableInfo.Columns[2].ID), 0)
},
},
{ // recursive CTE
sql: "with recursive cte(x, y) as (select a, b from t where c > 1 union select x + 1, y from cte where x < 5) select * from cte",
check: func(p plannercore.Plan, tableInfo *model.TableInfo) {
pc, ok := p.(*plannercore.PhysicalCTE)
require.True(t, ok)
pp, ok := pc.SeedPlan.(*plannercore.PhysicalProjection)
require.True(t, ok)
reader, ok := pp.Children()[0].(*plannercore.PhysicalTableReader)
require.True(t, ok)
require.Greater(t, countFullStats(reader.Stats().HistColl, tableInfo.Columns[2].ID), 0)
},
},
}
for _, testCase := range testCases {
if testCase.skip {
continue
}
is := dom.InfoSchema()
dom.StatsHandle().Clear() // clear statsCache
require.NoError(t, dom.StatsHandle().Update(is))
stmt, err := p.ParseOneStmt(testCase.sql, "", "")
require.NoError(t, err)
err = executor.ResetContextOfStmt(ctx, stmt)
require.NoError(t, err)
p, _, err := planner.Optimize(context.TODO(), ctx, stmt, is)
require.NoError(t, err)
tbl, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t"))
require.NoError(t, err)
tableInfo := tbl.Meta()
testCase.check(p, tableInfo)
}
}
func countFullStats(stats *statistics.HistColl, colID int64) int {
for _, col := range stats.Columns {
if col.Info.ID == colID {
return col.Histogram.Len() + col.TopN.Num()
}
}
return -1
}
func TestPlanStatsLoadTimeout(t *testing.T) {
p := parser.New()
originConfig := config.GetGlobalConfig()
newConfig := config.NewConfig()
newConfig.Performance.StatsLoadConcurrency = 0 // no worker to consume channel
newConfig.Performance.StatsLoadQueueSize = 1
config.StoreGlobalConfig(newConfig)
defer config.StoreGlobalConfig(originConfig)
store, dom, clean := testkit.CreateMockStoreAndDomain(t)
defer clean()
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
originalVal1 := tk.MustQuery("select @@tidb_stats_load_pseudo_timeout").Rows()[0][0].(string)
defer func() {
tk.MustExec(fmt.Sprintf("set global tidb_stats_load_pseudo_timeout = %v", originalVal1))
}()
ctx := tk.Session().(sessionctx.Context)
tk.MustExec("drop table if exists t")
tk.MustExec("set @@session.tidb_analyze_version=2")
// since queue full, make sync-wait return as timeout as soon as possible
tk.MustExec("set @@session.tidb_stats_load_sync_wait = 1")
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)")
oriLease := dom.StatsHandle().Lease()
dom.StatsHandle().SetLease(1)
defer func() {
dom.StatsHandle().SetLease(oriLease)
}()
tk.MustExec("analyze table t")
is := dom.InfoSchema()
require.NoError(t, dom.StatsHandle().Update(is))
tbl, err := is.TableByName(model.NewCIStr("test"), model.NewCIStr("t"))
require.NoError(t, err)
tableInfo := tbl.Meta()
neededColumn := model.TableColumnID{TableID: tableInfo.ID, ColumnID: tableInfo.Columns[0].ID}
resultCh := make(chan model.TableColumnID, 1)
timeout := time.Duration(1<<63 - 1)
dom.StatsHandle().AppendNeededColumn(neededColumn, resultCh, timeout) // make channel queue full
stmt, err := p.ParseOneStmt("select * from t where c>1", "", "")
require.NoError(t, err)
tk.MustExec("set global tidb_stats_load_pseudo_timeout=false")
_, _, err = planner.Optimize(context.TODO(), ctx, stmt, is)
require.Error(t, err) // fail sql for timeout when pseudo=false
tk.MustExec("set global tidb_stats_load_pseudo_timeout=true")
plan, _, err := planner.Optimize(context.TODO(), ctx, stmt, is)
require.NoError(t, err) // not fail sql for timeout when pseudo=true
switch pp := plan.(type) {
case *plannercore.PhysicalTableReader:
stats := pp.Stats().HistColl
require.Greater(t, countFullStats(stats, tableInfo.Columns[0].ID), 0)
require.Equal(t, 0, countFullStats(stats, tableInfo.Columns[2].ID)) // pseudo stats
default:
t.Error("unexpected plan:", pp)
}
}