Skip to content

Commit dd180de

Browse files
authored
infoschema: optimize 'select count(*) from information_schema.tables' for v2 (#55574)
close #55515
1 parent 0583e84 commit dd180de

File tree

6 files changed

+183
-9
lines changed

6 files changed

+183
-9
lines changed

pkg/executor/infoschema_reader.go

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -689,15 +689,25 @@ func (e *memtableRetriever) setDataFromOneTable(
689689
return rows, nil
690690
}
691691

692-
func (e *memtableRetriever) setDataFromTables(ctx context.Context, sctx sessionctx.Context) error {
693-
useStatsCache := e.updateStatsCacheIfNeed()
694-
checker := privilege.GetPrivilegeManager(sctx)
692+
func onlySchemaOrTableColumns(columns []*model.ColumnInfo) bool {
693+
if len(columns) <= 3 {
694+
for _, colInfo := range columns {
695+
switch colInfo.Name.L {
696+
case "table_schema":
697+
case "table_name":
698+
case "table_catalog":
699+
default:
700+
return false
701+
}
702+
}
703+
return true
704+
}
705+
return false
706+
}
695707

708+
func (e *memtableRetriever) setDataFromTables(ctx context.Context, sctx sessionctx.Context) error {
696709
var rows [][]types.Datum
697-
loc := sctx.GetSessionVars().TimeZone
698-
if loc == nil {
699-
loc = time.Local
700-
}
710+
checker := privilege.GetPrivilegeManager(sctx)
701711
ex, ok := e.extractor.(*plannercore.InfoSchemaTablesExtractor)
702712
if !ok {
703713
return errors.Errorf("wrong extractor type: %T, expected InfoSchemaTablesExtractor", e.extractor)
@@ -706,10 +716,82 @@ func (e *memtableRetriever) setDataFromTables(ctx context.Context, sctx sessionc
706716
return nil
707717
}
708718

719+
// Special optimize for queries on infoschema v2 like:
720+
// select count(table_schema) from INFORMATION_SCHEMA.TABLES
721+
// select count(*) from INFORMATION_SCHEMA.TABLES
722+
// select table_schema, table_name from INFORMATION_SCHEMA.TABLES
723+
// column pruning in general is not supported here.
724+
if onlySchemaOrTableColumns(e.columns) {
725+
is := e.is
726+
if raw, ok := is.(*infoschema.SessionExtendedInfoSchema); ok {
727+
is = raw.InfoSchema
728+
}
729+
v2, ok := is.(interface {
730+
IterateAllTableItems(visit func(infoschema.TableItem) bool)
731+
})
732+
if ok {
733+
if x := ctx.Value("cover-check"); x != nil {
734+
// The interface assertion is too tricky, so we add test to cover here.
735+
// To ensure that if implementation changes one day, we can catch it.
736+
slot := x.(*bool)
737+
*slot = true
738+
}
739+
v2.IterateAllTableItems(func(t infoschema.TableItem) bool {
740+
if !ex.HasTableName(t.TableName.L) {
741+
return true
742+
}
743+
if !ex.HasTableSchema(t.DBName.L) {
744+
return true
745+
}
746+
if checker != nil && !checker.RequestVerification(sctx.GetSessionVars().ActiveRoles, t.DBName.L, t.TableName.L, "", mysql.SelectPriv) {
747+
return true
748+
}
749+
750+
record := types.MakeDatums(
751+
infoschema.CatalogVal, // TABLE_CATALOG
752+
t.DBName.O, // TABLE_SCHEMA
753+
t.TableName.O, // TABLE_NAME
754+
nil, // TABLE_TYPE
755+
nil, // ENGINE
756+
nil, // VERSION
757+
nil, // ROW_FORMAT
758+
nil, // TABLE_ROWS
759+
nil, // AVG_ROW_LENGTH
760+
nil, // DATA_LENGTH
761+
nil, // MAX_DATA_LENGTH
762+
nil, // INDEX_LENGTH
763+
nil, // DATA_FREE
764+
nil, // AUTO_INCREMENT
765+
nil, // CREATE_TIME
766+
nil, // UPDATE_TIME
767+
nil, // CHECK_TIME
768+
nil, // TABLE_COLLATION
769+
nil, // CHECKSUM
770+
nil, // CREATE_OPTIONS
771+
nil, // TABLE_COMMENT
772+
nil, // TIDB_TABLE_ID
773+
nil, // TIDB_ROW_ID_SHARDING_INFO
774+
nil, // TIDB_PK_TYPE
775+
nil, // TIDB_PLACEMENT_POLICY_NAME
776+
)
777+
rows = append(rows, record)
778+
return true
779+
})
780+
e.rows = rows
781+
return nil
782+
}
783+
}
784+
785+
// Normal code path.
709786
schemas, tables, err := ex.ListSchemasAndTables(ctx, e.is)
710787
if err != nil {
711788
return errors.Trace(err)
712789
}
790+
useStatsCache := e.updateStatsCacheIfNeed()
791+
loc := sctx.GetSessionVars().TimeZone
792+
if loc == nil {
793+
loc = time.Local
794+
}
713795
for i, table := range tables {
714796
rows, err = e.setDataFromOneTable(sctx, loc, checker, schemas[i], table, rows, useStatsCache)
715797
if err != nil {

pkg/executor/infoschema_reader_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,3 +957,32 @@ func TestInfoSchemaConditionWorks(t *testing.T) {
957957
rows = tk.MustQuery("select * from information_schema.partitions where table_schema = 'db_no_partition' and (partition_name is NULL or partition_name = 'p0');").Rows()
958958
require.Equal(t, 2, len(rows))
959959
}
960+
961+
func TestInfoschemaTablesSpecialOptimizationCovered(t *testing.T) {
962+
store := testkit.CreateMockStore(t)
963+
tk := testkit.NewTestKit(t, store)
964+
965+
for _, testCase := range []struct {
966+
sql string
967+
expect bool
968+
}{
969+
{"select table_name, table_schema from information_schema.tables", true},
970+
{"select table_name from information_schema.tables", true},
971+
{"select table_name from information_schema.tables where table_schema = 'test'", true},
972+
{"select table_schema from information_schema.tables", true},
973+
{"select count(table_schema) from information_schema.tables", true},
974+
{"select count(table_name) from information_schema.tables", true},
975+
{"select count(table_rows) from information_schema.tables", false},
976+
{"select count(1) from information_schema.tables", true},
977+
{"select count(*) from information_schema.tables", true},
978+
{"select count(1) from (select table_name from information_schema.tables) t", true},
979+
{"select * from information_schema.tables", false},
980+
{"select table_name, table_catalog from information_schema.tables", true},
981+
{"select table_name, table_rows from information_schema.tables", false},
982+
} {
983+
var covered bool
984+
ctx := context.WithValue(context.Background(), "cover-check", &covered)
985+
tk.MustQueryWithContext(ctx, testCase.sql)
986+
require.Equal(t, testCase.expect, covered, testCase.sql)
987+
}
988+
}

pkg/infoschema/infoschema_v2.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,34 @@ func (is *infoschemaV2) TableByID(ctx context.Context, id int64) (val table.Tabl
645645
return ret, true
646646
}
647647

648+
// TableItem is exported from tableItem.
649+
type TableItem struct {
650+
DBName model.CIStr
651+
TableName model.CIStr
652+
}
653+
654+
// IterateAllTableItems is used for special performance optimization.
655+
// Used by executor/infoschema_reader.go to handle reading from INFORMATION_SCHEMA.TABLES.
656+
func (is *infoschemaV2) IterateAllTableItems(visit func(TableItem) bool) {
657+
pivot, ok := is.byName.Max()
658+
if !ok {
659+
return
660+
}
661+
if !visit(TableItem{DBName: pivot.dbName, TableName: pivot.tableName}) {
662+
return
663+
}
664+
is.byName.Descend(pivot, func(item tableItem) bool {
665+
if pivot.dbName == item.dbName && pivot.tableName == item.tableName {
666+
return true // skip MVCC version
667+
}
668+
pivot = item
669+
if !item.tomb {
670+
return visit(TableItem{DBName: item.dbName, TableName: item.tableName})
671+
}
672+
return true
673+
})
674+
}
675+
648676
// IsSpecialDB tells whether the database is a special database.
649677
func IsSpecialDB(dbName string) bool {
650678
return dbName == util.InformationSchemaName.L ||

pkg/planner/core/memtable_infoschema_extractor.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,16 @@ func NewInfoSchemaTablesExtractor() *InfoSchemaTablesExtractor {
260260
return e
261261
}
262262

263+
// HasTableName returns true if table name is specified in predicates.
264+
func (e *InfoSchemaTablesExtractor) HasTableName(name string) bool {
265+
return !e.filter(TableName, name)
266+
}
267+
268+
// HasTableSchema returns true if table schema is specified in predicates.
269+
func (e *InfoSchemaTablesExtractor) HasTableSchema(name string) bool {
270+
return !e.filter(TableSchema, name)
271+
}
272+
263273
// InfoSchemaViewsExtractor is the predicate extractor for information_schema.views.
264274
type InfoSchemaViewsExtractor struct {
265275
InfoSchemaBaseExtractor

tests/integrationtest/r/executor/infoschema_reader.result

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,5 +420,22 @@ select * from information_schema.table_constraints where table_schema = 'executo
420420
CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_SCHEMA TABLE_NAME CONSTRAINT_TYPE
421421
select * from information_schema.table_constraints where table_schema = 'executor__infoschema_reader' and CONSTRAINT_NAME = 'c1';
422422
CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_SCHEMA TABLE_NAME CONSTRAINT_TYPE
423-
def executor__infoschema_reader PRIMARY executor__infoschema_reader t PRIMARY KEY
424-
def executor__infoschema_reader PRIMARY executor__infoschema_reader t_int PRIMARY KEY
423+
select TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,TABLE_TYPE,ENGINE,VERSION from information_schema.tables where table_name = 't';
424+
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE ENGINE VERSION
425+
def executor__infoschema_reader t BASE TABLE InnoDB 10
426+
select table_name, table_schema from information_schema.tables where table_name = 't';
427+
table_name table_schema
428+
t executor__infoschema_reader
429+
select table_name from information_schema.tables where table_name = 't';
430+
table_name
431+
t
432+
explain format='brief' select table_name, table_schema from information_schema.tables;
433+
id estRows task access object operator info
434+
Projection 10000.00 root Column#3, Column#2
435+
└─MemTableScan 10000.00 root table:TABLES
436+
select count(*) from information_schema.tables where table_name = 't';
437+
count(*)
438+
1
439+
select count(table_name) from information_schema.tables where table_name = 't';
440+
count(table_name)
441+
1

tests/integrationtest/t/executor/infoschema_reader.test

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,11 @@ select * from information_schema.table_constraints where table_schema = 'executo
307307
select * from information_schema.table_constraints where table_schema = 'executor__infoschema_reader' and table_name = 'non_exist';
308308
--sorted_result
309309
select * from information_schema.table_constraints where table_schema = 'executor__infoschema_reader' and CONSTRAINT_NAME = 'c1';
310+
311+
# TestTables
312+
select TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,TABLE_TYPE,ENGINE,VERSION from information_schema.tables where table_name = 't';
313+
select table_name, table_schema from information_schema.tables where table_name = 't';
314+
select table_name from information_schema.tables where table_name = 't';
315+
explain format='brief' select table_name, table_schema from information_schema.tables;
316+
select count(*) from information_schema.tables where table_name = 't';
317+
select count(table_name) from information_schema.tables where table_name = 't';

0 commit comments

Comments
 (0)