diff --git a/.gitignore b/.gitignore index eecdba0afbe7452c5c33a22a075815882bb5942e..7dbe1bab031e0687ff27fe56b004c41d7b40004c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ bin/ release/ +test/tmp/ common/version.go doc/blueprint/ *.iml diff --git a/.travis.yml b/.travis.yml index b707490de2d89cfe351ead8b9b005d224fa12e50..b79a35b319a37ca85c447065772fccb375963add 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,15 @@ services: before_install: - docker pull mysql + - sudo add-apt-repository ppa:duggan/bats --yes + - sudo apt-get update -qq + - sudo apt-get install -qq bats script: - make build - make docker - make cover + - make test-cli after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/Makefile b/Makefile index 9f7636a94d155395338c1a1c43cb29b992d59c82..95d88c8bdefc5512b15b0f323ca76304924bd2be 100644 --- a/Makefile +++ b/Makefile @@ -72,15 +72,24 @@ fmt: go_version_check .PHONY: test test: @echo "$(CGREEN)Run all test cases ...$(CEND)" - go test -race ./... + go test -timeout 10m -race ./... @echo "test Success!" # Rule golang test cases with `-update` flag +.PHONY: test-update test-update: @echo "$(CGREEN)Run all test cases with -update flag ...$(CEND)" go test ./... -update @echo "test-update Success!" +# Using bats test framework run all cli test cases +# https://github.com/sstephenson/bats +.PHONY: test-cli +test-cli: build + @echo "$(CGREEN)Run all cli test cases ...$(CEND)" + bats ./test + @echo "test-cli Success!" + # Code Coverage # colorful coverage numerical >=90% GREEN, <80% RED, Other YELLOW .PHONY: cover @@ -180,46 +189,44 @@ release: build docker: @echo "$(CGREEN)Build mysql test enviorment ...$(CEND)" @docker stop soar-mysql 2>/dev/null || true - @docker wait soar-mysql 2>/dev/null || true + @docker wait soar-mysql 2>/dev/null >/dev/null || true @echo "docker run --name soar-mysql $(MYSQL_RELEASE):$(MYSQL_VERSION)" @docker run --name soar-mysql --rm -d \ -e MYSQL_ROOT_PASSWORD=1tIsB1g3rt \ -e MYSQL_DATABASE=sakila \ -p 3306:3306 \ - -v `pwd`/doc/example/sakila.sql.gz:/docker-entrypoint-initdb.d/sakila.sql.gz \ + -v `pwd`/test/sql/init.sql.gz:/docker-entrypoint-initdb.d/init.sql.gz \ $(MYSQL_RELEASE):$(MYSQL_VERSION) @echo "waiting for sakila database initializing " - @while ! docker exec soar-mysql mysql --user=root --password=1tIsB1g3rt --host "127.0.0.1" --silent -NBe "do 1" >/dev/null 2>&1 ; do \ - printf '.' ; \ - sleep 1 ; \ - done ; \ - echo '.' - @echo "mysql test enviorment is ready!" + @timeout=180; while [ $${timeout} -gt 0 ] ; do \ + if ! docker exec soar-mysql mysql --user=root --password=1tIsB1g3rt --host "127.0.0.1" --silent -NBe "do 1" >/dev/null 2>&1 ; then \ + timeout=`expr $$timeout - 1`; \ + printf '.' ; sleep 1 ; \ + else \ + echo "." ; echo "mysql test enviorment is ready!" ; break ; \ + fi ; \ + if [ $$timeout = 0 ] ; then \ + echo "." ; echo "$(CRED)docker soar-mysql start timeout(180 s)!$(CEND)" ; exit 1 ; \ + fi ; \ + done .PHONY: docker-connect docker-connect: - docker exec -it soar-mysql mysql --user=root --password=1tIsB1g3rt --host "127.0.0.1" + @docker exec -it soar-mysql mysql --user=root --password=1tIsB1g3rt --host "127.0.0.1" sakila # attach docker container with bash interactive mode .PHONY: docker-it docker-it: docker exec -it soar-mysql /bin/bash -.PHONY: main_test -main_test: install - @echo "$(CGREEN)running main_test ...$(CEND)" - @echo "soar -list-test-sqls | soar" - @./doc/example/main_test.sh - @echo "main_test Success!" - .PHONY: daily -daily: | deps fmt vendor docker cover doc lint release install main_test clean logo +daily: | deps fmt vendor docker cover doc lint release install test-cli clean logo @echo "$(CGREEN)daily build finished ...$(CEND)" # vendor, docker will cost long time, if all those are ready, daily-quick will much more fast. .PHONY: daily-quick -daily-quick: | deps fmt cover main_test doc lint logo +daily-quick: | deps fmt cover test-cli doc lint logo @echo "$(CGREEN)daily-quick build finished ...$(CEND)" .PHONY: logo @@ -238,7 +245,7 @@ clean: rm -f ${BINARY}.$${GOOS}-$${GOARCH} ;\ done ;\ done - rm -f ${BINARY} coverage.* + rm -f ${BINARY} coverage.* test/tmp/* find . -name "*.log" -delete git clean -fi docker stop soar-mysql 2>/dev/null || true diff --git a/advisor/index.go b/advisor/index.go index d1351cff4758466b947993e6d7d60f1788ba6a00..27fd85c53a6b40606521db8f6115df61f0d0f1b6 100644 --- a/advisor/index.go +++ b/advisor/index.go @@ -18,6 +18,7 @@ package advisor import ( "fmt" + "sort" "strings" "github.com/XiaoMi/soar/ast" @@ -989,7 +990,13 @@ func (idxAdvs IndexAdvises) Format() map[string]Rule { rules[advKey].Content = strings.Trim(rules[advKey].Content, common.Config.Delimiter) } + var sortAdvs []string for adv := range rules { + sortAdvs = append(sortAdvs, adv) + } + sort.Strings(sortAdvs) + + for _, adv := range sortAdvs { key := fmt.Sprintf("IDX.%03d", number) ddl := ast.MergeAlterTables(sqls[adv]...) // 由于传入合并的SQL都是一张表的,所以一定只会输出一条ddl语句 diff --git a/common/tricks.go b/common/tricks.go index 9e89a5795127ed0709a510dafbb5e2271dd4a916..53360caa703cbf4ba104e3f80943ebf6d1bd0d5d 100644 --- a/common/tricks.go +++ b/common/tricks.go @@ -20,7 +20,6 @@ import ( "bufio" "bytes" "fmt" - "io" "io/ioutil" "os" "path/filepath" @@ -67,24 +66,22 @@ func captureOutput(f func()) string { r, w, _ := os.Pipe() os.Stdout = w - // execute function - f() - - outC := make(chan string) // copy the output in a separate goroutine so printing can't block indefinitely + outC := make(chan string) go func() { - var buf bytes.Buffer - _, err := io.Copy(&buf, r) + buf, err := ioutil.ReadAll(r) if err != nil { - Log.Warning(err.Error()) + panic(err) } - outC <- buf.String() + outC <- string(buf) }() + // execute function + f() + // back to normal state - err := w.Close() - if err != nil { - Log.Warning(err.Error()) + if err := w.Close(); err != nil { + panic(err) } os.Stdout = oldStdout // restoring the real stdout out := <-outC diff --git a/common/tricks_test.go b/common/tricks_test.go new file mode 100644 index 0000000000000000000000000000000000000000..063750ee2a183403251e72790762034862d6bbdf --- /dev/null +++ b/common/tricks_test.go @@ -0,0 +1,51 @@ +/* + * Copyright 2018 Xiaomi, 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 common + +import ( + "fmt" + "strings" + "testing" + "time" +) + +func TestCaptureOutput(t *testing.T) { + c1 := make(chan string, 1) + // test output buf large than 65535 + length := 1<<16 + 1 + go func() { + str := captureOutput( + func() { + var str []string + for i := 0; i < length; i++ { + str = append(str, "a") + } + fmt.Println(strings.Join(str, "")) + }, + ) + c1 <- str + }() + + select { + case res := <-c1: + if len(res) <= length { + t.Errorf("want %d, got %d", length, len(res)) + } + case <-time.After(1 * time.Second): + t.Error("capture timeout, pipe read hangup") + } +} diff --git a/database/explain.go b/database/explain.go index 75c3f728b968b7448f26718212b9f5373aef936a..df00651655fba57ea6f2b00ab585f1f51cd5a55e 100644 --- a/database/explain.go +++ b/database/explain.go @@ -22,6 +22,7 @@ import ( "fmt" "regexp" "runtime" + "sort" "strconv" "strings" @@ -652,6 +653,7 @@ func ExplainInfoTranslator(exp *ExplainInfo) string { } if len(selectTypeBuf) > 0 { buf = append(buf, fmt.Sprint("#### SelectType信息解读\n")) + sort.Strings(selectTypeBuf) buf = append(buf, strings.Join(selectTypeBuf, "\n")) } @@ -681,6 +683,7 @@ func ExplainInfoTranslator(exp *ExplainInfo) string { } if len(accessTypeBuf) > 0 { buf = append(buf, fmt.Sprint("#### Type信息解读\n")) + sort.Strings(accessTypeBuf) buf = append(buf, strings.Join(accessTypeBuf, "\n")) } @@ -693,10 +696,11 @@ func ExplainInfoTranslator(exp *ExplainInfo) string { for _, row := range rows { for k, c := range explainExtra { if strings.Contains(row.Extra, k) { - if k == "Impossible WHERE" { - if strings.Contains(row.Extra, "Impossible WHERE noticed after reading const tables") { - continue - } + if k == "Impossible WHERE" && strings.Contains(row.Extra, "Impossible WHERE noticed after reading const tables") { + continue + } + if k == "Using index" && strings.Contains(row.Extra, "Using index condition") { + continue } warn := false for _, w := range common.Config.ExplainWarnExtra { @@ -716,6 +720,7 @@ func ExplainInfoTranslator(exp *ExplainInfo) string { } if len(extraTypeBuf) > 0 { buf = append(buf, fmt.Sprint("#### Extra信息解读\n")) + sort.Strings(extraTypeBuf) buf = append(buf, strings.Join(extraTypeBuf, "\n")) } diff --git a/database/explain_test.go b/database/explain_test.go index 54320b74eec1e27c9276135c9146a0bdb6e9aab6..f5705b6cafe8e9860a35a91175ef7b244b8eed82 100644 --- a/database/explain_test.go +++ b/database/explain_test.go @@ -26,6 +26,7 @@ import ( ) var sqls = []string{ + `use sakila`, // not explain able sql, will convert to empty! `select * from city where country_id = 44;`, `select * from address where address2 is not null;`, `select * from address where address2 is null;`, diff --git a/deps.sh b/deps.sh index 68b5b4a8ba98a73cf59efbd68712baf59aa90c3f..21a3e948d8f2f3712dba3502e774ac97bd48a1b1 100755 --- a/deps.sh +++ b/deps.sh @@ -1,6 +1,6 @@ #!/bin/bash -NEEDED_COMMANDS="docker git go govendor retool" +NEEDED_COMMANDS="docker git go govendor retool bats" for cmd in ${NEEDED_COMMANDS} ; do if ! command -v "${cmd}" &> /dev/null ; then @@ -25,3 +25,7 @@ done # retool ## go get github.com/twitchtv/retool + +# bats https://github.com/sstephenson/bats +## Ubuntu: apt-get install bats +## Mac: brew install bats diff --git a/doc/example/main_test.md b/doc/example/main_test.md deleted file mode 100644 index 1ff04158d22a5d6454361b7ce782af0cdd9685a9..0000000000000000000000000000000000000000 --- a/doc/example/main_test.md +++ /dev/null @@ -1,2612 +0,0 @@ -# Query: C3FAEDA6AD6D762B - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH = 86 -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: E969B9297DA79BA6 - -★ ★ ★ ★ ☆ 90分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH IS NULL -``` - -## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断 - -* **Item:** ARG.006 - -* **Severity:** L1 - -* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0; - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 8A106444D14B9880 - -★ ★ ★ ☆ ☆ 60分 - -```sql - -SELECT - * -FROM - film -HAVING - title = 'abc' -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 不建议使用 HAVING 子句 - -* **Item:** CLA.013 - -* **Severity:** L3 - -* **Content:** 将查询的 HAVING 子句改写为 WHERE 中的查询条件,可以在查询处理期间使用索引。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: A0C5E62C724A121A - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - sakila. film -WHERE - LENGTH >= 60 -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 868317D1973FD1B0 - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH BETWEEN 60 - AND 84 -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 707FE669669FA075 - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - film -WHERE - title LIKE 'AIR%' -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: DF916439ABD07664 - -★ ★ ★ ★ ☆ 90分 - -```sql - -SELECT - * -FROM - film -WHERE - title IS NOT NULL -``` - -## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断 - -* **Item:** ARG.006 - -* **Severity:** L1 - -* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0; - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: B9336971FF3D3792 - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH = 114 - AND title = 'ALABAMA DEVIL' -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: 68E48001ECD53152 - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH > 100 - AND title = 'ALABAMA DEVIL' -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: 12FF1DAA3D425FA9 - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH > 100 - AND language_id < 10 - AND title = 'xyz' -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: E84CBAAC2E12BDEA - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH > 100 - AND language_id < 10 -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: 6A0F035BD4E01018 - -★ ★ ★ ☆ ☆ 75分 - -```sql - -SELECT - release_year, SUM( LENGTH) -FROM - film -WHERE - LENGTH = 123 - AND language_id = 1 -GROUP BY - release_year -``` - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -## 使用 SUM(COL) 时需注意 NPE 问题 - -* **Item:** FUN.006 - -* **Severity:** L1 - -* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: 23D176AEA2947002 - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - release_year, SUM( LENGTH) -FROM - film -WHERE - LENGTH >= 123 -GROUP BY - release_year -``` - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -## 使用 SUM(COL) 时需注意 NPE 问题 - -* **Item:** FUN.006 - -* **Severity:** L1 - -* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl - -# Query: 73DDF6E6D9E40384 - -★ ★ ★ ☆ ☆ 65分 - -```sql - -SELECT - release_year, language_id, SUM( LENGTH) -FROM - film -GROUP BY - release_year, language_id -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -## 使用 SUM(COL) 时需注意 NPE 问题 - -* **Item:** FUN.006 - -* **Severity:** L1 - -* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl - -# Query: B3C502B4AA344196 - -★ ★ ★ ☆ ☆ 75分 - -```sql - -SELECT - release_year, SUM( LENGTH) -FROM - film -WHERE - LENGTH = 123 -GROUP BY - release_year, (LENGTH+ language_id) -``` - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -## GROUP BY 的条件为表达式 - -* **Item:** CLA.010 - -* **Severity:** L2 - -* **Content:** 当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 - -## 使用 SUM(COL) 时需注意 NPE 问题 - -* **Item:** FUN.006 - -* **Severity:** L1 - -* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl - -# Query: 47044E1FE1A965A5 - -★ ★ ★ ☆ ☆ 70分 - -```sql - -SELECT - release_year, SUM( film_id) -FROM - film -GROUP BY - release_year -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -# Query: 2BA1217F6C8CF0AB - -★ ★ ☆ ☆ ☆ 45分 - -```sql - -SELECT - * -FROM - address -GROUP BY - address, district -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 非确定性的 GROUP BY - -* **Item:** RES.001 - -* **Severity:** L4 - -* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。 - -# Query: 863A85207E4F410D - -★ ★ ★ ★ ☆ 80分 - -```sql - -SELECT - title -FROM - film -WHERE - ABS( language_id) = 3 -GROUP BY - title -``` - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -## 避免在 WHERE 条件中使用函数或其他运算符 - -* **Item:** FUN.001 - -* **Severity:** L2 - -* **Content:** 虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰。 - -# Query: DF59FD602E4AA368 - -★ ★ ★ ★ ☆ 80分 - -```sql - -SELECT - language_id -FROM - film -WHERE - LENGTH = 123 -GROUP BY - release_year -ORDER BY - language_id -``` - -## 非确定性的 GROUP BY - -* **Item:** RES.001 - -* **Severity:** L4 - -* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。 - -# Query: F6DBEAA606D800FC - -★ ★ ★ ★ ★ 100分 - -```sql - -SELECT - release_year -FROM - film -WHERE - LENGTH = 123 -GROUP BY - release_year -ORDER BY - release_year -``` - -## OK - -# Query: 6E9B96CA3F0E6BDA - -★ ★ ★ ☆ ☆ 65分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH = 123 -ORDER BY - release_year ASC, language_id DESC -``` - -## ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引 - -* **Item:** CLA.007 - -* **Severity:** L2 - -* **Content:** ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## ORDER BY 多个列但排序方向不同时可能无法使用索引 - -* **Item:** KEY.008 - -* **Severity:** L4 - -* **Content:** 在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。 - -# Query: 2EAACFD7030EA528 - -★ ★ ★ ★ ★ 100分 - -```sql - -SELECT - release_year -FROM - film -WHERE - LENGTH = 123 -GROUP BY - release_year -ORDER BY - release_year -LIMIT - 10 -``` - -## OK - -# Query: 5CE2F187DBF2A710 - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH = 123 -ORDER BY - release_year -LIMIT - 10 -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: E75234155B5E2E14 - -★ ★ ★ ☆ ☆ 75分 - -```sql - -SELECT - * -FROM - film -ORDER BY - release_year -LIMIT - 10 -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: AFEEBF10A8D74E32 - -★ ★ ★ ★ ☆ 80分 - -```sql - -SELECT - film_id -FROM - film -ORDER BY - release_year -LIMIT - 10 -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -# Query: 965D5AC955824512 - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH > 100 -ORDER BY - LENGTH -LIMIT - 10 -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 1E2CF4145EE706A5 - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH < 100 -ORDER BY - LENGTH -LIMIT - 10 -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: A314542EEE8571EE - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - customer -WHERE - address_id in (224, 510) -ORDER BY - last_name -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 0BE2D79E2F1E7CB0 - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - film -WHERE - release_year = 2016 - AND LENGTH != 1 -ORDER BY - title -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -## '!=' 运算符是非标准的 - -* **Item:** STA.001 - -* **Severity:** L0 - -* **Content:** "<>"才是标准SQL中的不等于运算符。 - -# Query: 4E73AA068370E6A8 - -★ ★ ★ ★ ★ 100分 - -```sql - -SELECT - title -FROM - film -WHERE - release_year = 1995 -``` - -## OK - -# Query: BA7111449E4F1122 - -★ ★ ★ ★ ☆ 90分 - -```sql - -SELECT - title, replacement_cost -FROM - film -WHERE - language_id = 5 - AND LENGTH = 70 -``` - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: B13E0ACEAF8F3119 - -★ ★ ★ ★ ☆ 90分 - -```sql - -SELECT - title -FROM - film -WHERE - language_id > 5 - AND LENGTH > 70 -``` - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: A3FAB6027484B88B - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH = 100 - AND title = 'xyz' -ORDER BY - release_year -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: CB42080E9F35AB07 - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH > 100 - AND title = 'xyz' -ORDER BY - release_year -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: C4A212A42400411D - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - film -WHERE - LENGTH > 100 -ORDER BY - release_year -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 4ECCA9568BE69E68 - -★ ★ ★ ☆ ☆ 75分 - -```sql - -SELECT - * -FROM - city a - INNER JOIN country b ON a. country_id= b. country_id -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 485D56FC88BBBDB9 - -★ ★ ★ ☆ ☆ 75分 - -```sql - -SELECT - * -FROM - city a - LEFT JOIN country b ON a. country_id= b. country_id -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 0D0DABACEDFF5765 - -★ ★ ★ ☆ ☆ 75分 - -```sql - -SELECT - * -FROM - city a - RIGHT JOIN country b ON a. country_id= b. country_id -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 1E56C6CCEA2131CC - -★ ★ ★ ★ ☆ 90分 - -```sql - -SELECT - * -FROM - city a - LEFT JOIN country b ON a. country_id= b. country_id -WHERE - b. last_update IS NULL -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断 - -* **Item:** ARG.006 - -* **Severity:** L1 - -* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0; - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: F5D30BCAC1E206A1 - -★ ★ ★ ★ ☆ 90分 - -```sql - -SELECT - * -FROM - city a - RIGHT JOIN country b ON a. country_id= b. country_id -WHERE - a. last_update IS NULL -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断 - -* **Item:** ARG.006 - -* **Severity:** L1 - -* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0; - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 17D5BCF21DC2364C - -★ ★ ★ ☆ ☆ 65分 - -```sql - -SELECT - * -FROM - city a - LEFT JOIN country b ON a. country_id= b. country_id -UNION -SELECT - * -FROM - city a - RIGHT JOIN country b ON a. country_id= b. country_id -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION - -* **Item:** SUB.002 - -* **Severity:** L2 - -* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。 - -# Query: A4911095C201896F - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - city a - RIGHT JOIN country b ON a. country_id= b. country_id -WHERE - a. last_update IS NULL -UNION -SELECT - * -FROM - city a - LEFT JOIN country b ON a. country_id= b. country_id -WHERE - b. last_update IS NULL -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION - -* **Item:** SUB.002 - -* **Severity:** L2 - -* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。 - -# Query: 3FF20E28EC9CBEF9 - -★ ★ ★ ★ ☆ 80分 - -```sql - -SELECT - country_id, last_update -FROM - city NATURAL - JOIN country -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -# Query: 5C547F08EADBB131 - -★ ★ ★ ★ ☆ 80分 - -```sql - -SELECT - country_id, last_update -FROM - city NATURAL - LEFT JOIN country -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -# Query: AF0C1EB58B23D2FA - -★ ★ ★ ★ ☆ 80分 - -```sql - -SELECT - country_id, last_update -FROM - city NATURAL - RIGHT JOIN country -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -# Query: 626571EAE84E2C8A - -★ ★ ★ ★ ☆ 80分 - -```sql - -SELECT - a. country_id, a. last_update -FROM - city a STRAIGHT_JOIN country b ON a. country_id= b. country_id -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -# Query: F76BFFC87914E3D5 - -★ ★ ★ ☆ ☆ 60分 - -```sql - -SELECT - d. deptno, d. dname, d. loc -FROM - scott. dept d -WHERE - d. deptno IN ( -SELECT - e. deptno -FROM - scott. emp e) -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## MySQL 对子查询的优化效果不佳 - -* **Item:** SUB.001 - -* **Severity:** L4 - -* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 - -# Query: 7253A3D336F9F3FE - -★ ☆ ☆ ☆ ☆ 30分 - -```sql - -SELECT - visitor_id, url -FROM - ( -SELECT - id -FROM - LOG -WHERE - ip= "123.45.67.89" -ORDER BY - ts desc -LIMIT - 50, 10) I - JOIN LOG ON (I. id= LOG. id) - JOIN url ON (url. id= LOG. url_id) -ORDER BY - TS desc -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 同一张表被连接两次 - -* **Item:** JOI.002 - -* **Severity:** L4 - -* **Content:** 相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。 - -## 用字符类型存储IP地址 - -* **Item:** LIT.001 - -* **Severity:** L2 - -* **Content:** 字符串字面上看起来像IP地址,但不是 INET\_ATON() 的参数,表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。 - -## MySQL 对子查询的优化效果不佳 - -* **Item:** SUB.001 - -* **Severity:** L4 - -* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 - -# Query: 7F02E23D44A38A6D - -★ ★ ★ ★ ☆ 80分 - -```sql -DELETE city, country -FROM - city - INNER JOIN country using (country_id) -WHERE - city. city_id = 1 -``` - -## 不建议使用联表删除或更新 - -* **Item:** JOI.007 - -* **Severity:** L4 - -* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 - -## 使用DELETE/DROP/TRUNCATE等操作时注意备份 - -* **Item:** SEC.003 - -* **Severity:** L0 - -* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 - -# Query: F8314ABD1CBF2FF1 - -★ ★ ★ ★ ☆ 80分 - -```sql -DELETE city -FROM - city - LEFT JOIN country ON city. country_id = country. country_id -WHERE - country. country IS NULL -``` - -## 不建议使用联表删除或更新 - -* **Item:** JOI.007 - -* **Severity:** L4 - -* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 - -## 使用DELETE/DROP/TRUNCATE等操作时注意备份 - -* **Item:** SEC.003 - -* **Severity:** L0 - -* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 - -# Query: 1A53649C43122975 - -★ ★ ★ ★ ☆ 80分 - -```sql -DELETE a1, a2 -FROM - city AS a1 - INNER JOIN country AS a2 -WHERE - a1. country_id= a2. country_id -``` - -## 不建议使用联表删除或更新 - -* **Item:** JOI.007 - -* **Severity:** L4 - -* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 - -## 使用DELETE/DROP/TRUNCATE等操作时注意备份 - -* **Item:** SEC.003 - -* **Severity:** L0 - -* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 - -# Query: B862978586C6338B - -★ ★ ★ ★ ☆ 80分 - -```sql - -DELETE FROM - a1, a2 USING city AS a1 - INNER JOIN country AS a2 -WHERE - a1. country_id= a2. country_id -``` - -## 不建议使用联表删除或更新 - -* **Item:** JOI.007 - -* **Severity:** L4 - -* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 - -## 使用DELETE/DROP/TRUNCATE等操作时注意备份 - -* **Item:** SEC.003 - -* **Severity:** L0 - -* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 - -# Query: F16FD63381EF8299 - -★ ★ ★ ★ ★ 100分 - -```sql - -DELETE FROM - film -WHERE - LENGTH > 100 -``` - -## 使用DELETE/DROP/TRUNCATE等操作时注意备份 - -* **Item:** SEC.003 - -* **Severity:** L0 - -* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 - -# Query: 08CFE41C7D20AAC8 - -★ ★ ★ ★ ☆ 80分 - -```sql - -UPDATE - city - INNER JOIN country USING( country_id) -SET - city. city = 'Abha', - city. last_update = '2006-02-15 04:45:25', - country. country = 'Afghanistan' -WHERE - city. city_id= 10 -``` - -## 不建议使用联表删除或更新 - -* **Item:** JOI.007 - -* **Severity:** L4 - -* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 - -# Query: C15BDF2C73B5B7ED - -★ ★ ★ ★ ☆ 80分 - -```sql - -UPDATE - city - INNER JOIN country ON city. country_id = country. country_id - INNER JOIN address ON city. city_id = address. city_id -SET - city. city = 'Abha', - city. last_update = '2006-02-15 04:45:25', - country. country = 'Afghanistan' -WHERE - city. city_id= 10 -``` - -## 不建议使用联表删除或更新 - -* **Item:** JOI.007 - -* **Severity:** L4 - -* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 - -# Query: FCD1ABF36F8CDAD7 - -★ ★ ★ ★ ☆ 90分 - -```sql - -UPDATE - city, country -SET - city. city = 'Abha', - city. last_update = '2006-02-15 04:45:25', - country. country = 'Afghanistan' -WHERE - city. country_id = country. country_id - AND city. city_id= 10 -``` - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: FE409EB794EE91CF - -★ ★ ★ ★ ★ 100分 - -```sql - -UPDATE - film -SET - LENGTH = 10 -WHERE - language_id = 20 -``` - -## OK - -# Query: 3656B13CC4F888E2 - -★ ★ ★ ☆ ☆ 65分 - -```sql -INSERT INTO city (country_id) -SELECT - country_id -FROM - country -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## INSERT INTO xx SELECT 加锁粒度较大请谨慎 - -* **Item:** LCK.001 - -* **Severity:** L3 - -* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎 - -# Query: 2F7439623B712317 - -★ ★ ★ ★ ★ 100分 - -```sql -INSERT INTO city (country_id) -VALUES - (1), - (2), - (3) -``` - -## OK - -# Query: 11EC7AAACC97DC0F - -★ ★ ★ ★ ☆ 85分 - -```sql -INSERT INTO city (country_id) -SELECT - 10 -FROM - DUAL -``` - -## INSERT INTO xx SELECT 加锁粒度较大请谨慎 - -* **Item:** LCK.001 - -* **Severity:** L3 - -* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎 - -# Query: E3DDA1A929236E72 - -★ ★ ★ ☆ ☆ 65分 - -```sql -REPLACE INTO city (country_id) -SELECT - country_id -FROM - country -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## INSERT INTO xx SELECT 加锁粒度较大请谨慎 - -* **Item:** LCK.001 - -* **Severity:** L3 - -* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎 - -# Query: 466F1AC2F5851149 - -★ ★ ★ ★ ★ 100分 - -```sql -REPLACE INTO city (country_id) -VALUES - (1), - (2), - (3) -``` - -## OK - -# Query: A7973BDD268F926E - -★ ★ ★ ★ ☆ 85分 - -```sql -REPLACE INTO city (country_id) -SELECT - 10 -FROM - DUAL -``` - -## INSERT INTO xx SELECT 加锁粒度较大请谨慎 - -* **Item:** LCK.001 - -* **Severity:** L3 - -* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎 - -# Query: 105C870D5DFB6710 - -★ ★ ★ ☆ ☆ 65分 - -```sql - -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - ( -SELECT - film_id -FROM - film -) film -) film -) film -) film -) film -) film -) film -) film -) film -) film -) film -) film -) film -) film -) film -) film -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 执行计划中嵌套连接深度过深 - -* **Item:** SUB.004 - -* **Severity:** L3 - -* **Content:** MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。 - -# Query: 16C2B14E7DAA9906 - -★ ☆ ☆ ☆ ☆ 35分 - -```sql - -SELECT - * -FROM - film -WHERE - language_id = ( -SELECT - language_id -FROM - language -LIMIT - 1) -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 未使用 ORDER BY 的 LIMIT 查询 - -* **Item:** RES.002 - -* **Severity:** L4 - -* **Content:** 没有 ORDER BY 的 LIMIT 会导致非确定性的结果,这取决于查询执行计划。 - -## MySQL 对子查询的优化效果不佳 - -* **Item:** SUB.001 - -* **Severity:** L4 - -* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 - -# Query: 16CB4628D2597D40 - -★ ★ ★ ☆ ☆ 65分 - -```sql - -SELECT - * -FROM - city i - LEFT JOIN country o ON i. city_id= o. country_id -UNION -SELECT - * -FROM - city i - RIGHT JOIN country o ON i. city_id= o. country_id -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION - -* **Item:** SUB.002 - -* **Severity:** L2 - -* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。 - -# Query: EA50643B01E139A8 - -★ ☆ ☆ ☆ ☆ 35分 - -```sql - -SELECT - * -FROM - ( -SELECT - * -FROM - actor -WHERE - last_update= '2006-02-15 04:34:33' - AND last_name= 'CHASE' -) t -WHERE - last_update= '2006-02-15 04:34:33' - AND last_name= 'CHASE' -GROUP BY - first_name -``` - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 非确定性的 GROUP BY - -* **Item:** RES.001 - -* **Severity:** L4 - -* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。 - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -## MySQL 对子查询的优化效果不佳 - -* **Item:** SUB.001 - -* **Severity:** L4 - -* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 - -# Query: 7598A4EDE6CFA6BE - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - city i - LEFT JOIN country o ON i. city_id= o. country_id -WHERE - o. country_id is null -UNION -SELECT - * -FROM - city i - RIGHT JOIN country o ON i. city_id= o. country_id -WHERE - i. city_id is null -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION - -* **Item:** SUB.002 - -* **Severity:** L2 - -* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。 - -# Query: 1E8B70E30062FD13 - -★ ★ ★ ★ ☆ 80分 - -```sql - -SELECT - first_name, last_name, email -FROM - customer STRAIGHT_JOIN address ON customer. address_id= address. address_id -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -# Query: E48A20D0413512DA - -★ ☆ ☆ ☆ ☆ 30分 - -```sql - -SELECT - ID, name -FROM - ( -SELECT - address -FROM - customer_list -WHERE - SID= 1 -ORDER BY - phone -LIMIT - 50, 10) a - JOIN customer_list l ON (a. address= l. address) - JOIN city c ON (c. city= l. city) -ORDER BY - phone desc -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引 - -* **Item:** CLA.007 - -* **Severity:** L2 - -* **Content:** ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。 - -## 同一张表被连接两次 - -* **Item:** JOI.002 - -* **Severity:** L4 - -* **Content:** 相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。 - -## MySQL 对子查询的优化效果不佳 - -* **Item:** SUB.001 - -* **Severity:** L4 - -* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 - -# Query: B0BA5A7079EA16B3 - -★ ★ ★ ★ ☆ 85分 - -```sql - -SELECT - * -FROM - film -WHERE - DATE( last_update) = '2006-02-15' -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -## 避免在 WHERE 条件中使用函数或其他运算符 - -* **Item:** FUN.001 - -* **Severity:** L2 - -* **Content:** 虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰。 - -# Query: 18A2AD1395A58EAE - -★ ★ ★ ☆ ☆ 60分 - -```sql - -SELECT - last_update -FROM - film -GROUP BY - DATE( last_update) -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -## GROUP BY 的条件为表达式 - -* **Item:** CLA.010 - -* **Severity:** L2 - -* **Content:** 当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 - -# Query: 60F234BA33AAC132 - -★ ★ ★ ☆ ☆ 70分 - -```sql - -SELECT - last_update -FROM - film -ORDER BY - DATE( last_update) -``` - -## 最外层 SELECT 未指定 WHERE 条件 - -* **Item:** CLA.001 - -* **Severity:** L4 - -* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 - -## ORDER BY 的条件为表达式 - -* **Item:** CLA.009 - -* **Severity:** L2 - -* **Content:** 当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 - -# Query: 1ED2B7ECBA4215E1 - -★ ★ ★ ★ ☆ 90分 - -```sql - -SELECT - description -FROM - film -WHERE - description IN( 'NEWS', - 'asd' -) -GROUP BY - description -``` - -## 请为 GROUP BY 显示添加 ORDER BY 条件 - -* **Item:** CLA.008 - -* **Severity:** L2 - -* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 - -# Query: 255BAC03F56CDBC7 - -★ ★ ★ ★ ★ 100分 - -```sql - -ALTER TABLE - address -ADD - index idx_city_id( city_id) -``` - -## OK - -# Query: C315BC4EE0F4E523 - -★ ★ ★ ★ ★ 100分 - -```sql - -ALTER TABLE - inventory -ADD - index `idx_store_film` ( - `store_id`, `film_id`) -``` - -## 提醒:请将索引属性顺序与查询对齐 - -* **Item:** KEY.004 - -* **Severity:** L0 - -* **Content:** 如果为列创建复合索引,请确保查询属性与索引属性的顺序相同,以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐,那么DBMS可能无法在查询处理期间使用索引。 - -# Query: 9BB74D074BA0727C - -★ ★ ★ ★ ★ 100分 - -```sql - -ALTER TABLE - inventory -ADD - index `idx_store_film` ( - `store_id`, `film_id`), - ADD - index `idx_store_film` ( - `store_id`, `film_id`), - ADD - index `idx_store_film` ( - `store_id`, `film_id`) -``` - -## 提醒:请将索引属性顺序与查询对齐 - -* **Item:** KEY.004 - -* **Severity:** L0 - -* **Content:** 如果为列创建复合索引,请确保查询属性与索引属性的顺序相同,以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐,那么DBMS可能无法在查询处理期间使用索引。 - -# Query: CE8A69541550D286 - -★ ☆ ☆ ☆ ☆ 30分 - -```sql - -SELECT - DATE_FORMAT( t. atm, '%Y-%m-%d' -), -COUNT( DISTINCT ( - t. usr)) - FROM - usr_terminal t - WHERE - t. atm > '2018-10-22 00:00:00' - AND t. agent LIKE '%Chrome%' - AND t. system = 'eip' - GROUP BY - DATE_FORMAT( t. atm, '%Y-%m-%d' -) -ORDER BY - DATE_FORMAT( t. atm, '%Y-%m-%d' -) -``` - -## 建议使用 AS 关键字显示声明一个别名 - -* **Item:** ALI.001 - -* **Severity:** L0 - -* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 - -## 不建议使用前项通配符查找 - -* **Item:** ARG.001 - -* **Severity:** L4 - -* **Content:** 例如 "%foo",查询参数有一个前项通配符的情况无法使用已有索引。 - -## ORDER BY 的条件为表达式 - -* **Item:** CLA.009 - -* **Severity:** L2 - -* **Content:** 当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 - -## GROUP BY 的条件为表达式 - -* **Item:** CLA.010 - -* **Severity:** L2 - -* **Content:** 当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 - -## ORDER BY 多个列但排序方向不同时可能无法使用索引 - -* **Item:** KEY.008 - -* **Severity:** L4 - -* **Content:** 在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。 - -## 不建议使用连续判断 - -* **Item:** RES.009 - -* **Severity:** L2 - -* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 - -# Query: C11ECE7AE5F80CE5 - -★ ★ ☆ ☆ ☆ 45分 - -```sql -create table hello. t (id int unsigned) -``` - -## 建议为表添加注释 - -* **Item:** CLA.011 - -* **Severity:** L1 - -* **Content:** 为表添加注释能够使得表的意义更明确,从而为日后的维护带来极大的便利。 - -## 请为列添加默认值 - -* **Item:** COL.004 - -* **Severity:** L1 - -* **Content:** 请为列添加默认值,如果是 ALTER 操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。 - -## 列未添加注释 - -* **Item:** COL.005 - -* **Severity:** L1 - -* **Content:** 建议对表中每个列添加注释,来明确每个列在表中的含义及作用。 - -## 未指定主键或主键非 int 或 bigint - -* **Item:** KEY.007 - -* **Severity:** L4 - -* **Content:** 未指定主键或主键非 int 或 bigint,建议将主键设置为 int unsigned 或 bigint unsigned。 - -## 请为表选择合适的存储引擎 - -* **Item:** TBL.002 - -* **Severity:** L4 - -* **Content:** 建表或修改表的存储引擎时建议使用推荐的存储引擎,如:innodb - -# Query: 291F95B7DCB74C21 - -★ ★ ★ ★ ☆ 95分 - -```sql - -SELECT - * -FROM - tb -WHERE - data >= '' -``` - -## 不建议使用 SELECT * 类型查询 - -* **Item:** COL.001 - -* **Severity:** L1 - -* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 - -# Query: 084DA3E3EE38DD85 - -★ ★ ★ ★ ★ 100分 - -```sql - -ALTER TABLE - tb alter column id -DROP - DEFAULT -``` - -## OK - diff --git a/doc/example/main_test.sh b/doc/example/main_test.sh deleted file mode 100755 index 8eb2d7c2bb449a88509884d4d951fd13db74dda0..0000000000000000000000000000000000000000 --- a/doc/example/main_test.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -GOPATH=$(go env GOPATH) -PROJECT_PATH=${GOPATH}/src/github.com/XiaoMi/soar/ - -if [ "$1x" == "-updatex" ]; then - cd "${PROJECT_PATH}" && ./bin/soar -list-test-sqls | ./bin/soar -config=../etc/soar.yaml > ./doc/example/main_test.md - if [ ! $? -eq 0 ]; then - exit 1 - fi -else - cd "${PROJECT_PATH}" && ./bin/soar -list-test-sqls | ./bin/soar -config=../etc/soar.yaml > ./doc/example/main_test.log - if [ ! $? -eq 0 ]; then - exit 1 - fi - # optimizer_XXX 库名,散粒度,以及索引先后顺序每次可能会不一致 - DIFF_LINES=$(cat ./doc/example/main_test.log ./doc/example/main_test.md | grep -v "optimizer\|散粒度" | sort | uniq -u | wc -l) - if [ "${DIFF_LINES}" -gt 0 ]; then - git diff ./doc/example/main_test.log ./doc/example/main_test.md - fi -fi diff --git a/doc/example/sakila.sql.gz b/doc/example/sakila.sql.gz deleted file mode 100644 index 5cdf8c7798a5220a6388cc2e4a50620fef4ad311..0000000000000000000000000000000000000000 Binary files a/doc/example/sakila.sql.gz and /dev/null differ diff --git a/test/fixture/test_Run_all_test_cases.golden b/test/fixture/test_Run_all_test_cases.golden new file mode 100644 index 0000000000000000000000000000000000000000..27a102f90cc27088bab8adb78e9729dae49e94c1 --- /dev/null +++ b/test/fixture/test_Run_all_test_cases.golden @@ -0,0 +1,4643 @@ +# Query: C3FAEDA6AD6D762B + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH = 86 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: E969B9297DA79BA6 + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH IS NULL +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断 + +* **Item:** ARG.006 + +* **Severity:** L1 + +* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0; + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 8A106444D14B9880 + +★ ★ ★ ☆ ☆ 60分 + +```sql + +SELECT + * +FROM + film +HAVING + title = 'abc' +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 不建议使用 HAVING 子句 + +* **Item:** CLA.013 + +* **Severity:** L3 + +* **Content:** 将查询的 HAVING 子句改写为 WHERE 中的查询条件,可以在查询处理期间使用索引。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: A0C5E62C724A121A + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + sakila. film +WHERE + LENGTH >= 60 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 33.33% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 868317D1973FD1B0 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH BETWEEN 60 + AND 84 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 11.11% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 707FE669669FA075 + +★ ★ ★ ★ ☆ 95分 + +```sql + +SELECT + * +FROM + film +WHERE + title LIKE 'AIR%' +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | range | idx\_title | idx\_title | 767 | NULL | 2 | ☠️ **100.00%** | ☠️ **O(n)** | Using index condition | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素. + +#### Extra信息解读 + +* **Using index condition**: 在5.6版本后加入的新特性(Index Condition Pushdown)。Using index condition 会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行。 + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: DF916439ABD07664 + +★ ★ ★ ★ ☆ 90分 + +```sql + +SELECT + * +FROM + film +WHERE + title IS NOT NULL +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | idx\_title | NULL | NULL | NULL | 1000 | 90.00% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断 + +* **Item:** ARG.006 + +* **Severity:** L1 + +* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0; + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: B9336971FF3D3792 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH = 114 + AND title = 'ALABAMA DEVIL' +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ref | idx\_title | idx\_title | 767 | const | 1 | 10.00% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\` (\`title\`,\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 68E48001ECD53152 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH > 100 + AND title = 'ALABAMA DEVIL' +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ref | idx\_title | idx\_title | 767 | const | 1 | 33.33% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\` (\`title\`,\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 12FF1DAA3D425FA9 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH > 100 + AND language_id < 10 + AND title = 'xyz' +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ref | idx\_title,
idx\_fk\_language\_id | idx\_title | 767 | const | 1 | 33.33% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\` (\`title\`,\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: E84CBAAC2E12BDEA + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH > 100 + AND language_id < 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | idx\_fk\_language\_id | NULL | NULL | NULL | 1000 | 33.33% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 6A0F035BD4E01018 + +★ ★ ★ ☆ ☆ 75分 + +```sql + +SELECT + release_year, SUM( LENGTH) +FROM + film +WHERE + LENGTH = 123 + AND language_id = 1 +GROUP BY + release_year +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | idx\_fk\_language\_id | NULL | NULL | NULL | 1000 | 10.00% | ☠️ **O(n)** | Using where; Using temporary | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_language\_id\_release\_year\` (\`length\`,\`language\_id\`,\`release\_year\`) ; + + + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +## 使用 SUM(COL) 时需注意 NPE 问题 + +* **Item:** FUN.006 + +* **Severity:** L1 + +* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl + +# Query: 23D176AEA2947002 + +★ ★ ★ ☆ ☆ 75分 + +```sql + +SELECT + release_year, SUM( LENGTH) +FROM + film +WHERE + LENGTH >= 123 +GROUP BY + release_year +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 33.33% | ☠️ **O(n)** | Using where; Using temporary | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ; + + + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +## 使用 SUM(COL) 时需注意 NPE 问题 + +* **Item:** FUN.006 + +* **Severity:** L1 + +* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl + +# Query: 73DDF6E6D9E40384 + +★ ★ ☆ ☆ ☆ 55分 + +```sql + +SELECT + release_year, language_id, SUM( LENGTH) +FROM + film +GROUP BY + release_year, language_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_release\_year\_language\_id\` (\`release\_year\`,\`language\_id\`) ; + + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +## 使用 SUM(COL) 时需注意 NPE 问题 + +* **Item:** FUN.006 + +* **Severity:** L1 + +* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl + +# Query: B3C502B4AA344196 + +★ ★ ★ ☆ ☆ 65分 + +```sql + +SELECT + release_year, SUM( LENGTH) +FROM + film +WHERE + LENGTH = 123 +GROUP BY + release_year, (LENGTH+ language_id) +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00% | ☠️ **O(n)** | Using where; Using temporary | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +## GROUP BY 的条件为表达式 + +* **Item:** CLA.010 + +* **Severity:** L2 + +* **Content:** 当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 + +## 使用 SUM(COL) 时需注意 NPE 问题 + +* **Item:** FUN.006 + +* **Severity:** L1 + +* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl + +# Query: 47044E1FE1A965A5 + +★ ★ ★ ☆ ☆ 60分 + +```sql + +SELECT + release_year, SUM( film_id) +FROM + film +GROUP BY + release_year +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_release\_year\` (\`release\_year\`) ; + + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +# Query: 2BA1217F6C8CF0AB + +★ ☆ ☆ ☆ ☆ 35分 + +```sql + +SELECT + * +FROM + address +GROUP BY + address, district +``` + +## 为sakila库的address表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`address\` add index \`idx\_address\_district\` (\`address\`,\`district\`) ; + + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## 非确定性的 GROUP BY + +* **Item:** RES.001 + +* **Severity:** L4 + +* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。 + +# Query: 863A85207E4F410D + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + title +FROM + film +WHERE + ABS( language_id) = 3 +GROUP BY + title +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | index | idx\_title | idx\_title | 767 | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +## 避免在 WHERE 条件中使用函数或其他运算符 + +* **Item:** FUN.001 + +* **Severity:** L2 + +* **Content:** 虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰。 + +# Query: DF59FD602E4AA368 + +★ ★ ★ ☆ ☆ 70分 + +```sql + +SELECT + language_id +FROM + film +WHERE + LENGTH = 123 +GROUP BY + release_year +ORDER BY + language_id +``` + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ; + + + +## 非确定性的 GROUP BY + +* **Item:** RES.001 + +* **Severity:** L4 + +* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。 + +# Query: F6DBEAA606D800FC + +★ ★ ★ ★ ☆ 90分 + +```sql + +SELECT + release_year +FROM + film +WHERE + LENGTH = 123 +GROUP BY + release_year +ORDER BY + release_year +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00% | ☠️ **O(n)** | Using where; Using temporary; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ; + + + +# Query: 6E9B96CA3F0E6BDA + +★ ★ ☆ ☆ ☆ 55分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH = 123 +ORDER BY + release_year ASC, language_id DESC +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00% | ☠️ **O(n)** | Using where; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引 + +* **Item:** CLA.007 + +* **Severity:** L2 + +* **Content:** ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## ORDER BY 多个列但排序方向不同时可能无法使用索引 + +* **Item:** KEY.008 + +* **Severity:** L4 + +* **Content:** 在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。 + +# Query: 2EAACFD7030EA528 + +★ ★ ★ ★ ☆ 90分 + +```sql + +SELECT + release_year +FROM + film +WHERE + LENGTH = 123 +GROUP BY + release_year +ORDER BY + release_year +LIMIT + 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00% | ☠️ **O(n)** | Using where; Using temporary; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ; + + + +# Query: 5CE2F187DBF2A710 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH = 123 +ORDER BY + release_year +LIMIT + 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00% | ☠️ **O(n)** | Using where; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: E75234155B5E2E14 + +★ ★ ★ ☆ ☆ 75分 + +```sql + +SELECT + * +FROM + film +ORDER BY + release_year +LIMIT + 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: AFEEBF10A8D74E32 + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + film_id +FROM + film +ORDER BY + release_year +LIMIT + 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +# Query: 965D5AC955824512 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH > 100 +ORDER BY + LENGTH +LIMIT + 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 33.33% | ☠️ **O(n)** | Using where; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 1E2CF4145EE706A5 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH < 100 +ORDER BY + LENGTH +LIMIT + 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 33.33% | ☠️ **O(n)** | Using where; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: A314542EEE8571EE + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + customer +WHERE + address_id in (224, 510) +ORDER BY + last_name +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *customer* | NULL | range | idx\_fk\_address\_id | idx\_fk\_address\_id | 2 | NULL | 2 | ☠️ **100.00%** | ☠️ **O(n)** | Using index condition; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素. + +#### Extra信息解读 + +* **Using index condition**: 在5.6版本后加入的新特性(Index Condition Pushdown)。Using index condition 会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行。 + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 为sakila库的customer表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`customer\` add index \`idx\_address\_id\_last\_name\` (\`address\_id\`,\`last\_name\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 0BE2D79E2F1E7CB0 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + release_year = 2016 + AND LENGTH != 1 +ORDER BY + title +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 9.00% | ☠️ **O(n)** | Using where; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_release\_year\_length\_title\` (\`release\_year\`,\`length\`,\`title\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## '!=' 运算符是非标准的 + +* **Item:** STA.001 + +* **Severity:** L0 + +* **Content:** "<>"才是标准SQL中的不等于运算符。 + +# Query: 4E73AA068370E6A8 + +★ ★ ★ ★ ☆ 90分 + +```sql + +SELECT + title +FROM + film +WHERE + release_year = 1995 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_release\_year\` (\`release\_year\`) ; + + + +# Query: BA7111449E4F1122 + +★ ★ ★ ★ ☆ 90分 + +```sql + +SELECT + title, replacement_cost +FROM + film +WHERE + language_id = 5 + AND LENGTH = 70 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ref | idx\_fk\_language\_id | idx\_fk\_language\_id | 1 | const | 1 | 10.00% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_language\_id\` (\`length\`,\`language\_id\`) ; + + + +# Query: B13E0ACEAF8F3119 + +★ ★ ★ ★ ☆ 90分 + +```sql + +SELECT + title +FROM + film +WHERE + language_id > 5 + AND LENGTH > 70 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | range | idx\_fk\_language\_id | idx\_fk\_language\_id | 1 | NULL | 1 | 33.33% | ☠️ **O(n)** | Using index condition; Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素. + +#### Extra信息解读 + +* **Using index condition**: 在5.6版本后加入的新特性(Index Condition Pushdown)。Using index condition 会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行。 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +# Query: A3FAB6027484B88B + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH = 100 + AND title = 'xyz' +ORDER BY + release_year +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ref | idx\_title | idx\_title | 767 | const | 1 | 10.00% | ☠️ **O(n)** | Using index condition; Using where; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +#### Extra信息解读 + +* **Using index condition**: 在5.6版本后加入的新特性(Index Condition Pushdown)。Using index condition 会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行。 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\_release\_year\` (\`title\`,\`length\`,\`release\_year\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: CB42080E9F35AB07 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH > 100 + AND title = 'xyz' +ORDER BY + release_year +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ref | idx\_title | idx\_title | 767 | const | 1 | 33.33% | ☠️ **O(n)** | Using index condition; Using where; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +#### Extra信息解读 + +* **Using index condition**: 在5.6版本后加入的新特性(Index Condition Pushdown)。Using index condition 会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行。 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\_release\_year\` (\`title\`,\`length\`,\`release\_year\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: C4A212A42400411D + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH > 100 +ORDER BY + release_year +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 33.33% | ☠️ **O(n)** | Using where; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 4ECCA9568BE69E68 + +★ ★ ★ ☆ ☆ 75分 + +```sql + +SELECT + * +FROM + city a + INNER JOIN country b ON a. country_id= b. country_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *b* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 485D56FC88BBBDB9 + +★ ★ ★ ☆ ☆ 75分 + +```sql + +SELECT + * +FROM + city a + LEFT JOIN country b ON a. country_id= b. country_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 0D0DABACEDFF5765 + +★ ★ ★ ☆ ☆ 75分 + +```sql + +SELECT + * +FROM + city a + RIGHT JOIN country b ON a. country_id= b. country_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 1E56C6CCEA2131CC + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + * +FROM + city a + LEFT JOIN country b ON a. country_id= b. country_id +WHERE + b. last_update IS NULL +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | 10.00% | ☠️ **O(n)** | Using where; Not exists | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行. + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的country表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`country\` add index \`idx\_last\_update\` (\`last\_update\`) ; + + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断 + +* **Item:** ARG.006 + +* **Severity:** L1 + +* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0; + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: F5D30BCAC1E206A1 + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + * +FROM + city a + RIGHT JOIN country b ON a. country_id= b. country_id +WHERE + a. last_update IS NULL +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | 10.00% | ☠️ **O(n)** | Using where; Not exists | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行. + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的city表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_last\_update\` (\`last\_update\`) ; + + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断 + +* **Item:** ARG.006 + +* **Severity:** L1 + +* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0; + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 17D5BCF21DC2364C + +★ ★ ★ ☆ ☆ 65分 + +```sql + +SELECT + * +FROM + city a + LEFT JOIN country b ON a. country_id= b. country_id +UNION +SELECT + * +FROM + city a + RIGHT JOIN country b ON a. country_id= b. country_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | PRIMARY | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | PRIMARY | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 2 | UNION | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 2 | UNION | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 2 | UNION | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **PRIMARY**: 最外层的select. + +* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集. + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION + +* **Item:** SUB.002 + +* **Severity:** L2 + +* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。 + +# Query: A4911095C201896F + +★ ★ ★ ☆ ☆ 65分 + +```sql + +SELECT + * +FROM + city a + RIGHT JOIN country b ON a. country_id= b. country_id +WHERE + a. last_update IS NULL +UNION +SELECT + * +FROM + city a + LEFT JOIN country b ON a. country_id= b. country_id +WHERE + b. last_update IS NULL +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | PRIMARY | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | PRIMARY | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | 10.00% | ☠️ **O(n)** | Using where; Not exists | +| 2 | UNION | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 2 | UNION | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | 10.00% | ☠️ **O(n)** | Using where; Not exists | +| 2 | UNION | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | 10.00% | ☠️ **O(n)** | Using where; Not exists | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **PRIMARY**: 最外层的select. + +* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集. + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行. + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的city表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_last\_update\` (\`last\_update\`) ; + + + +## 为sakila库的country表添加索引 + +* **Item:** IDX.002 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`country\` add index \`idx\_last\_update\` (\`last\_update\`) ; + + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION + +* **Item:** SUB.002 + +* **Severity:** L2 + +* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。 + +# Query: 3FF20E28EC9CBEF9 + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + country_id, last_update +FROM + city NATURAL + JOIN country +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *country* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *city* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.country.country\_id | 5 | 10.00% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +# Query: 5C547F08EADBB131 + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + country_id, last_update +FROM + city NATURAL + LEFT JOIN country +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *country* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +# Query: AF0C1EB58B23D2FA + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + country_id, last_update +FROM + city NATURAL + RIGHT JOIN country +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *country* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *city* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.country.country\_id | 5 | ☠️ **100.00%** | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +# Query: 626571EAE84E2C8A + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + a. country_id, a. last_update +FROM + city a STRAIGHT_JOIN country b ON a. country_id= b. country_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *a* | NULL | ALL | idx\_fk\_country\_id | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +# Query: 50F2AB4243CE2071 + +★ ★ ★ ☆ ☆ 60分 + +```sql + +SELECT + a. address, a. postal_code +FROM + sakila. address a +WHERE + a. city_id IN ( +SELECT + c. city_id +FROM + sakila. city c) +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *c* | NULL | index | PRIMARY | idx\_fk\_country\_id | 2 | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | +| 1 | SIMPLE | *a* | NULL | ref | idx\_fk\_city\_id | idx\_fk\_city\_id | 2 | sakila.c.city\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大. + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## MySQL 对子查询的优化效果不佳 + +* **Item:** SUB.001 + +* **Severity:** L4 + +* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 + +# Query: 584CCEC8069B6947 + +★ ☆ ☆ ☆ ☆ 30分 + +```sql + +SELECT + city +FROM( +SELECT + city_id +FROM + city +WHERE + city = "A Corua (La Corua)" +ORDER BY + last_update DESC +LIMIT + 50, 10) I + JOIN city ON (I. city_id = city. city_id) + JOIN country ON (country. country_id = city. country_id) +ORDER BY + city DESC +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | PRIMARY | ** | NULL | ALL | NULL | NULL | NULL | NULL | 60 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary; Using filesort | +| 1 | PRIMARY | *city* | NULL | eq\_ref | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | I.city\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | PRIMARY | *country* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | +| 2 | DERIVED | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | 10.00% | ☠️ **O(n)** | Using where; Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **DERIVED**: 用于from子句里有子查询的情况. MySQL会递归执行这些子查询, 把结果放在临时表里. + +* **PRIMARY**: 最外层的select. + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的city表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_city\_last\_update\` (\`city\`,\`last\_update\`) ; + + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 同一张表被连接两次 + +* **Item:** JOI.002 + +* **Severity:** L4 + +* **Content:** 相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。 + +## MySQL 对子查询的优化效果不佳 + +* **Item:** SUB.001 + +* **Severity:** L4 + +* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 + +# Query: 7F02E23D44A38A6D + +★ ★ ★ ★ ☆ 80分 + +```sql +DELETE city, country +FROM + city + INNER JOIN country using (country_id) +WHERE + city. city_id = 1 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | DELETE | *city* | NULL | const | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | DELETE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### Type信息解读 + +* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1. + + +## 不建议使用联表删除或更新 + +* **Item:** JOI.007 + +* **Severity:** L4 + +* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 + +## 使用DELETE/DROP/TRUNCATE等操作时注意备份 + +* **Item:** SEC.003 + +* **Severity:** L0 + +* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 + +# Query: F8314ABD1CBF2FF1 + +★ ★ ★ ☆ ☆ 70分 + +```sql +DELETE city +FROM + city + LEFT JOIN country ON city. country_id = country. country_id +WHERE + country. country IS NULL +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | DELETE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *country* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\_id | 1 | 10.00% | ☠️ **O(n)** | Using where; Not exists | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行. + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的country表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`country\` add index \`idx\_country\` (\`country\`) ; + + + +## 不建议使用联表删除或更新 + +* **Item:** JOI.007 + +* **Severity:** L4 + +* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 + +## 使用DELETE/DROP/TRUNCATE等操作时注意备份 + +* **Item:** SEC.003 + +* **Severity:** L0 + +* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 + +# Query: 1A53649C43122975 + +★ ★ ★ ★ ☆ 80分 + +```sql +DELETE a1, a2 +FROM + city AS a1 + INNER JOIN country AS a2 +WHERE + a1. country_id= a2. country_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | DELETE | *a2* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | DELETE | *a1* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.a2.country\_id | 5 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## 不建议使用联表删除或更新 + +* **Item:** JOI.007 + +* **Severity:** L4 + +* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 + +## 使用DELETE/DROP/TRUNCATE等操作时注意备份 + +* **Item:** SEC.003 + +* **Severity:** L0 + +* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 + +# Query: B862978586C6338B + +★ ★ ★ ★ ☆ 80分 + +```sql + +DELETE FROM + a1, a2 USING city AS a1 + INNER JOIN country AS a2 +WHERE + a1. country_id= a2. country_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | DELETE | *a2* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | DELETE | *a1* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.a2.country\_id | 5 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### Type信息解读 + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## 不建议使用联表删除或更新 + +* **Item:** JOI.007 + +* **Severity:** L4 + +* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 + +## 使用DELETE/DROP/TRUNCATE等操作时注意备份 + +* **Item:** SEC.003 + +* **Severity:** L0 + +* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 + +# Query: F16FD63381EF8299 + +★ ★ ★ ★ ☆ 90分 + +```sql + +DELETE FROM + film +WHERE + LENGTH > 100 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | DELETE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 使用DELETE/DROP/TRUNCATE等操作时注意备份 + +* **Item:** SEC.003 + +* **Severity:** L0 + +* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。 + +# Query: 08CFE41C7D20AAC8 + +★ ★ ★ ★ ☆ 80分 + +```sql + +UPDATE + city + INNER JOIN country USING( country_id) +SET + city. city = 'Abha', + city. last_update = '2006-02-15 04:45:25', + country. country = 'Afghanistan' +WHERE + city. city_id= 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | UPDATE | *city* | NULL | const | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | UPDATE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### Type信息解读 + +* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1. + + +## 不建议使用联表删除或更新 + +* **Item:** JOI.007 + +* **Severity:** L4 + +* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 + +# Query: C15BDF2C73B5B7ED + +★ ★ ★ ★ ☆ 80分 + +```sql + +UPDATE + city + INNER JOIN country ON city. country_id = country. country_id + INNER JOIN address ON city. city_id = address. city_id +SET + city. city = 'Abha', + city. last_update = '2006-02-15 04:45:25', + country. country = 'Afghanistan' +WHERE + city. city_id= 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | UPDATE | *city* | NULL | const | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | UPDATE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *address* | NULL | ref | idx\_fk\_city\_id | idx\_fk\_city\_id | 2 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1. + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + + +## 不建议使用联表删除或更新 + +* **Item:** JOI.007 + +* **Severity:** L4 + +* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。 + +# Query: FCD1ABF36F8CDAD7 + +★ ★ ★ ★ ★ 100分 + +```sql + +UPDATE + city, country +SET + city. city = 'Abha', + city. last_update = '2006-02-15 04:45:25', + country. country = 'Afghanistan' +WHERE + city. country_id = country. country_id + AND city. city_id= 10 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | UPDATE | *city* | NULL | const | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | UPDATE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### Type信息解读 + +* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1. + + +# Query: FE409EB794EE91CF + +★ ★ ★ ★ ★ 100分 + +```sql + +UPDATE + film +SET + LENGTH = 10 +WHERE + language_id = 20 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | UPDATE | *film* | NULL | range | idx\_fk\_language\_id | idx\_fk\_language\_id | 1 | const | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### Type信息解读 + +* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +# Query: 3656B13CC4F888E2 + +★ ★ ★ ☆ ☆ 65分 + +```sql +INSERT INTO city (country_id) +SELECT + country_id +FROM + country +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | INSERT | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | 0.00% | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *country* | NULL | index | NULL | PRIMARY | 2 | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## INSERT INTO xx SELECT 加锁粒度较大请谨慎 + +* **Item:** LCK.001 + +* **Severity:** L3 + +* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎 + +# Query: 2F7439623B712317 + +★ ★ ★ ★ ★ 100分 + +```sql +INSERT INTO city (country_id) +VALUES + (1), + (2), + (3) +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | INSERT | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | 0.00% | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +# Query: 11EC7AAACC97DC0F + +★ ★ ★ ★ ☆ 85分 + +```sql +INSERT INTO city (country_id) +SELECT + 10 +FROM + DUAL +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | INSERT | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | 0.00% | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## INSERT INTO xx SELECT 加锁粒度较大请谨慎 + +* **Item:** LCK.001 + +* **Severity:** L3 + +* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎 + +# Query: E3DDA1A929236E72 + +★ ★ ★ ☆ ☆ 65分 + +```sql +REPLACE INTO city (country_id) +SELECT + country_id +FROM + country +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | REPLACE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | 0.00% | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *country* | NULL | index | NULL | PRIMARY | 2 | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## INSERT INTO xx SELECT 加锁粒度较大请谨慎 + +* **Item:** LCK.001 + +* **Severity:** L3 + +* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎 + +# Query: 466F1AC2F5851149 + +★ ★ ★ ★ ★ 100分 + +```sql +REPLACE INTO city (country_id) +VALUES + (1), + (2), + (3) +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | REPLACE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | 0.00% | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +# Query: A7973BDD268F926E + +★ ★ ★ ★ ☆ 85分 + +```sql +REPLACE INTO city (country_id) +SELECT + 10 +FROM + DUAL +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | REPLACE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | 0.00% | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## INSERT INTO xx SELECT 加锁粒度较大请谨慎 + +* **Item:** LCK.001 + +* **Severity:** L3 + +* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎 + +# Query: 105C870D5DFB6710 + +★ ★ ★ ☆ ☆ 65分 + +```sql + +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + ( +SELECT + film_id +FROM + film +) film +) film +) film +) film +) film +) film +) film +) film +) film +) film +) film +) film +) film +) film +) film +) film +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | index | NULL | idx\_fk\_language\_id | 1 | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 执行计划中嵌套连接深度过深 + +* **Item:** SUB.004 + +* **Severity:** L3 + +* **Content:** MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。 + +# Query: 16C2B14E7DAA9906 + +★ ☆ ☆ ☆ ☆ 35分 + +```sql + +SELECT + * +FROM + film +WHERE + language_id = ( +SELECT + language_id +FROM + language +LIMIT + 1) +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | PRIMARY | *film* | NULL | ALL | idx\_fk\_language\_id | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where | +| 2 | SUBQUERY | *language* | NULL | index | NULL | PRIMARY | 1 | NULL | 6 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **PRIMARY**: 最外层的select. + +* **SUBQUERY**: 子查询中的第一个SELECT查询, 不依赖于外部查询的结果集. + +#### Type信息解读 + +* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## 未使用 ORDER BY 的 LIMIT 查询 + +* **Item:** RES.002 + +* **Severity:** L4 + +* **Content:** 没有 ORDER BY 的 LIMIT 会导致非确定性的结果,这取决于查询执行计划。 + +## MySQL 对子查询的优化效果不佳 + +* **Item:** SUB.001 + +* **Severity:** L4 + +* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 + +# Query: 16CB4628D2597D40 + +★ ★ ★ ☆ ☆ 65分 + +```sql + +SELECT + * +FROM + city i + LEFT JOIN country o ON i. city_id= o. country_id +UNION +SELECT + * +FROM + city i + RIGHT JOIN country o ON i. city_id= o. country_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | PRIMARY | *i* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | PRIMARY | *o* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.i.city\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 2 | UNION | *o* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 2 | UNION | *i* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 2 | UNION | *i* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **PRIMARY**: 最外层的select. + +* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集. + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION + +* **Item:** SUB.002 + +* **Severity:** L2 + +* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。 + +# Query: EA50643B01E139A8 + +★ ☆ ☆ ☆ ☆ 35分 + +```sql + +SELECT + * +FROM + ( +SELECT + * +FROM + actor +WHERE + last_update= '2006-02-15 04:34:33' + AND last_name= 'CHASE' +) t +WHERE + last_update= '2006-02-15 04:34:33' + AND last_name= 'CHASE' +GROUP BY + first_name +``` + +## 为sakila库的actor表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`actor\` add index \`idx\_last\_name\_last\_update\_first\_name\` (\`last\_name\`,\`last\_update\`,\`first\_name\`) ; + + + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## 非确定性的 GROUP BY + +* **Item:** RES.001 + +* **Severity:** L4 + +* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。 + +## MySQL 对子查询的优化效果不佳 + +* **Item:** SUB.001 + +* **Severity:** L4 + +* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 + +# Query: 7598A4EDE6CFA6BE + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + city i + LEFT JOIN country o ON i. city_id= o. country_id +WHERE + o. country_id is null +UNION +SELECT + * +FROM + city i + RIGHT JOIN country o ON i. city_id= o. country_id +WHERE + i. city_id is null +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | PRIMARY | *i* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | PRIMARY | *o* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.i.city\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using where; Not exists | +| 2 | UNION | *o* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 2 | UNION | *i* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using where; Not exists | +| 2 | UNION | *i* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using where; Not exists | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **PRIMARY**: 最外层的select. + +* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集. + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行. + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION + +* **Item:** SUB.002 + +* **Severity:** L2 + +* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。 + +# Query: 1E8B70E30062FD13 + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + first_name, last_name, email +FROM + customer STRAIGHT_JOIN address ON customer. address_id= address. address_id +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *customer* | NULL | ALL | idx\_fk\_address\_id | NULL | NULL | NULL | 599 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | SIMPLE | *address* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.customer.address\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +# Query: E48A20D0413512DA + +★ ☆ ☆ ☆ ☆ 20分 + +```sql + +SELECT + ID, name +FROM + ( +SELECT + address +FROM + customer_list +WHERE + SID= 1 +ORDER BY + phone +LIMIT + 50, 10) a + JOIN customer_list l ON (a. address= l. address) + JOIN city c ON (c. city= l. city) +ORDER BY + phone desc +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | PRIMARY | *country* | NULL | index | PRIMARY | PRIMARY | 2 | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | Using index; Using temporary; Using filesort | +| 1 | PRIMARY | *city* | NULL | ref | PRIMARY,
idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.country.country\_id | 5 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | PRIMARY | *c* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | 10.00% | ☠️ **O(n)** | Using where; Using join buffer (Block Nested Loop) | +| 1 | PRIMARY | *a* | NULL | ref | PRIMARY,
idx\_fk\_city\_id | idx\_fk\_city\_id | 2 | sakila.city.city\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | PRIMARY | *cu* | NULL | ref | idx\_fk\_address\_id | idx\_fk\_address\_id | 2 | sakila.a.address\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 1 | PRIMARY | ** | NULL | ref | | | 152 | sakila.a.address | 6 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | +| 2 | DERIVED | *a* | NULL | ALL | PRIMARY,
idx\_fk\_city\_id | NULL | NULL | NULL | 603 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort | +| 2 | DERIVED | *cu* | NULL | ref | idx\_fk\_store\_id,
idx\_fk\_address\_id | idx\_fk\_address\_id | 2 | sakila.a.address\_id | 1 | 54.42% | ☠️ **O(n)** | Using where | +| 2 | DERIVED | *city* | NULL | eq\_ref | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | sakila.a.city\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | NULL | +| 2 | DERIVED | *country* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\_id | 1 | ☠️ **100.00%** | ☠️ **O(n)** | Using index | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **DERIVED**: 用于from子句里有子查询的情况. MySQL会递归执行这些子查询, 把结果放在临时表里. + +* **PRIMARY**: 最外层的select. + +#### Type信息解读 + +* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'. + +* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大. + +* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'. + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询. + +* **Using join buffer**: 从已有连接中找被读入缓存的数据, 并且通过缓存来完成与当前表的连接. + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的city表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_city\` (\`city\`) ; + + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引 + +* **Item:** CLA.007 + +* **Severity:** L2 + +* **Content:** ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。 + +## 同一张表被连接两次 + +* **Item:** JOI.002 + +* **Severity:** L4 + +* **Content:** 相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。 + +## MySQL 对子查询的优化效果不佳 + +* **Item:** SUB.001 + +* **Severity:** L4 + +* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 + +# Query: B0BA5A7079EA16B3 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + DATE( last_update) = '2006-02-15' +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +## 避免在 WHERE 条件中使用函数或其他运算符 + +* **Item:** FUN.001 + +* **Severity:** L2 + +* **Content:** 虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰。 + +# Query: 18A2AD1395A58EAE + +★ ★ ★ ☆ ☆ 60分 + +```sql + +SELECT + last_update +FROM + film +GROUP BY + DATE( last_update) +``` + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +## GROUP BY 的条件为表达式 + +* **Item:** CLA.010 + +* **Severity:** L2 + +* **Content:** 当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 + +# Query: 60F234BA33AAC132 + +★ ★ ★ ☆ ☆ 70分 + +```sql + +SELECT + last_update +FROM + film +ORDER BY + DATE( last_update) +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'. + + +## 最外层 SELECT 未指定 WHERE 条件 + +* **Item:** CLA.001 + +* **Severity:** L4 + +* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 + +## ORDER BY 的条件为表达式 + +* **Item:** CLA.009 + +* **Severity:** L2 + +* **Content:** 当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 + +# Query: 1ED2B7ECBA4215E1 + +★ ★ ★ ★ ☆ 80分 + +```sql + +SELECT + description +FROM + film +WHERE + description IN( 'NEWS', + 'asd' +) +GROUP BY + description +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 20.00% | ☠️ **O(n)** | Using where; Using temporary | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + +* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_description\` (\`description\`(255)) ; + + + +## 请为 GROUP BY 显示添加 ORDER BY 条件 + +* **Item:** CLA.008 + +* **Severity:** L2 + +* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。 + +# Query: 255BAC03F56CDBC7 + +★ ★ ★ ★ ★ 100分 + +```sql + +ALTER TABLE + address +ADD + index idx_city_id( city_id) +``` + +## OK + +# Query: C315BC4EE0F4E523 + +★ ★ ★ ★ ★ 100分 + +```sql + +ALTER TABLE + inventory +ADD + index `idx_store_film` ( + `store_id`, `film_id`) +``` + +## 提醒:请将索引属性顺序与查询对齐 + +* **Item:** KEY.004 + +* **Severity:** L0 + +* **Content:** 如果为列创建复合索引,请确保查询属性与索引属性的顺序相同,以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐,那么DBMS可能无法在查询处理期间使用索引。 + +# Query: 9BB74D074BA0727C + +★ ★ ★ ★ ☆ 90分 + +```sql + +ALTER TABLE + inventory +ADD + index `idx_store_film` ( + `store_id`, `film_id`), + ADD + index `idx_store_film` ( + `store_id`, `film_id`), + ADD + index `idx_store_film` ( + `store_id`, `film_id`) +``` + +## 索引名称已存在 + +* **Item:** IDX.001 + +* **Severity:** L2 + +* **Content:** Duplicate key name 'idx\_store\_film' + +* **Case:** alter table inventory add index \`idx\_store\_film\` (\`store\_id\`,\`film\_id\`),add index \`idx\_store\_film\` (\`store\_id\`,\`film\_id\`),add index \`idx\_store\_film\` (\`store\_id\`,\`film\_id\`) + + +## 提醒:请将索引属性顺序与查询对齐 + +* **Item:** KEY.004 + +* **Severity:** L0 + +* **Content:** 如果为列创建复合索引,请确保查询属性与索引属性的顺序相同,以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐,那么DBMS可能无法在查询处理期间使用索引。 + +# Query: C95B5C028C8FFF95 + +★ ☆ ☆ ☆ ☆ 30分 + +```sql + +SELECT + DATE_FORMAT( t. last_update, '%Y-%m-%d' +), +COUNT( DISTINCT ( + t. city)) + FROM + city t + WHERE + t. last_update > '2018-10-22 00:00:00' + AND t. city LIKE '%Chrome%' + AND t. city = 'eip' + GROUP BY + DATE_FORMAT( t. last_update, '%Y-%m-%d' +) +ORDER BY + DATE_FORMAT( t. last_update, '%Y-%m-%d' +) +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *NULL* | NULL | NULL | NULL | NULL | NULL | NULL | 0 | 0.00% | ☠️ **O(n)** | NULL | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + + +## 为sakila库的city表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_city\_last\_update\` (\`city\`,\`last\_update\`) ; + + + +## 建议使用 AS 关键字显示声明一个别名 + +* **Item:** ALI.001 + +* **Severity:** L0 + +* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。 + +## 不建议使用前项通配符查找 + +* **Item:** ARG.001 + +* **Severity:** L4 + +* **Content:** 例如 "%foo",查询参数有一个前项通配符的情况无法使用已有索引。 + +## ORDER BY 的条件为表达式 + +* **Item:** CLA.009 + +* **Severity:** L2 + +* **Content:** 当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 + +## GROUP BY 的条件为表达式 + +* **Item:** CLA.010 + +* **Severity:** L2 + +* **Content:** 当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。 + +## ORDER BY 多个列但排序方向不同时可能无法使用索引 + +* **Item:** KEY.008 + +* **Severity:** L4 + +* **Content:** 在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。 + +# Query: C11ECE7AE5F80CE5 + +★ ★ ☆ ☆ ☆ 45分 + +```sql +create table hello. t (id int unsigned) +``` + +## 建议为表添加注释 + +* **Item:** CLA.011 + +* **Severity:** L1 + +* **Content:** 为表添加注释能够使得表的意义更明确,从而为日后的维护带来极大的便利。 + +## 请为列添加默认值 + +* **Item:** COL.004 + +* **Severity:** L1 + +* **Content:** 请为列添加默认值,如果是 ALTER 操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。 + +## 列未添加注释 + +* **Item:** COL.005 + +* **Severity:** L1 + +* **Content:** 建议对表中每个列添加注释,来明确每个列在表中的含义及作用。 + +## 未指定主键或主键非 int 或 bigint + +* **Item:** KEY.007 + +* **Severity:** L4 + +* **Content:** 未指定主键或主键非 int 或 bigint,建议将主键设置为 int unsigned 或 bigint unsigned。 + +## 请为表选择合适的存储引擎 + +* **Item:** TBL.002 + +* **Severity:** L4 + +* **Content:** 建表或修改表的存储引擎时建议使用推荐的存储引擎,如:innodb + +# Query: 291F95B7DCB74C21 + +★ ★ ★ ★ ☆ 95分 + +```sql + +SELECT + * +FROM + tb +WHERE + data >= '' +``` + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + +# Query: 084DA3E3EE38DD85 + +★ ★ ★ ★ ★ 100分 + +```sql + +ALTER TABLE + tb alter column id +DROP + DEFAULT +``` + diff --git a/test/fixture/test_Simple_Query_Optimizer.golden b/test/fixture/test_Simple_Query_Optimizer.golden new file mode 100644 index 0000000000000000000000000000000000000000..4442d4df22697c6352c8d1011277cd37041b776b --- /dev/null +++ b/test/fixture/test_Simple_Query_Optimizer.golden @@ -0,0 +1,56 @@ +# Query: 5767EE37339B2402 + +★ ★ ★ ★ ☆ 85分 + +```sql + +SELECT + * +FROM + film +WHERE + LENGTH > 120 +``` + +## Explain信息 + +| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 33.33% | ☠️ **O(n)** | Using where | + + + +### Explain信息解读 + +#### SelectType信息解读 + +* **SIMPLE**: 简单SELECT(不使用UNION或子查询等). + +#### Type信息解读 + +* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描. + +#### Extra信息解读 + +* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的. + + +## 为sakila库的film表添加索引 + +* **Item:** IDX.001 + +* **Severity:** L2 + + +* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ; + + + +## 不建议使用 SELECT * 类型查询 + +* **Item:** COL.001 + +* **Severity:** L1 + +* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 + diff --git a/test/main.bats b/test/main.bats new file mode 100755 index 0000000000000000000000000000000000000000..ac67bb06125d0c8e54ca2d32bbdada8597eb4179 --- /dev/null +++ b/test/main.bats @@ -0,0 +1,20 @@ +#!/usr/bin/env bats + +load test_helper + +@test "Simple Query Optimizer" { + ${SOAR_BIN_ENV} -query "select * from film where length > 120" | grep -v "散粒度" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden + run golden_diff ${BATS_TEST_NAME} + [ $status -eq 0 ] +} + +@test "Syntax Check" { + run ${SOAR_BIN} -query "select * frm film" -only-syntax-check + [ $status -eq 1 ] +} + +@test "Run all test cases" { + ${SOAR_BIN} -list-test-sqls | ${SOAR_BIN_ENV} | grep -v "散粒度" > ${BATS_TMP_DIRNAME}/${BATS_TEST_NAME}.golden + run golden_diff ${BATS_TEST_NAME} + [ $status -eq 0 ] +} diff --git a/test/sql/README.md b/test/sql/README.md new file mode 100644 index 0000000000000000000000000000000000000000..926d54d19d1fb945cdfff6aa035c83a8f1356eea --- /dev/null +++ b/test/sql/README.md @@ -0,0 +1,19 @@ +# Test Database + +## sakila + +* Download MySQL sakila database from http://downloads.mysql.com/docs/sakila-db.tar.gz . +* InnoDB added FULLTEXT support in 5.6.10, you should add version comment `/*!50610 xxx */` for `film_text` table and it's triggers. +* Merge schema and data into one file `sakila.sql` + +## world\_x + +world\_x contain JSON datatype, SOAR use this database for JSON testing. + +* Download MySQL world\_x database from http://downloads.mysql.com/docs/world_x-db.tar.gz . +* MySQL support JSON datatype since 5.7.8, you should add version comment `/*!50708 xxx */` for `city`, `countryinfo`. +* Merge `sakila.sql`, `world_x.sql` into init.sql. + +```bash +gzip init.sql +``` diff --git a/test/sql/init.sql.gz b/test/sql/init.sql.gz new file mode 100644 index 0000000000000000000000000000000000000000..c3659425f53884ce8626be6beb02e00fbbc14b21 Binary files /dev/null and b/test/sql/init.sql.gz differ diff --git a/test/test_helper.bash b/test/test_helper.bash new file mode 100644 index 0000000000000000000000000000000000000000..1af72cc378fad6e4b977e0a0897ab1b19b41b0e9 --- /dev/null +++ b/test/test_helper.bash @@ -0,0 +1,14 @@ +setup() { + export SOAR_DEV_DIRNAME="${BATS_TEST_DIRNAME}/../" + export SOAR_BIN="${SOAR_DEV_DIRNAME}/bin/soar" + export SOAR_BIN_ENV="${SOAR_DEV_DIRNAME}/bin/soar -config ${SOAR_DEV_DIRNAME}/etc/soar.yaml" + export BATS_TMP_DIRNAME="${BATS_TEST_DIRNAME}/tmp" + export BATS_FIXTURE_DIRNAME="${BATS_TEST_DIRNAME}/fixture" + mkdir -p "${BATS_TMP_DIRNAME}" +} + +golden_diff() { + FUNC_NAME=$1 + diff "${BATS_TMP_DIRNAME}/${FUNC_NAME}.golden" "${BATS_FIXTURE_DIRNAME}/${FUNC_NAME}.golden" >/dev/null + return $? +}