diff --git a/sqle/driver/mysql/advisor.go b/sqle/driver/mysql/advisor.go index 5ac196fd5f..bd6ee24fa8 100644 --- a/sqle/driver/mysql/advisor.go +++ b/sqle/driver/mysql/advisor.go @@ -21,6 +21,13 @@ import ( const ( MAX_INDEX_COLUMN string = "composite_index_max_column" MAX_INDEX_COLUMN_DEFAULT_VALUE int = 5 + threeStarIndexAdviceFormat string = "索引建议 | 根据三星索引设计规范,建议对表%s添加复合索引:【%s】" + prefixIndexAdviceFormat string = "索引建议 | SQL使用了前缀模式匹配,数据量大时,可建立翻转函数索引" + extremalIndexAdviceFormat string = "索引建议 | SQL使用了最值函数,可以利用索引有序的性质快速找到最值,建议对表%s添加单列索引,参考列:%s" + functionIndexAdviceFormatV80 string = "索引建议 | SQL使用了函数作为查询条件,在MySQL8.0.13以上的版本,可以创建函数索引,建议对表%s添加函数索引,参考列:%s" + functionIndexAdviceFormatV57 string = "索引建议 | SQL使用了函数作为查询条件,在MySQL5.7以上的版本,可以在虚拟列上创建索引,建议对表%s添加虚拟列索引,参考列:%s" + functionIndexAdviceFormatAll string = "索引建议 | SQL使用了函数作为查询条件,在MySQL5.7以上的版本,可以在虚拟列上创建索引,在MySQL8.0.13以上的版本,可以创建函数索引,建议根据MySQL版本对表%s添加合适的索引,参考列:%s" + joinIndexAdviceFormat string = "索引建议 | SQL中字段%s为被驱动表%s上的关联字段,建议对表%s添加单列索引,参考列:%s" ) type OptimizeResult struct { @@ -345,10 +352,12 @@ func (a *threeStarIndexAdvisor) GiveAdvices() []*OptimizeResult { if util.IsIndex(a.adviceColumns.columnMap, a.drivingTableCreateStmt.Constraints) { return nil } + tableName := util.GetTableNameFromTableSource(a.drivingTableSource) + indexColumns := a.adviceColumns.stringSlice() return []*OptimizeResult{{ - TableName: util.GetTableNameFromTableSource(a.drivingTableSource), - IndexedColumns: a.adviceColumns.stringSlice(), - Reason: fmt.Sprintf("索引建议 | SQL:%s 中,根据三星索引设计规范", restore(a.originNode)), + TableName: tableName, + IndexedColumns: indexColumns, + Reason: fmt.Sprintf(threeStarIndexAdviceFormat, tableName, strings.Join(indexColumns, ",")), }} } @@ -680,6 +689,9 @@ func newJoinIndexAdvisor(ctx *session.Context, log *logrus.Entry, originNode ast } func (a *joinIndexAdvisor) GiveAdvices() []*OptimizeResult { + if a.originNode == nil { + return nil + } err := a.loadEssentials() if err != nil { a.log.Logger.Warnf("when join index advisor load essentials failed, err:%v", err) @@ -780,7 +792,7 @@ func (a *joinIndexAdvisor) giveAdvice() { a.advices = append(a.advices, &OptimizeResult{ TableName: drivenTableName, IndexedColumns: indexColumn, - Reason: fmt.Sprintf("索引建议 | SQL:%s 中,字段 %s 为被驱动表 %s 上的关联字段", restore(a.currentNode), strings.Join(indexColumn, ","), drivenTableName), + Reason: fmt.Sprintf(joinIndexAdviceFormat, strings.Join(indexColumn, ","), drivenTableName, drivenTableName, strings.Join(indexColumn, ",")), }) } @@ -841,15 +853,18 @@ func newFunctionIndexAdvisor(ctx *session.Context, log *logrus.Entry, originNode } func (a *functionIndexAdvisor) GiveAdvices() []*OptimizeResult { + node, ok := a.originNode.(*ast.SelectStmt) + if !ok { + return nil + } + if node.Where == nil { + return nil + } err := a.loadEssentials() if err != nil { a.log.Logger.Warnf("when function index advisor load essentials failed, err:%v", err) return nil } - node, ok := a.originNode.(*ast.SelectStmt) - if !ok { - return nil - } node.Where.Accept(a) return a.advices } @@ -917,7 +932,7 @@ func (a *functionIndexAdvisor) giveAdvice() { a.advices = append(a.advices, &OptimizeResult{ TableName: tableName, IndexedColumns: columns, - Reason: fmt.Sprintf("索引建议 | SQL:%s 中,使用了函数作为查询条件,在MySQL5.7以上的版本,可以在虚拟列上创建索引", restore(a.currentNode.L)), + Reason: fmt.Sprintf(functionIndexAdviceFormatV57, tableName, strings.Join(columns, ",")), }) return } @@ -925,7 +940,7 @@ func (a *functionIndexAdvisor) giveAdvice() { a.advices = append(a.advices, &OptimizeResult{ TableName: tableName, IndexedColumns: columns, - Reason: fmt.Sprintf("索引建议 | SQL:%s 中,使用了函数作为查询条件,在MySQL8.0.13以上的版本,可以创建函数索引", restore(a.currentNode.L)), + Reason: fmt.Sprintf(functionIndexAdviceFormatV80, tableName, strings.Join(columns, ",")), }) return } @@ -933,7 +948,7 @@ func (a *functionIndexAdvisor) giveAdvice() { a.advices = append(a.advices, &OptimizeResult{ TableName: tableName, IndexedColumns: columns, - Reason: fmt.Sprintf("索引建议 | SQL:%s 中,使用了函数作为查询条件,在MySQL5.7以上的版本,可以在虚拟列上创建索引,在MySQL8.0.13以上的版本,可以创建函数索引", restore(a.currentNode.L)), + Reason: fmt.Sprintf(functionIndexAdviceFormatAll, tableName, strings.Join(columns, ",")), }) } @@ -966,6 +981,9 @@ func newExtremalIndexAdvisor(ctx *session.Context, log *logrus.Entry, originNode } func (a *extremalIndexAdvisor) GiveAdvices() []*OptimizeResult { + if a.originNode == nil { + return nil + } err := a.loadEssentials() if err != nil { a.log.Logger.Warnf("when extremal index advisor load essentials failed, err:%v", err) @@ -1027,7 +1045,7 @@ func (a *extremalIndexAdvisor) giveAdvice() { a.advices = append(a.advices, &OptimizeResult{ TableName: tableName, IndexedColumns: []string{indexColumn}, - Reason: fmt.Sprintf("索引建议 | SQL:%s 中,使用了最值函数,可以利用索引有序的性质快速找到最值", restore(a.currentNode)), + Reason: fmt.Sprintf(extremalIndexAdviceFormat, tableName, indexColumn), }) } @@ -1056,15 +1074,18 @@ func newPrefixIndexAdvisor(ctx *session.Context, log *logrus.Entry, originNode a } func (a *prefixIndexAdvisor) GiveAdvices() []*OptimizeResult { + node, ok := a.originNode.(*ast.SelectStmt) + if !ok { + return nil + } + if node.Where == nil { + return nil + } err := a.loadEssentials() if err != nil { a.log.Logger.Warnf("when prefix index advisor load essentials failed, err:%v", err) return nil } - node, ok := a.originNode.(*ast.SelectStmt) - if !ok { - return nil - } node.Where.Accept(a) return a.advices } @@ -1100,7 +1121,7 @@ func (v *prefixIndexAdvisor) Leave(in ast.Node) (out ast.Node, ok bool) { } func (a *prefixIndexAdvisor) giveAdvice() { - if !util.CheckWhereFuzzySearch(a.currentNode) { + if !util.CheckWhereLeftFuzzySearch(a.currentNode) { return } column, ok := a.currentNode.Expr.(*ast.ColumnNameExpr) @@ -1116,6 +1137,6 @@ func (a *prefixIndexAdvisor) giveAdvice() { a.advices = append(a.advices, &OptimizeResult{ TableName: tableName, IndexedColumns: []string{column.Name.Name.L}, - Reason: fmt.Sprintf("索引建议 | SQL:%s 中,使用了前缀模式匹配,在数据量大的时候,可以建立翻转函数索引", restore(a.currentNode)), + Reason: prefixIndexAdviceFormat, }) } diff --git a/sqle/driver/mysql/advisor_test.go b/sqle/driver/mysql/advisor_test.go index cbeba3306a..26794982c1 100644 --- a/sqle/driver/mysql/advisor_test.go +++ b/sqle/driver/mysql/advisor_test.go @@ -3,6 +3,7 @@ package mysql import ( "fmt" "regexp" + "strings" "testing" sqlmock "github.com/DATA-DOG/go-sqlmock" @@ -25,22 +26,62 @@ func mockPrefixIndexOptimizeResult(caseName string, c optimizerTestContent, t *t return mockOptimizeResultWithAdvisor(c.sql, c.maxColumn, c.queryResults, caseName, t, newPrefixIndexAdvisor) } +func newPrefixOptimizeResult(columns []string, tableName string) *OptimizeResult { + return &OptimizeResult{ + TableName: tableName, + IndexedColumns: columns, + Reason: prefixIndexAdviceFormat, + } +} + func mockThreeStarOptimizeResult(caseName string, c optimizerTestContent, t *testing.T) []*OptimizeResult { return mockOptimizeResultWithAdvisor(c.sql, c.maxColumn, c.queryResults, caseName, t, newThreeStarIndexAdvisor) } +func newThreeStarOptimizeResult(columns []string, tableName string) *OptimizeResult { + return &OptimizeResult{ + Reason: fmt.Sprintf(threeStarIndexAdviceFormat, tableName, strings.Join(columns, ",")), + IndexedColumns: columns, + TableName: tableName, + } +} + func mockFunctionOptimizeResult(caseName string, c optimizerTestContent, t *testing.T) []*OptimizeResult { return mockOptimizeResultWithAdvisor(c.sql, c.maxColumn, c.queryResults, caseName, t, newFunctionIndexAdvisor) } +func newFunctionIndexOptimizeResult(format string, columns []string, tableName string) *OptimizeResult { + return &OptimizeResult{ + TableName: tableName, + IndexedColumns: columns, + Reason: fmt.Sprintf(format, tableName, strings.Join(columns, ",")), + } +} + func mockExtremalOptimizeResult(caseName string, c optimizerTestContent, t *testing.T) []*OptimizeResult { return mockOptimizeResultWithAdvisor(c.sql, c.maxColumn, c.queryResults, caseName, t, newExtremalIndexAdvisor) } +func newExtremalIndexOptimizeResult(column string, tableName string) *OptimizeResult { + return &OptimizeResult{ + TableName: tableName, + IndexedColumns: []string{column}, + Reason: fmt.Sprintf(extremalIndexAdviceFormat, tableName, column), + } +} + func mockJoinOptimizeResult(caseName string, c optimizerTestContent, t *testing.T) []*OptimizeResult { return mockOptimizeResultWithAdvisor(c.sql, c.maxColumn, c.queryResults, caseName, t, newJoinIndexAdvisor) } +func newJoinIndexOptimizeResult(indexColumn []string, drivenTableName string) *OptimizeResult { + return &OptimizeResult{ + Reason: fmt.Sprintf(joinIndexAdviceFormat, strings.Join(indexColumn, ","), drivenTableName, drivenTableName, strings.Join(indexColumn, ",")), + IndexedColumns: indexColumn, + TableName: drivenTableName, + } +} + func mockOptimizeResultWithAdvisor(sql string, maxColumn int, queryResults []*queryResult, caseName string, t *testing.T, f func(ctx *session.Context, log *logrus.Entry, originNode ast.Node, params params.Params) CreateIndexAdvisor) []*OptimizeResult { e, handler, err := executor.NewMockExecutor() assert.NoErrorf(t, err, caseName) @@ -103,11 +144,7 @@ func TestPrefixIndexOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:`v1` LIKE '%_set' 中,使用了前缀模式匹配,在数据量大的时候,可以建立翻转函数索引", - IndexedColumns: []string{"v1"}, - TableName: "exist_tb_1", - }, + newPrefixOptimizeResult([]string{"v1"}, "exist_tb_1"), }, maxColumn: 1, } @@ -120,11 +157,7 @@ func TestPrefixIndexOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:`v1` LIKE UPPER('_set') 中,使用了前缀模式匹配,在数据量大的时候,可以建立翻转函数索引", - IndexedColumns: []string{"v1"}, - TableName: "exist_tb_1", - }, + newPrefixOptimizeResult([]string{"v1"}, "exist_tb_1"), }, maxColumn: 1, } @@ -138,6 +171,26 @@ func TestPrefixIndexOptimize(t *testing.T) { }, maxColumn: 1, } + testCases["test4-without where"] = optimizerTestContent{ + sql: `SELECT * FROM exist_tb_1`, + queryResults: []*queryResult{ + { + query: regexp.QuoteMeta(fmt.Sprintf(explainFormat, `SELECT * FROM exist_tb_1`)), + result: sqlmock.NewRows(explainColumns).AddRow(explainTypeAll, drivingTable), + }, + }, + maxColumn: 1, + } + testCases["test5-full fuzzy search"] = optimizerTestContent{ + sql: `SELECT * FROM exist_tb_1 WHERE v1 LIKE upper("_set_")`, + queryResults: []*queryResult{ + { + query: regexp.QuoteMeta(fmt.Sprintf(explainFormat, `SELECT * FROM exist_tb_1 WHERE v1 LIKE upper("_set_")`)), + result: sqlmock.NewRows(explainColumns).AddRow(explainTypeAll, drivingTable), + }, + }, + maxColumn: 1, + } testCases.testAll(mockPrefixIndexOptimizeResult, t) } @@ -155,11 +208,7 @@ func TestFunctionIndexOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:LOWER(`v1`) 中,使用了函数作为查询条件,在MySQL8.0.13以上的版本,可以创建函数索引", - IndexedColumns: []string{"v1"}, - TableName: "exist_tb_1", - }, + newFunctionIndexOptimizeResult(functionIndexAdviceFormatV80, []string{"v1"}, "exist_tb_1"), }, maxColumn: 4, } @@ -175,15 +224,11 @@ func TestFunctionIndexOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:LOWER(`v1`) 中,使用了函数作为查询条件,在MySQL5.7以上的版本,可以在虚拟列上创建索引", - IndexedColumns: []string{"v1"}, - TableName: "exist_tb_1", - }, + newFunctionIndexOptimizeResult(functionIndexAdviceFormatV57, []string{"v1"}, "exist_tb_1"), }, maxColumn: 4, } - testCases["test3"] = optimizerTestContent{ + testCases["test3-version lower than 5.7.0"] = optimizerTestContent{ sql: `SELECT v1 FROM exist_tb_1 WHERE LOWER(v1) = "s"`, queryResults: []*queryResult{ { @@ -196,7 +241,7 @@ func TestFunctionIndexOptimize(t *testing.T) { }, maxColumn: 4, } - testCases["test4"] = optimizerTestContent{ + testCases["test4-not use function"] = optimizerTestContent{ sql: `SELECT v1 FROM exist_tb_1 WHERE v1 = "s"`, queryResults: []*queryResult{ { @@ -209,6 +254,32 @@ func TestFunctionIndexOptimize(t *testing.T) { }, maxColumn: 4, } + testCases["test5-version can not parse"] = optimizerTestContent{ + sql: `SELECT v1 FROM exist_tb_1 WHERE LOWER(v1) = "s"`, + queryResults: []*queryResult{ + { + query: regexp.QuoteMeta(fmt.Sprintf(explainFormat, `SELECT v1 FROM exist_tb_1 WHERE LOWER(v1) = "s"`)), + result: sqlmock.NewRows(explainColumns).AddRow(explainTypeAll, drivingTable), + }, { + query: regexp.QuoteMeta("SHOW GLOBAL VARIABLES LIKE 'version'"), + result: sqlmock.NewRows([]string{"Value"}).AddRow("?,?,?"), + }, + }, + expectResults: []*OptimizeResult{ + newFunctionIndexOptimizeResult(functionIndexAdviceFormatAll, []string{"v1"}, "exist_tb_1"), + }, + maxColumn: 4, + } + testCases["test6-without where"] = optimizerTestContent{ + sql: `SELECT v1 FROM exist_tb_1`, + queryResults: []*queryResult{ + { + query: regexp.QuoteMeta(fmt.Sprintf(explainFormat, `SELECT v1 FROM exist_tb_1`)), + result: sqlmock.NewRows(explainColumns).AddRow(explainTypeAll, drivingTable), + }, + }, + maxColumn: 4, + } testCases.testAll(mockFunctionOptimizeResult, t) } @@ -226,11 +297,7 @@ func TestThreeStarOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:SELECT `v1`,`v2` FROM `exist_tb_3` WHERE `v1`='s' ORDER BY `v3` 中,根据三星索引设计规范", - IndexedColumns: []string{"v1", "v3", "v2"}, - TableName: "exist_tb_3", - }, + newThreeStarOptimizeResult([]string{"v1", "v3", "v2"}, "exist_tb_3"), }, maxColumn: 4, } @@ -246,11 +313,7 @@ func TestThreeStarOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:SELECT `id`,`v1`,`v2`,`v3` FROM `exist_tb_3` WHERE `v1`='s' ORDER BY `v3` 中,根据三星索引设计规范", - IndexedColumns: []string{"v1", "v3"}, - TableName: "exist_tb_3", - }, + newThreeStarOptimizeResult([]string{"v1", "v3"}, "exist_tb_3"), }, maxColumn: 3, } @@ -266,11 +329,7 @@ func TestThreeStarOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:SELECT `id`,`v1`,`v2`,`v3` FROM `exist_tb_3` WHERE `v1`='s' ORDER BY `v3` 中,根据三星索引设计规范", - IndexedColumns: []string{"v1", "v3", "id", "v2"}, - TableName: "exist_tb_3", - }, + newThreeStarOptimizeResult([]string{"v1", "v3", "id", "v2"}, "exist_tb_3"), }, maxColumn: 4, } @@ -286,11 +345,7 @@ func TestThreeStarOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:SELECT `id`,`v1`,`v2`,`v3` FROM `exist_tb_3` WHERE `v1`='s' AND `v2`='s' AND `id`=20 ORDER BY `v3` 中,根据三星索引设计规范", - IndexedColumns: []string{"id", "v2", "v3"}, - TableName: "exist_tb_3", - }, + newThreeStarOptimizeResult([]string{"id", "v2", "v3"}, "exist_tb_3"), }, maxColumn: 3, } @@ -306,11 +361,7 @@ func TestThreeStarOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:SELECT `id`,`v1`,`v2`,`v3` FROM `exist_tb_3` WHERE `v1`='s' AND `v2`='s' AND `id`=20 ORDER BY `v2` 中,根据三星索引设计规范", - IndexedColumns: []string{"id", "v2", "v1"}, - TableName: "exist_tb_3", - }, + newThreeStarOptimizeResult([]string{"id", "v2", "v1"}, "exist_tb_3"), }, maxColumn: 3, } @@ -326,11 +377,7 @@ func TestThreeStarOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:SELECT `id`,`v1`,`v2`,`v3` FROM `exist_tb_3` WHERE `v1`='s' AND `v2`='s' AND `id`<=20 ORDER BY `v3` 中,根据三星索引设计规范", - IndexedColumns: []string{"v2", "v1", "v3", "id"}, - TableName: "exist_tb_3", - }, + newThreeStarOptimizeResult([]string{"v2", "v1", "v3", "id"}, "exist_tb_3"), }, maxColumn: 4, } @@ -359,11 +406,7 @@ func TestThreeStarOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:SELECT `id`,`v1`,`v2`,`v3` FROM `exist_tb_3` WHERE `v1`='s' AND `v3`='6' AND `v2`='s' OR `id`=3 ORDER BY `v3` 中,根据三星索引设计规范", - IndexedColumns: []string{"id", "v3", "v2", "v1"}, - TableName: "exist_tb_3", - }, + newThreeStarOptimizeResult([]string{"id", "v3", "v2", "v1"}, "exist_tb_3"), }, maxColumn: 4, } @@ -379,11 +422,7 @@ func TestThreeStarOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:SELECT * FROM `exist_tb_3` WHERE `v1`='s' AND `v3`='6' AND `v2`='s' OR `id`=3 ORDER BY `v3` 中,根据三星索引设计规范", - IndexedColumns: []string{"id", "v3", "v2", "v1"}, - TableName: "exist_tb_3", - }, + newThreeStarOptimizeResult([]string{"id", "v3", "v2", "v1"}, "exist_tb_3"), }, maxColumn: 4, } @@ -401,16 +440,8 @@ func TestExtremalOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:MIN(`v3`) 中,使用了最值函数,可以利用索引有序的性质快速找到最值", - IndexedColumns: []string{"v3"}, - TableName: "exist_tb_1", - }, - { - Reason: "索引建议 | SQL:MAX(`v2`) 中,使用了最值函数,可以利用索引有序的性质快速找到最值", - IndexedColumns: []string{"v2"}, - TableName: "exist_tb_1", - }, + newExtremalIndexOptimizeResult("v3", "exist_tb_1"), + newExtremalIndexOptimizeResult("v2", "exist_tb_1"), }, maxColumn: 4, } @@ -423,11 +454,7 @@ func TestExtremalOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:MIN(`v2`) 中,使用了最值函数,可以利用索引有序的性质快速找到最值", - IndexedColumns: []string{"v2"}, - TableName: "exist_tb_1", - }, + newExtremalIndexOptimizeResult("v2", "exist_tb_1"), }, maxColumn: 4, } @@ -440,11 +467,7 @@ func TestExtremalOptimize(t *testing.T) { }, }, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:MIN(`v2`) 中,使用了最值函数,可以利用索引有序的性质快速找到最值", - IndexedColumns: []string{"v2"}, - TableName: "exist_tb_1", - }, + newExtremalIndexOptimizeResult("v2", "exist_tb_1"), }, maxColumn: 4, } @@ -483,11 +506,7 @@ func TestJoinOptimize(t *testing.T) { }, maxColumn: 1, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:`exist_tb_1` AS `t1` JOIN `exist_tb_2` AS `t2` ON `t1`.`v1`=`t2`.`v1` 中,字段 v1 为被驱动表 t2 上的关联字段", - IndexedColumns: []string{"v1"}, - TableName: "t2", - }, + newJoinIndexOptimizeResult([]string{"v1"}, "t2"), }, } testCases["test2"] = optimizerTestContent{ @@ -500,11 +519,7 @@ func TestJoinOptimize(t *testing.T) { }, maxColumn: 1, expectResults: []*OptimizeResult{ - { - Reason: "索引建议 | SQL:`exist_tb_1` AS `t1` JOIN `exist_tb_2` AS `t2` USING (`v1`) 中,字段 v1 为被驱动表 t2 上的关联字段", - IndexedColumns: []string{"v1"}, - TableName: "t2", - }, + newJoinIndexOptimizeResult([]string{"v1"}, "t2"), }, } testCases["test3"] = optimizerTestContent{ diff --git a/sqle/driver/mysql/mysql.go b/sqle/driver/mysql/mysql.go index f1710c6279..b4a3321ec7 100644 --- a/sqle/driver/mysql/mysql.go +++ b/sqle/driver/mysql/mysql.go @@ -373,7 +373,7 @@ func (i *MysqlDriverImpl) audit(ctx context.Context, sql string) (*driverV2.Audi i.result.Add( driverV2.RuleLevelNotice, rulepkg.ConfigOptimizeIndexEnabled, - fmt.Sprintf("%s,建议从表%s中选取合适的列添加索引,参考列:%s \n", advice.Reason, advice.TableName, strings.Join(advice.IndexedColumns, "、")), + advice.Reason, ) } diff --git a/sqle/driver/mysql/util/parser_helper.go b/sqle/driver/mysql/util/parser_helper.go index 400459149a..cb85f6679f 100644 --- a/sqle/driver/mysql/util/parser_helper.go +++ b/sqle/driver/mysql/util/parser_helper.go @@ -458,28 +458,48 @@ func WhereStmtExistNot(where ast.ExprNode) bool { return existNOT } +// Check is exist a left fuzzy query. +func CheckWhereLeftFuzzySearch(where ast.ExprNode) bool { + isExist := false + ScanWhereStmt(func(expr ast.ExprNode) (skip bool) { + switch x := expr.(type) { + case *ast.PatternLikeExpr: + var resultString string + switch pattern := x.Pattern.(type) { + case *driver.ValueExpr: + resultString = pattern.Datum.GetString() + case *ast.FuncCallExpr: + resultString = NewFuncCallStringResultGenerator(pattern).GenerateResult() + } + if (strings.HasPrefix(resultString, "%") || strings.HasPrefix(resultString, "_")) && !(strings.HasSuffix(resultString, "%") || strings.HasSuffix(resultString, "_")) { + isExist = true + return true + } + } + return false + }, where) + return isExist +} + // Check is exist a full fuzzy query or a left fuzzy query. E.g: %name% or %name func CheckWhereFuzzySearch(where ast.ExprNode) bool { isExist := false ScanWhereStmt(func(expr ast.ExprNode) (skip bool) { switch x := expr.(type) { case *ast.PatternLikeExpr: + var resultString string switch pattern := x.Pattern.(type) { case *driver.ValueExpr: - datum := pattern.Datum.GetString() - if strings.HasPrefix(datum, "%") || strings.HasPrefix(datum, "_") { - isExist = true - return true - } + resultString = pattern.Datum.GetString() case *ast.FuncCallExpr: - result := NewFuncCallStringResultGenerator(pattern).GenerateResult() - if strings.HasPrefix(result, "%") || strings.HasPrefix(result, "_") { - isExist = true - return true - } + resultString = NewFuncCallStringResultGenerator(pattern).GenerateResult() // unsupport subquery result as value of like // example (select '%' 'any_string' '%') } + if strings.HasPrefix(resultString, "%") || strings.HasPrefix(resultString, "_") { + isExist = true + return true + } } return false }, where)