提交 9df3418c 编写于 作者: martianzhang's avatar martianzhang 提交者: Pengxiang Li

add cli test frame work bats (#181)

* import bats for cli test

  https://github.com/sstephenson/bats

* add simple test case for cli use bats

* format main.bats add env

* replace main_test.sh with main.bats

  make explain suggestion sorted
  make index suggestion sorted

* add test database world_x

* update bats golden result

* make docker add 180s timeout

  1. add timeout for docker start
  2. add not explain able sql test case into explain_test.go

* explain info Using index VS Using index condition

  remove nonappearence key from explain extra info

* add timeout for test
上级 c2a05448
bin/ bin/
release/ release/
test/tmp/
common/version.go common/version.go
doc/blueprint/ doc/blueprint/
*.iml *.iml
......
...@@ -20,11 +20,15 @@ services: ...@@ -20,11 +20,15 @@ services:
before_install: before_install:
- docker pull mysql - docker pull mysql
- sudo add-apt-repository ppa:duggan/bats --yes
- sudo apt-get update -qq
- sudo apt-get install -qq bats
script: script:
- make build - make build
- make docker - make docker
- make cover - make cover
- make test-cli
after_success: after_success:
- bash <(curl -s https://codecov.io/bash) - bash <(curl -s https://codecov.io/bash)
...@@ -72,15 +72,24 @@ fmt: go_version_check ...@@ -72,15 +72,24 @@ fmt: go_version_check
.PHONY: test .PHONY: test
test: test:
@echo "$(CGREEN)Run all test cases ...$(CEND)" @echo "$(CGREEN)Run all test cases ...$(CEND)"
go test -race ./... go test -timeout 10m -race ./...
@echo "test Success!" @echo "test Success!"
# Rule golang test cases with `-update` flag # Rule golang test cases with `-update` flag
.PHONY: test-update
test-update: test-update:
@echo "$(CGREEN)Run all test cases with -update flag ...$(CEND)" @echo "$(CGREEN)Run all test cases with -update flag ...$(CEND)"
go test ./... -update go test ./... -update
@echo "test-update Success!" @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 # Code Coverage
# colorful coverage numerical >=90% GREEN, <80% RED, Other YELLOW # colorful coverage numerical >=90% GREEN, <80% RED, Other YELLOW
.PHONY: cover .PHONY: cover
...@@ -180,46 +189,44 @@ release: build ...@@ -180,46 +189,44 @@ release: build
docker: docker:
@echo "$(CGREEN)Build mysql test enviorment ...$(CEND)" @echo "$(CGREEN)Build mysql test enviorment ...$(CEND)"
@docker stop soar-mysql 2>/dev/null || true @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)" @echo "docker run --name soar-mysql $(MYSQL_RELEASE):$(MYSQL_VERSION)"
@docker run --name soar-mysql --rm -d \ @docker run --name soar-mysql --rm -d \
-e MYSQL_ROOT_PASSWORD=1tIsB1g3rt \ -e MYSQL_ROOT_PASSWORD=1tIsB1g3rt \
-e MYSQL_DATABASE=sakila \ -e MYSQL_DATABASE=sakila \
-p 3306:3306 \ -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) $(MYSQL_RELEASE):$(MYSQL_VERSION)
@echo "waiting for sakila database initializing " @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 \ @timeout=180; while [ $${timeout} -gt 0 ] ; do \
printf '.' ; \ if ! docker exec soar-mysql mysql --user=root --password=1tIsB1g3rt --host "127.0.0.1" --silent -NBe "do 1" >/dev/null 2>&1 ; then \
sleep 1 ; \ timeout=`expr $$timeout - 1`; \
done ; \ printf '.' ; sleep 1 ; \
echo '.' else \
@echo "mysql test enviorment is ready!" 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 .PHONY: docker-connect
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 # attach docker container with bash interactive mode
.PHONY: docker-it .PHONY: docker-it
docker-it: docker-it:
docker exec -it soar-mysql /bin/bash 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 .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)" @echo "$(CGREEN)daily build finished ...$(CEND)"
# vendor, docker will cost long time, if all those are ready, daily-quick will much more fast. # vendor, docker will cost long time, if all those are ready, daily-quick will much more fast.
.PHONY: daily-quick .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)" @echo "$(CGREEN)daily-quick build finished ...$(CEND)"
.PHONY: logo .PHONY: logo
...@@ -238,7 +245,7 @@ clean: ...@@ -238,7 +245,7 @@ clean:
rm -f ${BINARY}.$${GOOS}-$${GOARCH} ;\ rm -f ${BINARY}.$${GOOS}-$${GOARCH} ;\
done ;\ done ;\
done done
rm -f ${BINARY} coverage.* rm -f ${BINARY} coverage.* test/tmp/*
find . -name "*.log" -delete find . -name "*.log" -delete
git clean -fi git clean -fi
docker stop soar-mysql 2>/dev/null || true docker stop soar-mysql 2>/dev/null || true
...@@ -18,6 +18,7 @@ package advisor ...@@ -18,6 +18,7 @@ package advisor
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
"github.com/XiaoMi/soar/ast" "github.com/XiaoMi/soar/ast"
...@@ -989,7 +990,13 @@ func (idxAdvs IndexAdvises) Format() map[string]Rule { ...@@ -989,7 +990,13 @@ func (idxAdvs IndexAdvises) Format() map[string]Rule {
rules[advKey].Content = strings.Trim(rules[advKey].Content, common.Config.Delimiter) rules[advKey].Content = strings.Trim(rules[advKey].Content, common.Config.Delimiter)
} }
var sortAdvs []string
for adv := range rules { for adv := range rules {
sortAdvs = append(sortAdvs, adv)
}
sort.Strings(sortAdvs)
for _, adv := range sortAdvs {
key := fmt.Sprintf("IDX.%03d", number) key := fmt.Sprintf("IDX.%03d", number)
ddl := ast.MergeAlterTables(sqls[adv]...) ddl := ast.MergeAlterTables(sqls[adv]...)
// 由于传入合并的SQL都是一张表的,所以一定只会输出一条ddl语句 // 由于传入合并的SQL都是一张表的,所以一定只会输出一条ddl语句
......
...@@ -20,7 +20,6 @@ import ( ...@@ -20,7 +20,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
...@@ -67,24 +66,22 @@ func captureOutput(f func()) string { ...@@ -67,24 +66,22 @@ func captureOutput(f func()) string {
r, w, _ := os.Pipe() r, w, _ := os.Pipe()
os.Stdout = w os.Stdout = w
// execute function
f()
outC := make(chan string)
// copy the output in a separate goroutine so printing can't block indefinitely // copy the output in a separate goroutine so printing can't block indefinitely
outC := make(chan string)
go func() { go func() {
var buf bytes.Buffer buf, err := ioutil.ReadAll(r)
_, err := io.Copy(&buf, r)
if err != nil { if err != nil {
Log.Warning(err.Error()) panic(err)
} }
outC <- buf.String() outC <- string(buf)
}() }()
// execute function
f()
// back to normal state // back to normal state
err := w.Close() if err := w.Close(); err != nil {
if err != nil { panic(err)
Log.Warning(err.Error())
} }
os.Stdout = oldStdout // restoring the real stdout os.Stdout = oldStdout // restoring the real stdout
out := <-outC out := <-outC
......
/*
* 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")
}
}
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"runtime" "runtime"
"sort"
"strconv" "strconv"
"strings" "strings"
...@@ -652,6 +653,7 @@ func ExplainInfoTranslator(exp *ExplainInfo) string { ...@@ -652,6 +653,7 @@ func ExplainInfoTranslator(exp *ExplainInfo) string {
} }
if len(selectTypeBuf) > 0 { if len(selectTypeBuf) > 0 {
buf = append(buf, fmt.Sprint("#### SelectType信息解读\n")) buf = append(buf, fmt.Sprint("#### SelectType信息解读\n"))
sort.Strings(selectTypeBuf)
buf = append(buf, strings.Join(selectTypeBuf, "\n")) buf = append(buf, strings.Join(selectTypeBuf, "\n"))
} }
...@@ -681,6 +683,7 @@ func ExplainInfoTranslator(exp *ExplainInfo) string { ...@@ -681,6 +683,7 @@ func ExplainInfoTranslator(exp *ExplainInfo) string {
} }
if len(accessTypeBuf) > 0 { if len(accessTypeBuf) > 0 {
buf = append(buf, fmt.Sprint("#### Type信息解读\n")) buf = append(buf, fmt.Sprint("#### Type信息解读\n"))
sort.Strings(accessTypeBuf)
buf = append(buf, strings.Join(accessTypeBuf, "\n")) buf = append(buf, strings.Join(accessTypeBuf, "\n"))
} }
...@@ -693,10 +696,11 @@ func ExplainInfoTranslator(exp *ExplainInfo) string { ...@@ -693,10 +696,11 @@ func ExplainInfoTranslator(exp *ExplainInfo) string {
for _, row := range rows { for _, row := range rows {
for k, c := range explainExtra { for k, c := range explainExtra {
if strings.Contains(row.Extra, k) { if strings.Contains(row.Extra, k) {
if k == "Impossible WHERE" { if k == "Impossible WHERE" && strings.Contains(row.Extra, "Impossible WHERE noticed after reading const tables") {
if strings.Contains(row.Extra, "Impossible WHERE noticed after reading const tables") {
continue continue
} }
if k == "Using index" && strings.Contains(row.Extra, "Using index condition") {
continue
} }
warn := false warn := false
for _, w := range common.Config.ExplainWarnExtra { for _, w := range common.Config.ExplainWarnExtra {
...@@ -716,6 +720,7 @@ func ExplainInfoTranslator(exp *ExplainInfo) string { ...@@ -716,6 +720,7 @@ func ExplainInfoTranslator(exp *ExplainInfo) string {
} }
if len(extraTypeBuf) > 0 { if len(extraTypeBuf) > 0 {
buf = append(buf, fmt.Sprint("#### Extra信息解读\n")) buf = append(buf, fmt.Sprint("#### Extra信息解读\n"))
sort.Strings(extraTypeBuf)
buf = append(buf, strings.Join(extraTypeBuf, "\n")) buf = append(buf, strings.Join(extraTypeBuf, "\n"))
} }
......
...@@ -26,6 +26,7 @@ import ( ...@@ -26,6 +26,7 @@ import (
) )
var sqls = []string{ var sqls = []string{
`use sakila`, // not explain able sql, will convert to empty!
`select * from city where country_id = 44;`, `select * from city where country_id = 44;`,
`select * from address where address2 is not null;`, `select * from address where address2 is not null;`,
`select * from address where address2 is null;`, `select * from address where address2 is null;`,
......
#!/bin/bash #!/bin/bash
NEEDED_COMMANDS="docker git go govendor retool" NEEDED_COMMANDS="docker git go govendor retool bats"
for cmd in ${NEEDED_COMMANDS} ; do for cmd in ${NEEDED_COMMANDS} ; do
if ! command -v "${cmd}" &> /dev/null ; then if ! command -v "${cmd}" &> /dev/null ; then
...@@ -25,3 +25,7 @@ done ...@@ -25,3 +25,7 @@ done
# retool # retool
## go get github.com/twitchtv/retool ## go get github.com/twitchtv/retool
# bats https://github.com/sstephenson/bats
## Ubuntu: apt-get install bats
## Mac: brew install bats
#!/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
# Query: C3FAEDA6AD6D762B # Query: C3FAEDA6AD6D762B
★ ★ ★ ★ ☆ 95分 ★ ★ ★ ★ ☆ 85分
```sql ```sql
...@@ -12,6 +12,40 @@ WHERE ...@@ -12,6 +12,40 @@ WHERE
LENGTH = 86 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 * 类型查询 ## 不建议使用 SELECT * 类型查询
* **Item:** COL.001 * **Item:** COL.001
...@@ -22,7 +56,7 @@ WHERE ...@@ -22,7 +56,7 @@ WHERE
# Query: E969B9297DA79BA6 # Query: E969B9297DA79BA6
★ ★ ★ ★ ☆ 90分 ★ ★ ★ ★ ☆ 80分
```sql ```sql
...@@ -34,6 +68,40 @@ WHERE ...@@ -34,6 +68,40 @@ WHERE
LENGTH IS NULL 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 值判断 ## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断
* **Item:** ARG.006 * **Item:** ARG.006
...@@ -64,6 +132,25 @@ HAVING ...@@ -64,6 +132,25 @@ HAVING
title = 'abc' 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -90,7 +177,7 @@ HAVING ...@@ -90,7 +177,7 @@ HAVING
# Query: A0C5E62C724A121A # Query: A0C5E62C724A121A
★ ★ ★ ★ ☆ 95分 ★ ★ ★ ★ ☆ 85分
```sql ```sql
...@@ -102,6 +189,40 @@ WHERE ...@@ -102,6 +189,40 @@ WHERE
LENGTH >= 60 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 * 类型查询 ## 不建议使用 SELECT * 类型查询
* **Item:** COL.001 * **Item:** COL.001
...@@ -112,7 +233,7 @@ WHERE ...@@ -112,7 +233,7 @@ WHERE
# Query: 868317D1973FD1B0 # Query: 868317D1973FD1B0
★ ★ ★ ★ ☆ 95分 ★ ★ ★ ★ ☆ 85分
```sql ```sql
...@@ -125,6 +246,40 @@ WHERE ...@@ -125,6 +246,40 @@ WHERE
AND 84 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 * 类型查询 ## 不建议使用 SELECT * 类型查询
* **Item:** COL.001 * **Item:** COL.001
...@@ -147,6 +302,29 @@ WHERE ...@@ -147,6 +302,29 @@ WHERE
title LIKE 'AIR%' 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 * 类型查询 ## 不建议使用 SELECT * 类型查询
* **Item:** COL.001 * **Item:** COL.001
...@@ -169,6 +347,29 @@ WHERE ...@@ -169,6 +347,29 @@ WHERE
title IS NOT NULL 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 值判断 ## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断
* **Item:** ARG.006 * **Item:** ARG.006
...@@ -200,21 +401,47 @@ WHERE ...@@ -200,21 +401,47 @@ WHERE
AND title = 'ALABAMA DEVIL' AND title = 'ALABAMA DEVIL'
``` ```
## 不建议使用 SELECT * 类型查询 ## Explain信息
* **Item:** COL.001 | 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 |
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 不建议使用连续判断 ### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
* **Item:** RES.009 #### 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 * **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\` (\`title\`,\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 68E48001ECD53152 # Query: 68E48001ECD53152
...@@ -231,21 +458,47 @@ WHERE ...@@ -231,21 +458,47 @@ WHERE
AND title = 'ALABAMA DEVIL' AND title = 'ALABAMA DEVIL'
``` ```
## 不建议使用 SELECT * 类型查询 ## Explain信息
* **Item:** COL.001 | 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 |
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 不建议使用连续判断 ### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **Item:** RES.009 * **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 * **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\` (\`title\`,\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 12FF1DAA3D425FA9 # Query: 12FF1DAA3D425FA9
...@@ -263,21 +516,47 @@ WHERE ...@@ -263,21 +516,47 @@ WHERE
AND title = 'xyz' AND title = 'xyz'
``` ```
## 不建议使用 SELECT * 类型查询 ## Explain信息
* **Item:** COL.001 | id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ref | idx\_title,<br>idx\_fk\_language\_id | idx\_title | 767 | const | 1 | 33.33% | ☠️ **O(n)** | Using where |
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 不建议使用连续判断 ### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
* **Item:** RES.009 #### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2 * **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\` (\`title\`,\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: E84CBAAC2E12BDEA # Query: E84CBAAC2E12BDEA
...@@ -294,21 +573,47 @@ WHERE ...@@ -294,21 +573,47 @@ WHERE
AND language_id < 10 AND language_id < 10
``` ```
## 不建议使用 SELECT * 类型查询 ## Explain信息
* **Item:** COL.001 | 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 |
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 不建议使用连续判断 ### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Item:** RES.009 * **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2 * **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 6A0F035BD4E01018 # Query: 6A0F035BD4E01018
...@@ -327,45 +632,41 @@ GROUP BY ...@@ -327,45 +632,41 @@ GROUP BY
release_year release_year
``` ```
## 请为 GROUP BY 显示添加 ORDER BY 条件 ## Explain信息
* **Item:** CLA.008 | 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 |
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
## 使用 SUM(COL) 时需注意 NPE 问题 ### Explain信息解读
* **Item:** FUN.006 #### SelectType信息解读
* **Severity:** L1 * **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl #### Type信息解读
## 不建议使用连续判断 * ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
* **Item:** RES.009 #### Extra信息解读
* **Severity:** L2 * **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。 * ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.
# Query: 23D176AEA2947002
★ ★ ★ ★ ☆ 85分 ## 为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\`) ;
```sql
SELECT
release_year, SUM( LENGTH)
FROM
film
WHERE
LENGTH >= 123
GROUP BY
release_year
```
## 请为 GROUP BY 显示添加 ORDER BY 条件 ## 请为 GROUP BY 显示添加 ORDER BY 条件
...@@ -383,31 +684,133 @@ GROUP BY ...@@ -383,31 +684,133 @@ GROUP BY
* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl * **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl
# Query: 73DDF6E6D9E40384 # Query: 23D176AEA2947002
★ ★ ★ ☆ ☆ 65分 ★ ★ ★ ☆ ☆ 75分
```sql ```sql
SELECT SELECT
release_year, language_id, SUM( LENGTH) release_year, SUM( LENGTH)
FROM FROM
film film
WHERE
LENGTH >= 123
GROUP BY GROUP BY
release_year, language_id release_year
``` ```
## 最外层 SELECT 未指定 WHERE 条件 ## Explain信息
* **Item:** CLA.001 | 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 |
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## 请为 GROUP BY 显示添加 ORDER BY 条件 ### Explain信息解读
* **Item:** CLA.008 #### 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 * **Severity:** L2
...@@ -423,7 +826,7 @@ GROUP BY ...@@ -423,7 +826,7 @@ GROUP BY
# Query: B3C502B4AA344196 # Query: B3C502B4AA344196
★ ★ ★ ☆ ☆ 75分 ★ ★ ★ ☆ ☆ 65分
```sql ```sql
...@@ -437,6 +840,42 @@ GROUP BY ...@@ -437,6 +840,42 @@ GROUP BY
release_year, (LENGTH+ language_id) 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 条件 ## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008 * **Item:** CLA.008
...@@ -463,7 +902,7 @@ GROUP BY ...@@ -463,7 +902,7 @@ GROUP BY
# Query: 47044E1FE1A965A5 # Query: 47044E1FE1A965A5
★ ★ ★ ☆ ☆ 70分 ★ ★ ★ ☆ ☆ 60分
```sql ```sql
...@@ -475,6 +914,40 @@ GROUP BY ...@@ -475,6 +914,40 @@ GROUP BY
release_year 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -493,7 +966,7 @@ GROUP BY ...@@ -493,7 +966,7 @@ GROUP BY
# Query: 2BA1217F6C8CF0AB # Query: 2BA1217F6C8CF0AB
★ ☆ ☆ ☆ 45分 ☆ ☆ ☆ ☆ 35分
```sql ```sql
...@@ -505,6 +978,17 @@ GROUP BY ...@@ -505,6 +978,17 @@ GROUP BY
address, district address, district
``` ```
## 为sakila库的address表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Case:** ALTER TABLE \`sakila\`.\`address\` add index \`idx\_address\_district\` (\`address\`,\`district\`) ;
## 最外层 SELECT 未指定 WHERE 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -553,6 +1037,29 @@ GROUP BY ...@@ -553,6 +1037,29 @@ GROUP BY
title 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 条件 ## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008 * **Item:** CLA.008
...@@ -571,7 +1078,7 @@ GROUP BY ...@@ -571,7 +1078,7 @@ GROUP BY
# Query: DF59FD602E4AA368 # Query: DF59FD602E4AA368
★ ★ ★ ★ ☆ 80分 ★ ★ ★ ☆ ☆ 70分
```sql ```sql
...@@ -587,6 +1094,17 @@ ORDER BY ...@@ -587,6 +1094,17 @@ ORDER BY
language_id 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 ## 非确定性的 GROUP BY
* **Item:** RES.001 * **Item:** RES.001
...@@ -597,7 +1115,7 @@ ORDER BY ...@@ -597,7 +1115,7 @@ ORDER BY
# Query: F6DBEAA606D800FC # Query: F6DBEAA606D800FC
★ ★ ★ ★ ★ 100分 ★ ★ ★ ★ ☆ 90分
```sql ```sql
...@@ -613,11 +1131,47 @@ ORDER BY ...@@ -613,11 +1131,47 @@ ORDER BY
release_year release_year
``` ```
## OK ## 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 # Query: 6E9B96CA3F0E6BDA
★ ★ ★ ☆ ☆ 65分 ★ ★ ☆ ☆ ☆ 55分
```sql ```sql
...@@ -631,6 +1185,42 @@ ORDER BY ...@@ -631,6 +1185,42 @@ ORDER BY
release_year ASC, language_id DESC 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 语句对多个不同条件使用不同方向的排序无法使用索引 ## ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引
* **Item:** CLA.007 * **Item:** CLA.007
...@@ -657,7 +1247,7 @@ ORDER BY ...@@ -657,7 +1247,7 @@ ORDER BY
# Query: 2EAACFD7030EA528 # Query: 2EAACFD7030EA528
★ ★ ★ ★ ★ 100分 ★ ★ ★ ★ ☆ 90分
```sql ```sql
...@@ -675,11 +1265,47 @@ LIMIT ...@@ -675,11 +1265,47 @@ LIMIT
10 10
``` ```
## OK ## 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 # Query: 5CE2F187DBF2A710
★ ★ ★ ★ ☆ 95分 ★ ★ ★ ★ ☆ 85分
```sql ```sql
...@@ -695,6 +1321,42 @@ LIMIT ...@@ -695,6 +1321,42 @@ LIMIT
10 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 * 类型查询 ## 不建议使用 SELECT * 类型查询
* **Item:** COL.001 * **Item:** COL.001
...@@ -719,6 +1381,29 @@ LIMIT ...@@ -719,6 +1381,29 @@ LIMIT
10 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -751,6 +1436,29 @@ LIMIT ...@@ -751,6 +1436,29 @@ LIMIT
10 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -761,7 +1469,7 @@ LIMIT ...@@ -761,7 +1469,7 @@ LIMIT
# Query: 965D5AC955824512 # Query: 965D5AC955824512
★ ★ ★ ★ ☆ 95分 ★ ★ ★ ★ ☆ 85分
```sql ```sql
...@@ -777,6 +1485,42 @@ LIMIT ...@@ -777,6 +1485,42 @@ LIMIT
10 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 * 类型查询 ## 不建议使用 SELECT * 类型查询
* **Item:** COL.001 * **Item:** COL.001
...@@ -787,7 +1531,7 @@ LIMIT ...@@ -787,7 +1531,7 @@ LIMIT
# Query: 1E2CF4145EE706A5 # Query: 1E2CF4145EE706A5
★ ★ ★ ★ ☆ 95分 ★ ★ ★ ★ ☆ 85分
```sql ```sql
...@@ -803,29 +1547,101 @@ LIMIT ...@@ -803,29 +1547,101 @@ LIMIT
10 10
``` ```
## 不建议使用 SELECT * 类型查询 ## 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中无法利用索引完成的排序操作称为'文件排序'.
* **Item:** COL.001 ## 为sakila库的customer表添加索引
* **Severity:** L1 * **Item:** IDX.001
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 * **Severity:** L2
# Query: A314542EEE8571EE
★ ★ ★ ★ ☆ 95分 * **Case:** ALTER TABLE \`sakila\`.\`customer\` add index \`idx\_address\_id\_last\_name\` (\`address\_id\`,\`last\_name\`) ;
```sql
SELECT
*
FROM
customer
WHERE
address_id in (224, 510)
ORDER BY
last_name
```
## 不建议使用 SELECT * 类型查询 ## 不建议使用 SELECT * 类型查询
...@@ -852,21 +1668,49 @@ ORDER BY ...@@ -852,21 +1668,49 @@ ORDER BY
title title
``` ```
## 不建议使用 SELECT * 类型查询 ## Explain信息
* **Item:** COL.001 | 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 |
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 不建议使用连续判断 ### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **Item:** RES.009 * ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2 * **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_release\_year\_length\_title\` (\`release\_year\`,\`length\`,\`title\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## '!=' 运算符是非标准的 ## '!=' 运算符是非标准的
...@@ -878,7 +1722,7 @@ ORDER BY ...@@ -878,7 +1722,7 @@ ORDER BY
# Query: 4E73AA068370E6A8 # Query: 4E73AA068370E6A8
★ ★ ★ ★ ★ 100分 ★ ★ ★ ★ ☆ 90分
```sql ```sql
...@@ -890,7 +1734,39 @@ WHERE ...@@ -890,7 +1734,39 @@ WHERE
release_year = 1995 release_year = 1995
``` ```
## OK ## 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 # Query: BA7111449E4F1122
...@@ -907,13 +1783,39 @@ WHERE ...@@ -907,13 +1783,39 @@ WHERE
AND LENGTH = 70 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 |
* **Item:** RES.009
### 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 * **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_language\_id\` (\`length\`,\`language\_id\`) ;
# Query: B13E0ACEAF8F3119 # Query: B13E0ACEAF8F3119
...@@ -930,13 +1832,41 @@ WHERE ...@@ -930,13 +1832,41 @@ WHERE
AND LENGTH > 70 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 |
* **Item:** RES.009
### 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 * **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
# Query: A3FAB6027484B88B # Query: A3FAB6027484B88B
...@@ -955,21 +1885,51 @@ ORDER BY ...@@ -955,21 +1885,51 @@ ORDER BY
release_year release_year
``` ```
## 不建议使用 SELECT * 类型查询 ## Explain信息
* **Item:** COL.001 | 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 |
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 不建议使用连续判断 ### 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 子句中的其他条件去过滤这些数据行。
* **Item:** RES.009 * **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2 * **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
* **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 # Query: CB42080E9F35AB07
...@@ -988,25 +1948,55 @@ ORDER BY ...@@ -988,25 +1948,55 @@ ORDER BY
release_year release_year
``` ```
## 不建议使用 SELECT * 类型查询 ## Explain信息
* **Item:** COL.001 | 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 |
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 不建议使用连续判断 ### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Item:** RES.009 * **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 * **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
* **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 # Query: C4A212A42400411D
★ ★ ★ ★ ☆ 95分 ★ ★ ★ ★ ☆ 85分
```sql ```sql
...@@ -1020,6 +2010,42 @@ ORDER BY ...@@ -1020,6 +2010,42 @@ ORDER BY
release_year 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 * 类型查询 ## 不建议使用 SELECT * 类型查询
* **Item:** COL.001 * **Item:** COL.001
...@@ -1041,6 +2067,28 @@ FROM ...@@ -1041,6 +2067,28 @@ FROM
INNER JOIN country b ON a. country_id= b. country_id 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -1078,6 +2126,28 @@ FROM ...@@ -1078,6 +2126,28 @@ FROM
LEFT JOIN country b ON a. country_id= b. country_id 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -1115,6 +2185,28 @@ FROM ...@@ -1115,6 +2185,28 @@ FROM
RIGHT JOIN country b ON a. country_id= b. country_id 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -1141,7 +2233,7 @@ FROM ...@@ -1141,7 +2233,7 @@ FROM
# Query: 1E56C6CCEA2131CC # Query: 1E56C6CCEA2131CC
★ ★ ★ ★ ☆ 90分 ★ ★ ★ ★ ☆ 80分
```sql ```sql
...@@ -1154,6 +2246,45 @@ WHERE ...@@ -1154,6 +2246,45 @@ WHERE
b. last_update IS NULL 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -1178,20 +2309,59 @@ WHERE ...@@ -1178,20 +2309,59 @@ WHERE
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。 * **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: F5D30BCAC1E206A1 # 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
★ ★ ★ ★ ☆ 90分 * **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_last\_update\` (\`last\_update\`) ;
```sql
SELECT
*
FROM
city a
RIGHT JOIN country b ON a. country_id= b. country_id
WHERE
a. last_update IS NULL
```
## 建议使用 AS 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
...@@ -1236,6 +2406,35 @@ FROM ...@@ -1236,6 +2406,35 @@ FROM
RIGHT JOIN country b ON a. country_id= b. country_id 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -1270,7 +2469,7 @@ FROM ...@@ -1270,7 +2469,7 @@ FROM
# Query: A4911095C201896F # Query: A4911095C201896F
★ ★ ★ ★ ☆ 85分 ★ ★ ★ ☆ ☆ 65分
```sql ```sql
...@@ -1291,6 +2490,63 @@ WHERE ...@@ -1291,6 +2490,63 @@ WHERE
b. last_update IS NULL 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -1328,6 +2584,32 @@ FROM ...@@ -1328,6 +2584,32 @@ FROM
JOIN country 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -1349,6 +2631,32 @@ FROM ...@@ -1349,6 +2631,32 @@ FROM
LEFT JOIN country 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -1370,6 +2678,32 @@ FROM ...@@ -1370,6 +2678,32 @@ FROM
RIGHT JOIN country 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -1390,6 +2724,32 @@ FROM ...@@ -1390,6 +2724,32 @@ FROM
city a STRAIGHT_JOIN country b ON a. country_id= b. country_id 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -1406,24 +2766,50 @@ FROM ...@@ -1406,24 +2766,50 @@ FROM
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。 * **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
# Query: F76BFFC87914E3D5 # Query: 50F2AB4243CE2071
★ ★ ★ ☆ ☆ 60分 ★ ★ ★ ☆ ☆ 60分
```sql ```sql
SELECT SELECT
d. deptno, d. dname, d. loc a. address, a. postal_code
FROM FROM
scott. dept d sakila. address a
WHERE WHERE
d. deptno IN ( a. city_id IN (
SELECT SELECT
e. deptno c. city_id
FROM FROM
scott. emp e) 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -1448,32 +2834,78 @@ FROM ...@@ -1448,32 +2834,78 @@ FROM
* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。 * **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。
# Query: 7253A3D336F9F3FE # Query: 584CCEC8069B6947
★ ☆ ☆ ☆ ☆ 30分 ★ ☆ ☆ ☆ ☆ 30分
```sql ```sql
SELECT SELECT
visitor_id, url city
FROM FROM(
(
SELECT SELECT
id city_id
FROM FROM
LOG city
WHERE WHERE
ip= "123.45.67.89" city = "A Corua (La Corua)"
ORDER BY ORDER BY
ts desc last_update DESC
LIMIT LIMIT
50, 10) I 50, 10) I
JOIN LOG ON (I. id= LOG. id) JOIN city ON (I. city_id = city. city_id)
JOIN url ON (url. id= LOG. url_id) JOIN country ON (country. country_id = city. country_id)
ORDER BY ORDER BY
TS desc city DESC
``` ```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | *<derived2>* | NULL | ALL | NULL | NULL | NULL | NULL | 60 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary; Using filesort |
| 1 | PRIMARY | *city* | NULL | eq\_ref | PRIMARY,<br>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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -1490,14 +2922,6 @@ ORDER BY ...@@ -1490,14 +2922,6 @@ ORDER BY
* **Content:** 相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。 * **Content:** 相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。
## 用字符类型存储IP地址
* **Item:** LIT.001
* **Severity:** L2
* **Content:** 字符串字面上看起来像IP地址,但不是 INET\_ATON() 的参数,表示数据被存储为字符而不是整数。将IP地址存储为整数更为有效。
## MySQL 对子查询的优化效果不佳 ## MySQL 对子查询的优化效果不佳
* **Item:** SUB.001 * **Item:** SUB.001
...@@ -1519,6 +2943,22 @@ WHERE ...@@ -1519,6 +2943,22 @@ WHERE
city. city_id = 1 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,<br>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 * **Item:** JOI.007
...@@ -1537,7 +2977,7 @@ WHERE ...@@ -1537,7 +2977,7 @@ WHERE
# Query: F8314ABD1CBF2FF1 # Query: F8314ABD1CBF2FF1
★ ★ ★ ★ ☆ 80分 ★ ★ ★ ☆ ☆ 70分
```sql ```sql
DELETE city DELETE city
...@@ -1548,6 +2988,45 @@ WHERE ...@@ -1548,6 +2988,45 @@ WHERE
country. country IS NULL 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 * **Item:** JOI.007
...@@ -1577,6 +3056,24 @@ WHERE ...@@ -1577,6 +3056,24 @@ WHERE
a1. country_id= a2. country_id 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 * **Item:** JOI.007
...@@ -1606,6 +3103,24 @@ WHERE ...@@ -1606,6 +3103,24 @@ WHERE
a1. country_id= a2. country_id 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 * **Item:** JOI.007
...@@ -1624,7 +3139,7 @@ WHERE ...@@ -1624,7 +3139,7 @@ WHERE
# Query: F16FD63381EF8299 # Query: F16FD63381EF8299
★ ★ ★ ★ ★ 100分 ★ ★ ★ ★ ☆ 90分
```sql ```sql
...@@ -1634,6 +3149,36 @@ WHERE ...@@ -1634,6 +3149,36 @@ WHERE
LENGTH > 100 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等操作时注意备份 ## 使用DELETE/DROP/TRUNCATE等操作时注意备份
* **Item:** SEC.003 * **Item:** SEC.003
...@@ -1659,6 +3204,22 @@ WHERE ...@@ -1659,6 +3204,22 @@ WHERE
city. city_id= 10 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,<br>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 * **Item:** JOI.007
...@@ -1685,6 +3246,33 @@ WHERE ...@@ -1685,6 +3246,33 @@ WHERE
city. city_id= 10 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,<br>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 * **Item:** JOI.007
...@@ -1695,7 +3283,7 @@ WHERE ...@@ -1695,7 +3283,7 @@ WHERE
# Query: FCD1ABF36F8CDAD7 # Query: FCD1ABF36F8CDAD7
★ ★ ★ ★ ☆ 90分 ★ ★ ★ ★ ★ 100分
```sql ```sql
...@@ -1710,13 +3298,21 @@ WHERE ...@@ -1710,13 +3298,21 @@ WHERE
AND city. city_id= 10 AND city. city_id= 10
``` ```
## 不建议使用连续判断 ## Explain信息
* **Item:** RES.009 | id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | UPDATE | *city* | NULL | const | PRIMARY,<br>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 |
* **Severity:** L2
* **Content:** 类似这样的 SELECT \* FROM tbl WHERE col = col = 'abc' 语句可能是书写错误,您可能想表达的含义是 col = 'abc'。如果确实是业务需求建议修改为 col = col and col = 'abc'。
### Explain信息解读
#### Type信息解读
* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.
# Query: FE409EB794EE91CF # Query: FE409EB794EE91CF
...@@ -1732,7 +3328,24 @@ WHERE ...@@ -1732,7 +3328,24 @@ WHERE
language_id = 20 language_id = 20
``` ```
## OK ## 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 # Query: 3656B13CC4F888E2
...@@ -1746,6 +3359,32 @@ FROM ...@@ -1746,6 +3359,32 @@ FROM
country 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -1774,7 +3413,20 @@ VALUES ...@@ -1774,7 +3413,20 @@ VALUES
(3) (3)
``` ```
## OK ## 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 # Query: 11EC7AAACC97DC0F
...@@ -1788,6 +3440,21 @@ FROM ...@@ -1788,6 +3440,21 @@ FROM
DUAL 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 加锁粒度较大请谨慎 ## INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Item:** LCK.001 * **Item:** LCK.001
...@@ -1808,6 +3475,32 @@ FROM ...@@ -1808,6 +3475,32 @@ FROM
country 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -1836,7 +3529,20 @@ VALUES ...@@ -1836,7 +3529,20 @@ VALUES
(3) (3)
``` ```
## OK ## 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 # Query: A7973BDD268F926E
...@@ -1850,6 +3556,21 @@ FROM ...@@ -1850,6 +3556,21 @@ FROM
DUAL 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 加锁粒度较大请谨慎 ## INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Item:** LCK.001 * **Item:** LCK.001
...@@ -1950,6 +3671,29 @@ FROM ...@@ -1950,6 +3671,29 @@ FROM
) 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -1986,6 +3730,36 @@ LIMIT ...@@ -1986,6 +3730,36 @@ LIMIT
1) 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -2037,6 +3811,33 @@ FROM ...@@ -2037,6 +3811,33 @@ FROM
RIGHT JOIN country o ON i. city_id= o. country_id 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -2094,6 +3895,17 @@ GROUP BY ...@@ -2094,6 +3895,17 @@ GROUP BY
first_name 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 条件 ## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008 * **Item:** CLA.008
...@@ -2118,14 +3930,6 @@ GROUP BY ...@@ -2118,14 +3930,6 @@ GROUP BY
* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。 * **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 对子查询的优化效果不佳 ## MySQL 对子查询的优化效果不佳
* **Item:** SUB.001 * **Item:** SUB.001
...@@ -2157,6 +3961,39 @@ WHERE ...@@ -2157,6 +3961,39 @@ WHERE
i. city_id is null 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -2193,6 +4030,32 @@ FROM ...@@ -2193,6 +4030,32 @@ FROM
customer STRAIGHT_JOIN address ON customer. address_id= address. address_id 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -2203,7 +4066,7 @@ FROM ...@@ -2203,7 +4066,7 @@ FROM
# Query: E48A20D0413512DA # Query: E48A20D0413512DA
★ ☆ ☆ ☆ ☆ 30分 ★ ☆ ☆ ☆ ☆ 20分
```sql ```sql
...@@ -2227,6 +4090,65 @@ ORDER BY ...@@ -2227,6 +4090,65 @@ ORDER BY
phone desc 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,<br>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,<br>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 | *<derived2>* | NULL | ref | <auto\_key0> | <auto\_key0> | 152 | sakila.a.address | 6 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |
| 2 | DERIVED | *a* | NULL | ALL | PRIMARY,<br>idx\_fk\_city\_id | NULL | NULL | NULL | 603 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort |
| 2 | DERIVED | *cu* | NULL | ref | idx\_fk\_store\_id,<br>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,<br>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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -2281,6 +4203,29 @@ WHERE ...@@ -2281,6 +4203,29 @@ WHERE
DATE( last_update) = '2006-02-15' 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 * 类型查询 ## 不建议使用 SELECT * 类型查询
* **Item:** COL.001 * **Item:** COL.001
...@@ -2349,6 +4294,29 @@ ORDER BY ...@@ -2349,6 +4294,29 @@ ORDER BY
DATE( last_update) 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 条件 ## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001 * **Item:** CLA.001
...@@ -2367,7 +4335,7 @@ ORDER BY ...@@ -2367,7 +4335,7 @@ ORDER BY
# Query: 1ED2B7ECBA4215E1 # Query: 1ED2B7ECBA4215E1
★ ★ ★ ★ ☆ 90分 ★ ★ ★ ★ ☆ 80分
```sql ```sql
...@@ -2383,6 +4351,42 @@ GROUP BY ...@@ -2383,6 +4351,42 @@ GROUP BY
description 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 条件 ## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008 * **Item:** CLA.008
...@@ -2428,7 +4432,7 @@ ADD ...@@ -2428,7 +4432,7 @@ ADD
# Query: 9BB74D074BA0727C # Query: 9BB74D074BA0727C
★ ★ ★ ★ ★ 100分 ★ ★ ★ ★ ☆ 90分
```sql ```sql
...@@ -2445,6 +4449,17 @@ ADD ...@@ -2445,6 +4449,17 @@ ADD
`store_id`, `film_id`) `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 * **Item:** KEY.004
...@@ -2453,31 +4468,57 @@ ADD ...@@ -2453,31 +4468,57 @@ ADD
* **Content:** 如果为列创建复合索引,请确保查询属性与索引属性的顺序相同,以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐,那么DBMS可能无法在查询处理期间使用索引。 * **Content:** 如果为列创建复合索引,请确保查询属性与索引属性的顺序相同,以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐,那么DBMS可能无法在查询处理期间使用索引。
# Query: CE8A69541550D286 # Query: C95B5C028C8FFF95
★ ☆ ☆ ☆ ☆ 30分 ★ ☆ ☆ ☆ ☆ 30分
```sql ```sql
SELECT SELECT
DATE_FORMAT( t. atm, '%Y-%m-%d' DATE_FORMAT( t. last_update, '%Y-%m-%d'
), ),
COUNT( DISTINCT ( COUNT( DISTINCT (
t. usr)) t. city))
FROM FROM
usr_terminal t city t
WHERE WHERE
t. atm > '2018-10-22 00:00:00' t. last_update > '2018-10-22 00:00:00'
AND t. agent LIKE '%Chrome%' AND t. city LIKE '%Chrome%'
AND t. system = 'eip' AND t. city = 'eip'
GROUP BY GROUP BY
DATE_FORMAT( t. atm, '%Y-%m-%d' DATE_FORMAT( t. last_update, '%Y-%m-%d'
) )
ORDER BY ORDER BY
DATE_FORMAT( t. atm, '%Y-%m-%d' 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 关键字显示声明一个别名 ## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001 * **Item:** ALI.001
...@@ -2518,14 +4559,6 @@ ORDER BY ...@@ -2518,14 +4559,6 @@ ORDER BY
* **Content:** 在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。 * **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 # Query: C11ECE7AE5F80CE5
★ ★ ☆ ☆ ☆ 45分 ★ ★ ☆ ☆ ☆ 45分
...@@ -2608,5 +4641,3 @@ DROP ...@@ -2608,5 +4641,3 @@ DROP
DEFAULT DEFAULT
``` ```
## OK
# 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:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
#!/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 ]
}
# 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
```
文件已添加
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 $?
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册