# Query: C3FAEDA6AD6D762B
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH = 86
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: E969B9297DA79BA6
★ ★ ★ ★ ☆ 80分
```sql
SELECT
*
FROM
film
WHERE
LENGTH IS NULL
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断
* **Item:** ARG.006
* **Severity:** L1
* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 8A106444D14B9880
★ ★ ★ ☆ ☆ 60分
```sql
SELECT
*
FROM
film
HAVING
title = 'abc'
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## 不建议使用 HAVING 子句
* **Item:** CLA.013
* **Severity:** L3
* **Content:** 将查询的 HAVING 子句改写为 WHERE 中的查询条件,可以在查询处理期间使用索引。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: A0C5E62C724A121A
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
sakila. film
WHERE
LENGTH >= 60
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 868317D1973FD1B0
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH BETWEEN 60
AND 84
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 707FE669669FA075
★ ★ ★ ★ ☆ 95分
```sql
SELECT
*
FROM
film
WHERE
title LIKE 'AIR%'
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | range | idx\_title | idx\_title | 767 | NULL | 2 | ☠️ **100.00%** | O(log n)+ | Using index condition |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素.
#### Extra信息解读
* **Using index condition**: 在5.6版本后加入的新特性(Index Condition Pushdown)。Using index condition 会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: DF916439ABD07664
★ ★ ★ ★ ☆ 90分
```sql
SELECT
*
FROM
film
WHERE
title IS NOT NULL
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断
* **Item:** ARG.006
* **Severity:** L1
* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: B9336971FF3D3792
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH = 114
AND title = 'ALABAMA DEVIL'
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ref | idx\_title | idx\_title | 767 | const | 1 | n% | O(log n) | Using where |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列title添加索引,散粒度为: n%; 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\` (\`title\`,\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 68E48001ECD53152
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH > 100
AND title = 'ALABAMA DEVIL'
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ref | idx\_title | idx\_title | 767 | const | 1 | n% | O(log n) | Using where |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列title添加索引,散粒度为: n%; 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\` (\`title\`,\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 12FF1DAA3D425FA9
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH > 100
AND language_id < 10
AND title = 'xyz'
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ref | idx\_title,
idx\_fk\_language\_id | idx\_title | 767 | const | 1 | n% | O(log n) | Using where |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列title添加索引,散粒度为: n%; 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\` (\`title\`,\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: E84CBAAC2E12BDEA
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH > 100
AND language_id < 10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | idx\_fk\_language\_id | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 6A0F035BD4E01018
★ ★ ★ ☆ ☆ 75分
```sql
SELECT
release_year, SUM( LENGTH)
FROM
film
WHERE
LENGTH = 123
AND language_id = 1
GROUP BY
release_year
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | idx\_fk\_language\_id | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%; 为列language\_id添加索引,散粒度为: n%; 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_language\_id\_release\_year\` (\`length\`,\`language\_id\`,\`release\_year\`) ;
## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
## 使用 SUM(COL) 时需注意 NPE 问题
* **Item:** FUN.006
* **Severity:** L1
* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl
# Query: 23D176AEA2947002
★ ★ ★ ☆ ☆ 75分
```sql
SELECT
release_year, SUM( LENGTH)
FROM
film
WHERE
LENGTH >= 123
GROUP BY
release_year
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%; 为列release\_year添加索引,散粒度为: n%;
* **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
* **Content:** 为列release\_year添加索引,散粒度为: n%; 为列language\_id添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_release\_year\_language\_id\` (\`release\_year\`,\`language\_id\`) ;
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
## 使用 SUM(COL) 时需注意 NPE 问题
* **Item:** FUN.006
* **Severity:** L1
* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl
# Query: B3C502B4AA344196
★ ★ ★ ☆ ☆ 65分
```sql
SELECT
release_year, SUM( LENGTH)
FROM
film
WHERE
LENGTH = 123
GROUP BY
release_year, (LENGTH+ language_id)
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
## GROUP BY 的条件为表达式
* **Item:** CLA.010
* **Severity:** L2
* **Content:** 当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。
## 使用 SUM(COL) 时需注意 NPE 问题
* **Item:** FUN.006
* **Severity:** L1
* **Content:** 当某一列的值全是 NULL 时,COUNT(COL) 的返回结果为0,但 SUM(COL) 的返回结果为 NULL,因此使用 SUM() 时需注意 NPE 问题。可以使用如下方式来避免 SUM 的 NPE 问题: SELECT IF(ISNULL(SUM(COL)), 0, SUM(COL)) FROM tbl
# Query: 47044E1FE1A965A5
★ ★ ★ ☆ ☆ 60分
```sql
SELECT
release_year, SUM( film_id)
FROM
film
GROUP BY
release_year
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_release\_year\` (\`release\_year\`) ;
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
# Query: 2BA1217F6C8CF0AB
★ ☆ ☆ ☆ ☆ 35分
```sql
SELECT
*
FROM
address
GROUP BY
address, district
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *address* | NULL | ALL | NULL | NULL | NULL | NULL | 603 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.
## 为sakila库的address表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列address添加索引,散粒度为: n%; 为列district添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`address\` add index \`idx\_address\_district\` (\`address\`,\`district\`) ;
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 非确定性的 GROUP BY
* **Item:** RES.001
* **Severity:** L4
* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。
# Query: 863A85207E4F410D
★ ★ ★ ★ ☆ 80分
```sql
SELECT
title
FROM
film
WHERE
ABS( language_id) = 3
GROUP BY
title
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | index | idx\_title | idx\_title | 767 | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
## 避免在 WHERE 条件中使用函数或其他运算符
* **Item:** FUN.001
* **Severity:** L2
* **Content:** 虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰。
# Query: DF59FD602E4AA368
★ ★ ★ ☆ ☆ 70分
```sql
SELECT
language_id
FROM
film
WHERE
LENGTH = 123
GROUP BY
release_year
ORDER BY
language_id
```
## 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 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%; 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ;
## 非确定性的 GROUP BY
* **Item:** RES.001
* **Severity:** L4
* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。
# Query: F6DBEAA606D800FC
★ ★ ★ ★ ☆ 90分
```sql
SELECT
release_year
FROM
film
WHERE
LENGTH = 123
GROUP BY
release_year
ORDER BY
release_year
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%; 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ;
# Query: 6E9B96CA3F0E6BDA
★ ★ ☆ ☆ ☆ 55分
```sql
SELECT
*
FROM
film
WHERE
LENGTH = 123
ORDER BY
release_year ASC, language_id DESC
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引
* **Item:** CLA.007
* **Severity:** L2
* **Content:** ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## ORDER BY 多个列但排序方向不同时可能无法使用索引
* **Item:** KEY.008
* **Severity:** L4
* **Content:** 在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。
# Query: 2EAACFD7030EA528
★ ★ ★ ★ ☆ 90分
```sql
SELECT
release_year
FROM
film
WHERE
LENGTH = 123
GROUP BY
release_year
ORDER BY
release_year
LIMIT
10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%; 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ;
# Query: 5CE2F187DBF2A710
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH = 123
ORDER BY
release_year
LIMIT
10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%; 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: E75234155B5E2E14
★ ★ ★ ☆ ☆ 75分
```sql
SELECT
*
FROM
film
ORDER BY
release_year
LIMIT
10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: AFEEBF10A8D74E32
★ ★ ★ ★ ☆ 80分
```sql
SELECT
film_id
FROM
film
ORDER BY
release_year
LIMIT
10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
# Query: 965D5AC955824512
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH > 100
ORDER BY
LENGTH
LIMIT
10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 1E2CF4145EE706A5
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH < 100
ORDER BY
LENGTH
LIMIT
10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%;
* **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(log n)+ | Using index condition; Using filesort |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素.
#### Extra信息解读
* **Using index condition**: 在5.6版本后加入的新特性(Index Condition Pushdown)。Using index condition 会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行。
* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.
## 为sakila库的customer表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列address\_id添加索引,散粒度为: n%; 为列last\_name添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`customer\` add index \`idx\_address\_id\_last\_name\` (\`address\_id\`,\`last\_name\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 0BE2D79E2F1E7CB0
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
release_year = 2016
AND LENGTH != 1
ORDER BY
title
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列release\_year添加索引,散粒度为: n%; 为列length添加索引,散粒度为: n%; 为列title添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_release\_year\_length\_title\` (\`release\_year\`,\`length\`,\`title\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## '!=' 运算符是非标准的
* **Item:** STA.001
* **Severity:** L0
* **Content:** "<>"才是标准SQL中的不等于运算符。
# Query: 4E73AA068370E6A8
★ ★ ★ ★ ☆ 90分
```sql
SELECT
title
FROM
film
WHERE
release_year = 1995
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_release\_year\` (\`release\_year\`) ;
# Query: BA7111449E4F1122
★ ★ ★ ★ ☆ 90分
```sql
SELECT
title, replacement_cost
FROM
film
WHERE
language_id = 5
AND LENGTH = 70
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ref | idx\_fk\_language\_id | idx\_fk\_language\_id | 1 | const | 1 | n% | O(log n) | Using where |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列length添加索引,散粒度为: n%; 为列language\_id添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_language\_id\` (\`length\`,\`language\_id\`) ;
# Query: B13E0ACEAF8F3119
★ ★ ★ ★ ☆ 90分
```sql
SELECT
title
FROM
film
WHERE
language_id > 5
AND LENGTH > 70
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | range | idx\_fk\_language\_id | idx\_fk\_language\_id | 1 | NULL | 1 | n% | O(log n)+ | Using index condition; Using where |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素.
#### Extra信息解读
* **Using index condition**: 在5.6版本后加入的新特性(Index Condition Pushdown)。Using index condition 会先条件过滤索引,过滤完索引后找到所有符合索引条件的数据行,随后用 WHERE 子句中的其他条件去过滤这些数据行。
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
# Query: A3FAB6027484B88B
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH = 100
AND title = 'xyz'
ORDER BY
release_year
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ref | idx\_title | idx\_title | 767 | const | 1 | n% | O(log n) | Using where; Using filesort |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列title添加索引,散粒度为: n%; 为列length添加索引,散粒度为: n%; 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\_release\_year\` (\`title\`,\`length\`,\`release\_year\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: CB42080E9F35AB07
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH > 100
AND title = 'xyz'
ORDER BY
release_year
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ref | idx\_title | idx\_title | 767 | const | 1 | n% | O(log n) | Using where; Using filesort |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列title添加索引,散粒度为: n%; 为列length添加索引,散粒度为: n%; 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_title\_length\_release\_year\` (\`title\`,\`length\`,\`release\_year\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: C4A212A42400411D
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
LENGTH > 100
ORDER BY
release_year
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列length添加索引,散粒度为: n%; 为列release\_year添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\_release\_year\` (\`length\`,\`release\_year\`) ;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 4ECCA9568BE69E68
★ ★ ★ ★ ☆ 95分
```sql
SELECT
*
FROM
city a
INNER JOIN country b ON a. country_id= b. country_id
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *b* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 485D56FC88BBBDB9
★ ★ ★ ★ ☆ 95分
```sql
SELECT
*
FROM
city a
LEFT JOIN country b ON a. country_id= b. country_id
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 0D0DABACEDFF5765
★ ★ ★ ★ ☆ 95分
```sql
SELECT
*
FROM
city a
RIGHT JOIN country b ON a. country_id= b. country_id
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 1E56C6CCEA2131CC
★ ★ ★ ★ ☆ 80分
```sql
SELECT
*
FROM
city a
LEFT JOIN country b ON a. country_id= b. country_id
WHERE
b. last_update IS NULL
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | n% | O(log 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
* **Content:** 为列last\_update添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`country\` add index \`idx\_last\_update\` (\`last\_update\`) ;
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断
* **Item:** ARG.006
* **Severity:** L1
* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: F5D30BCAC1E206A1
★ ★ ★ ★ ☆ 80分
```sql
SELECT
*
FROM
city a
RIGHT JOIN country b ON a. country_id= b. country_id
WHERE
a. last_update IS NULL
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | n% | O(log 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
* **Content:** 为列last\_update添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_last\_update\` (\`last\_update\`) ;
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 应尽量避免在 WHERE 子句中对字段进行 NULL 值判断
* **Item:** ARG.006
* **Severity:** L1
* **Content:** 使用 IS NULL 或 IS NOT NULL 将可能导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null;可以在num上设置默认值0,确保表中 num 列没有 NULL 值,然后这样查询: select id from t where num=0;
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 17D5BCF21DC2364C
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
city a
LEFT JOIN country b ON a. country_id= b. country_id
UNION
SELECT
*
FROM
city a
RIGHT JOIN country b ON a. country_id= b. country_id
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | *a* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | PRIMARY | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | ☠️ **100.00%** | O(log 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(log n) | NULL |
| 2 | UNION | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |
### Explain信息解读
#### SelectType信息解读
* **PRIMARY**: 最外层的select.
* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集.
#### Type信息解读
* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION
* **Item:** SUB.002
* **Severity:** L2
* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。
# Query: A4911095C201896F
★ ★ ★ ☆ ☆ 65分
```sql
SELECT
*
FROM
city a
RIGHT JOIN country b ON a. country_id= b. country_id
WHERE
a. last_update IS NULL
UNION
SELECT
*
FROM
city a
LEFT JOIN country b ON a. country_id= b. country_id
WHERE
b. last_update IS NULL
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | *b* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | PRIMARY | *a* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.b.country\_id | 5 | n% | O(log 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 | n% | O(log n) | Using where; Not exists |
| 2 | UNION | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | n% | O(log 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
* **Content:** 为列last\_update添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_last\_update\` (\`last\_update\`) ;
## 为sakila库的country表添加索引
* **Item:** IDX.002
* **Severity:** L2
* **Content:** 为列last\_update添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`country\` add index \`idx\_last\_update\` (\`last\_update\`) ;
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION
* **Item:** SUB.002
* **Severity:** L2
* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。
# Query: 3FF20E28EC9CBEF9
★ ★ ★ ★ ★ 100分
```sql
SELECT
country_id, last_update
FROM
city NATURAL
JOIN country
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *country* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *city* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.country.country\_id | 5 | n% | O(log 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, 则该查询可能是有问题的.
# Query: 5C547F08EADBB131
★ ★ ★ ★ ★ 100分
```sql
SELECT
country_id, last_update
FROM
city NATURAL
LEFT JOIN country
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *country* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\_id | 1 | ☠️ **100.00%** | O(log 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, 则该查询可能是有问题的.
# Query: AF0C1EB58B23D2FA
★ ★ ★ ★ ★ 100分
```sql
SELECT
country_id, last_update
FROM
city NATURAL
RIGHT JOIN country
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *country* | NULL | ALL | NULL | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *city* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.country.country\_id | 5 | ☠️ **100.00%** | O(log 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, 则该查询可能是有问题的.
# Query: 626571EAE84E2C8A
★ ★ ★ ★ ★ 100分
```sql
SELECT
a. country_id, a. last_update
FROM
city a STRAIGHT_JOIN country b ON a. country_id= b. country_id
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *a* | NULL | ALL | idx\_fk\_country\_id | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *b* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.a.country\_id | 1 | ☠️ **100.00%** | O(log n) | Using index |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
# Query: 50F2AB4243CE2071
★ ★ ★ ☆ ☆ 60分
```sql
SELECT
a. address, a. postal_code
FROM
sakila. address a
WHERE
a. city_id IN (
SELECT
c. city_id
FROM
sakila. city c)
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *c* | NULL | index | PRIMARY | idx\_fk\_country\_id | 2 | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |
| 1 | SIMPLE | *a* | NULL | ref | idx\_fk\_city\_id | idx\_fk\_city\_id | 2 | sakila.c.city\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## MySQL 对子查询的优化效果不佳
* **Item:** SUB.001
* **Severity:** L4
* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。
# Query: 584CCEC8069B6947
★ ★ ☆ ☆ ☆ 50分
```sql
SELECT
city
FROM(
SELECT
city_id
FROM
city
WHERE
city = "A Corua (La Corua)"
ORDER BY
last_update DESC
LIMIT
50, 10) I
JOIN city ON (I. city_id = city. city_id)
JOIN country ON (country. country_id = city. country_id)
ORDER BY
city DESC
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | ** | NULL | ALL | NULL | NULL | NULL | NULL | 60 | ☠️ **100.00%** | ☠️ **O(n)** | Using temporary; Using filesort |
| 1 | PRIMARY | *city* | NULL | eq\_ref | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | I.city\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |
| 1 | PRIMARY | *country* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\_id | 1 | ☠️ **100.00%** | O(log n) | Using index |
| 2 | DERIVED | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | n% | ☠️ **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
* **Content:** 为列city添加索引,散粒度为: n%; 为列last\_update添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_city\_last\_update\` (\`city\`,\`last\_update\`) ;
## 同一张表被连接两次
* **Item:** JOI.002
* **Severity:** L4
* **Content:** 相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。
## MySQL 对子查询的优化效果不佳
* **Item:** SUB.001
* **Severity:** L4
* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。
# Query: 7F02E23D44A38A6D
★ ★ ★ ★ ☆ 80分
```sql
DELETE city, country
FROM
city
INNER JOIN country using (country_id)
WHERE
city. city_id = 1
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | DELETE | *city* | NULL | const | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |
| 1 | DELETE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |
### Explain信息解读
#### Type信息解读
* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.
## 不建议使用联表删除或更新
* **Item:** JOI.007
* **Severity:** L4
* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。
## 使用DELETE/DROP/TRUNCATE等操作时注意备份
* **Item:** SEC.003
* **Severity:** L0
* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。
# Query: F8314ABD1CBF2FF1
★ ★ ★ ☆ ☆ 70分
```sql
DELETE city
FROM
city
LEFT JOIN country ON city. country_id = country. country_id
WHERE
country. country IS NULL
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | DELETE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *country* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\_id | 1 | n% | O(log 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
* **Content:** 为列country添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`country\` add index \`idx\_country\` (\`country\`) ;
## 不建议使用联表删除或更新
* **Item:** JOI.007
* **Severity:** L4
* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。
## 使用DELETE/DROP/TRUNCATE等操作时注意备份
* **Item:** SEC.003
* **Severity:** L0
* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。
# Query: 1A53649C43122975
★ ★ ★ ★ ☆ 80分
```sql
DELETE a1, a2
FROM
city AS a1
INNER JOIN country AS a2
WHERE
a1. country_id= a2. country_id
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | DELETE | *a2* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | DELETE | *a1* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.a2.country\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |
### Explain信息解读
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## 不建议使用联表删除或更新
* **Item:** JOI.007
* **Severity:** L4
* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。
## 使用DELETE/DROP/TRUNCATE等操作时注意备份
* **Item:** SEC.003
* **Severity:** L0
* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。
# Query: B862978586C6338B
★ ★ ★ ★ ☆ 80分
```sql
DELETE FROM
a1, a2 USING city AS a1
INNER JOIN country AS a2
WHERE
a1. country_id= a2. country_id
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | DELETE | *a2* | NULL | ALL | PRIMARY | NULL | NULL | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | DELETE | *a1* | NULL | ref | idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.a2.country\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |
### Explain信息解读
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## 不建议使用联表删除或更新
* **Item:** JOI.007
* **Severity:** L4
* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。
## 使用DELETE/DROP/TRUNCATE等操作时注意备份
* **Item:** SEC.003
* **Severity:** L0
* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。
# Query: F16FD63381EF8299
★ ★ ★ ★ ☆ 90分
```sql
DELETE FROM
film
WHERE
LENGTH > 100
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | DELETE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where |
### Explain信息解读
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列length添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_length\` (\`length\`) ;
## 使用DELETE/DROP/TRUNCATE等操作时注意备份
* **Item:** SEC.003
* **Severity:** L0
* **Content:** 在执行高危操作之前对数据进行备份是十分有必要的。
# Query: 08CFE41C7D20AAC8
★ ★ ★ ★ ☆ 80分
```sql
UPDATE
city
INNER JOIN country USING( country_id)
SET
city. city = 'Abha',
city. last_update = '2006-02-15 04:45:25',
country. country = 'Afghanistan'
WHERE
city. city_id= 10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | UPDATE | *city* | NULL | const | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |
| 1 | UPDATE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |
### Explain信息解读
#### Type信息解读
* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.
## 不建议使用联表删除或更新
* **Item:** JOI.007
* **Severity:** L4
* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。
# Query: C15BDF2C73B5B7ED
★ ★ ★ ★ ☆ 80分
```sql
UPDATE
city
INNER JOIN country ON city. country_id = country. country_id
INNER JOIN address ON city. city_id = address. city_id
SET
city. city = 'Abha',
city. last_update = '2006-02-15 04:45:25',
country. country = 'Afghanistan'
WHERE
city. city_id= 10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | UPDATE | *city* | NULL | const | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |
| 1 | UPDATE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |
| 1 | SIMPLE | *address* | NULL | ref | idx\_fk\_city\_id | idx\_fk\_city\_id | 2 | const | 1 | ☠️ **100.00%** | O(log n) | Using index |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.
## 不建议使用联表删除或更新
* **Item:** JOI.007
* **Severity:** L4
* **Content:** 当需要同时删除或更新多张表时建议使用简单语句,一条 SQL 只删除或更新一张表,尽量不要将多张表的操作在同一条语句。
# Query: FCD1ABF36F8CDAD7
★ ★ ★ ★ ★ 100分
```sql
UPDATE
city, country
SET
city. city = 'Abha',
city. last_update = '2006-02-15 04:45:25',
country. country = 'Afghanistan'
WHERE
city. country_id = country. country_id
AND city. city_id= 10
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | UPDATE | *city* | NULL | const | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |
| 1 | UPDATE | *country* | NULL | const | PRIMARY | PRIMARY | 2 | const | 1 | ☠️ **100.00%** | O(1) | NULL |
### Explain信息解读
#### Type信息解读
* **const**: const用于使用常数值比较PRIMARY KEY时, 当查询的表仅有一行时, 使用system. 例:SELECT * FROM tbl WHERE col = 1.
# Query: FE409EB794EE91CF
★ ★ ★ ★ ★ 100分
```sql
UPDATE
film
SET
LENGTH = 10
WHERE
language_id = 20
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | UPDATE | *film* | NULL | range | idx\_fk\_language\_id | idx\_fk\_language\_id | 1 | const | 1 | ☠️ **100.00%** | O(log n)+ | Using where |
### Explain信息解读
#### Type信息解读
* **range**: 只检索给定范围的行, 使用一个索引来选择行. key列显示使用了哪个索引. key_len包含所使用索引的最长关键元素.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
# Query: 3656B13CC4F888E2
★ ★ ★ ☆ ☆ 65分
```sql
INSERT INTO city (country_id)
SELECT
country_id
FROM
country
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | INSERT | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *country* | NULL | index | NULL | PRIMARY | 2 | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Item:** LCK.001
* **Severity:** L3
* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎
# Query: 2F7439623B712317
★ ★ ★ ★ ★ 100分
```sql
INSERT INTO city (country_id)
VALUES
(1),
(2),
(3)
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | INSERT | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |
### Explain信息解读
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
# Query: 11EC7AAACC97DC0F
★ ★ ★ ★ ☆ 85分
```sql
INSERT INTO city (country_id)
SELECT
10
FROM
DUAL
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | INSERT | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |
### Explain信息解读
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Item:** LCK.001
* **Severity:** L3
* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎
# Query: E3DDA1A929236E72
★ ★ ★ ☆ ☆ 65分
```sql
REPLACE INTO city (country_id)
SELECT
country_id
FROM
country
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | REPLACE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *country* | NULL | index | NULL | PRIMARY | 2 | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Item:** LCK.001
* **Severity:** L3
* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎
# Query: 466F1AC2F5851149
★ ★ ★ ★ ★ 100分
```sql
REPLACE INTO city (country_id)
VALUES
(1),
(2),
(3)
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | REPLACE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |
### Explain信息解读
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
# Query: A7973BDD268F926E
★ ★ ★ ★ ☆ 85分
```sql
REPLACE INTO city (country_id)
SELECT
10
FROM
DUAL
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | REPLACE | *city* | NULL | ALL | NULL | NULL | NULL | NULL | 0 | n% | ☠️ **O(n)** | NULL |
### Explain信息解读
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## INSERT INTO xx SELECT 加锁粒度较大请谨慎
* **Item:** LCK.001
* **Severity:** L3
* **Content:** INSERT INTO xx SELECT 加锁粒度较大请谨慎
# Query: 105C870D5DFB6710
★ ★ ★ ☆ ☆ 65分
```sql
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
(
SELECT
film_id
FROM
film
) film
) film
) film
) film
) film
) film
) film
) film
) film
) film
) film
) film
) film
) film
) film
) film
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | index | NULL | idx\_fk\_language\_id | 1 | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.
#### Extra信息解读
* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## 执行计划中嵌套连接深度过深
* **Item:** SUB.004
* **Severity:** L3
* **Content:** MySQL对子查询的优化效果不佳,MySQL将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。
# Query: 16C2B14E7DAA9906
★ ☆ ☆ ☆ ☆ 35分
```sql
SELECT
*
FROM
film
WHERE
language_id = (
SELECT
language_id
FROM
language
LIMIT
1)
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | *film* | NULL | ALL | idx\_fk\_language\_id | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where |
| 2 | SUBQUERY | *language* | NULL | index | NULL | PRIMARY | 1 | NULL | 6 | ☠️ **100.00%** | ☠️ **O(n)** | Using index |
### Explain信息解读
#### SelectType信息解读
* **PRIMARY**: 最外层的select.
* **SUBQUERY**: 子查询中的第一个SELECT查询, 不依赖于外部查询的结果集.
#### Type信息解读
* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 未使用 ORDER BY 的 LIMIT 查询
* **Item:** RES.002
* **Severity:** L4
* **Content:** 没有 ORDER BY 的 LIMIT 会导致非确定性的结果,这取决于查询执行计划。
## MySQL 对子查询的优化效果不佳
* **Item:** SUB.001
* **Severity:** L4
* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。
# Query: 16CB4628D2597D40
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
city i
LEFT JOIN country o ON i. city_id= o. country_id
UNION
SELECT
*
FROM
city i
RIGHT JOIN country o ON i. city_id= o. country_id
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | *i* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | PRIMARY | *o* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.i.city\_id | 1 | ☠️ **100.00%** | O(log 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(log n) | NULL |
| 2 | UNION | *i* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |
### Explain信息解读
#### SelectType信息解读
* **PRIMARY**: 最外层的select.
* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集.
#### Type信息解读
* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION
* **Item:** SUB.002
* **Severity:** L2
* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。
# Query: EA50643B01E139A8
★ ☆ ☆ ☆ ☆ 35分
```sql
SELECT
*
FROM
(
SELECT
*
FROM
actor
WHERE
last_update= '2006-02-15 04:34:33'
AND last_name= 'CHASE'
) t
WHERE
last_update= '2006-02-15 04:34:33'
AND last_name= 'CHASE'
GROUP BY
first_name
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *actor* | NULL | ref | idx\_actor\_last\_name | idx\_actor\_last\_name | 137 | const | 2 | n% | O(log n) | Using where; Using temporary |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
* ☠️ **Using temporary**: 表示MySQL在对查询结果排序时使用临时表. 常见于排序order by和分组查询group by.
## 为sakila库的actor表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列last\_name添加索引,散粒度为: n%; 为列last\_update添加索引,散粒度为: n%; 为列first\_name添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`actor\` add index \`idx\_last\_name\_last\_update\_first\_name\` (\`last\_name\`,\`last\_update\`,\`first\_name\`) ;
## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 非确定性的 GROUP BY
* **Item:** RES.001
* **Severity:** L4
* **Content:** SQL返回的列既不在聚合函数中也不是 GROUP BY 表达式的列中,因此这些值的结果将是非确定性的。如:select a, b, c from tbl where foo="bar" group by a,该 SQL 返回的结果就是不确定的。
## MySQL 对子查询的优化效果不佳
* **Item:** SUB.001
* **Severity:** L4
* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。
# Query: 7598A4EDE6CFA6BE
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
city i
LEFT JOIN country o ON i. city_id= o. country_id
WHERE
o. country_id is null
UNION
SELECT
*
FROM
city i
RIGHT JOIN country o ON i. city_id= o. country_id
WHERE
i. city_id is null
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | *i* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | PRIMARY | *o* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.i.city\_id | 1 | ☠️ **100.00%** | O(log 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(log n) | Using where; Not exists |
| 2 | UNION | *i* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.o.country\_id | 1 | ☠️ **100.00%** | O(log n) | Using where; Not exists |
### Explain信息解读
#### SelectType信息解读
* **PRIMARY**: 最外层的select.
* **UNION**: UNION中的第二个或后面的SELECT查询, 不依赖于外部查询的结果集.
#### Type信息解读
* **eq_ref**: 除const类型外最好的可能实现的连接类型. 它用在一个索引的所有部分被连接使用并且索引是UNIQUE或PRIMARY KEY, 对于每个索引键, 表中只有一条记录与之匹配. 例: 'SELECT * FROM RefTbl, tbl WHERE RefTbl.col=tbl.col;'.
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Not exists**: MySQL能够对LEFT JOIN查询进行优化, 并且在查找到符合LEFT JOIN条件的行后, 则不再查找更多的行.
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 如果您不在乎重复的话,建议使用 UNION ALL 替代 UNION
* **Item:** SUB.002
* **Severity:** L2
* **Content:** 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,那么使用UNION ALL将是一个更快的选项。
# Query: 1E8B70E30062FD13
★ ★ ★ ★ ★ 100分
```sql
SELECT
first_name, last_name, email
FROM
customer STRAIGHT_JOIN address ON customer. address_id= address. address_id
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *customer* | NULL | ALL | idx\_fk\_address\_id | NULL | NULL | NULL | 599 | ☠️ **100.00%** | ☠️ **O(n)** | NULL |
| 1 | SIMPLE | *address* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.customer.address\_id | 1 | ☠️ **100.00%** | O(log 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**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.
# Query: E48A20D0413512DA
★ ★ ☆ ☆ ☆ 40分
```sql
SELECT
ID, name
FROM
(
SELECT
address
FROM
customer_list
WHERE
SID= 1
ORDER BY
phone
LIMIT
50, 10) a
JOIN customer_list l ON (a. address= l. address)
JOIN city c ON (c. city= l. city)
ORDER BY
phone desc
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | *country* | NULL | index | PRIMARY | PRIMARY | 2 | NULL | 109 | ☠️ **100.00%** | ☠️ **O(n)** | Using index; Using temporary; Using filesort |
| 1 | PRIMARY | *city* | NULL | ref | PRIMARY,
idx\_fk\_country\_id | idx\_fk\_country\_id | 2 | sakila.country.country\_id | 5 | ☠️ **100.00%** | O(log n) | NULL |
| 1 | PRIMARY | *c* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | n% | ☠️ **O(n)** | Using where; Using join buffer (hash join) |
| 1 | PRIMARY | *a* | NULL | ref | PRIMARY,
idx\_fk\_city\_id | idx\_fk\_city\_id | 2 | sakila.city.city\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |
| 1 | PRIMARY | *cu* | NULL | ref | idx\_fk\_address\_id | idx\_fk\_address\_id | 2 | sakila.a.address\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |
| 1 | PRIMARY | ** | NULL | ref | | | 152 | sakila.a.address | 6 | ☠️ **100.00%** | O(log n) | Using index |
| 2 | DERIVED | *a* | NULL | ALL | PRIMARY,
idx\_fk\_city\_id | NULL | NULL | NULL | 603 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort |
| 2 | DERIVED | *cu* | NULL | ref | idx\_fk\_store\_id,
idx\_fk\_address\_id | idx\_fk\_address\_id | 2 | sakila.a.address\_id | 1 | n% | O(log n) | Using where |
| 2 | DERIVED | *city* | NULL | eq\_ref | PRIMARY,
idx\_fk\_country\_id | PRIMARY | 2 | sakila.a.city\_id | 1 | ☠️ **100.00%** | O(log n) | NULL |
| 2 | DERIVED | *country* | NULL | eq\_ref | PRIMARY | PRIMARY | 2 | sakila.city.country\_id | 1 | ☠️ **100.00%** | O(log 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
* **Content:** 为列city添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_city\` (\`city\`) ;
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## ORDER BY 语句对多个不同条件使用不同方向的排序无法使用索引
* **Item:** CLA.007
* **Severity:** L2
* **Content:** ORDER BY 子句中的所有表达式必须按统一的 ASC 或 DESC 方向排序,以便利用索引。
## 同一张表被连接两次
* **Item:** JOI.002
* **Severity:** L4
* **Content:** 相同的表在 FROM 子句中至少出现两次,可以简化为对该表的单次访问。
## MySQL 对子查询的优化效果不佳
* **Item:** SUB.001
* **Severity:** L4
* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。
# Query: B0BA5A7079EA16B3
★ ★ ★ ★ ☆ 85分
```sql
SELECT
*
FROM
film
WHERE
DATE( last_update) = '2006-02-15'
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using where |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
## 避免在 WHERE 条件中使用函数或其他运算符
* **Item:** FUN.001
* **Severity:** L2
* **Content:** 虽然在 SQL 中使用函数可以简化很多复杂的查询,但使用了函数的查询无法利用表中已经建立的索引,该查询将会是全表扫描,性能较差。通常建议将列名写在比较运算符左侧,将查询过滤条件放在比较运算符右侧。也不建议在查询比较条件两侧书写多余的括号,这会对阅读产生比较大的困扰。
# Query: 18A2AD1395A58EAE
★ ★ ★ ☆ ☆ 60分
```sql
SELECT
last_update
FROM
film
GROUP BY
DATE( last_update)
```
## 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.
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
## GROUP BY 的条件为表达式
* **Item:** CLA.010
* **Severity:** L2
* **Content:** 当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。
# Query: 60F234BA33AAC132
★ ★ ★ ☆ ☆ 70分
```sql
SELECT
last_update
FROM
film
ORDER BY
DATE( last_update)
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | ☠️ **100.00%** | ☠️ **O(n)** | Using filesort |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* ☠️ **Using filesort**: MySQL会对结果使用一个外部索引排序,而不是从表里按照索引次序读到相关内容. 可能在内存或者磁盘上进行排序. MySQL中无法利用索引完成的排序操作称为'文件排序'.
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## ORDER BY 的条件为表达式
* **Item:** CLA.009
* **Severity:** L2
* **Content:** 当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。
# Query: 1ED2B7ECBA4215E1
★ ★ ★ ★ ☆ 80分
```sql
SELECT
description
FROM
film
WHERE
description IN( 'NEWS',
'asd'
)
GROUP BY
description
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **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
* **Content:** 为列description添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_description\` (\`description\`(191)) ;
## 请为 GROUP BY 显示添加 ORDER BY 条件
* **Item:** CLA.008
* **Severity:** L2
* **Content:** 默认 MySQL 会对 'GROUP BY col1, col2, ...' 请求按如下顺序排序 'ORDER BY col1, col2, ...'。如果 GROUP BY 语句不指定 ORDER BY 条件会导致无谓的排序产生,如果不需要排序建议添加 'ORDER BY NULL'。
# Query: 255BAC03F56CDBC7
★ ★ ★ ★ ★ 100分
```sql
ALTER TABLE
address
ADD
index idx_city_id( city_id)
```
## OK
# Query: C315BC4EE0F4E523
★ ★ ★ ★ ★ 100分
```sql
ALTER TABLE
inventory
ADD
index `idx_store_film` (
`store_id`, `film_id`)
```
## 提醒:请将索引属性顺序与查询对齐
* **Item:** KEY.004
* **Severity:** L0
* **Content:** 如果为列创建复合索引,请确保查询属性与索引属性的顺序相同,以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐,那么DBMS可能无法在查询处理期间使用索引。
# Query: 9BB74D074BA0727C
★ ★ ★ ★ ☆ 90分
```sql
ALTER TABLE
inventory
ADD
index `idx_store_film` (
`store_id`, `film_id`),
ADD
index `idx_store_film` (
`store_id`, `film_id`),
ADD
index `idx_store_film` (
`store_id`, `film_id`)
```
## 索引名称已存在
* **Item:** IDX.001
* **Severity:** L2
* **Content:** Duplicate key name 'idx\_store\_film'
* **Case:** alter table inventory add index \`idx\_store\_film\` (\`store\_id\`,\`film\_id\`),add index \`idx\_store\_film\` (\`store\_id\`,\`film\_id\`),add index \`idx\_store\_film\` (\`store\_id\`,\`film\_id\`)
## 提醒:请将索引属性顺序与查询对齐
* **Item:** KEY.004
* **Severity:** L0
* **Content:** 如果为列创建复合索引,请确保查询属性与索引属性的顺序相同,以便DBMS在处理查询时使用索引。如果查询和索引属性订单没有对齐,那么DBMS可能无法在查询处理期间使用索引。
# Query: C95B5C028C8FFF95
★ ☆ ☆ ☆ ☆ 30分
```sql
SELECT
DATE_FORMAT( t. last_update, '%Y-%m-%d'
),
COUNT( DISTINCT (
t. city))
FROM
city t
WHERE
t. last_update > '2018-10-22 00:00:00'
AND t. city LIKE '%Chrome%'
AND t. city = 'eip'
GROUP BY
DATE_FORMAT( t. last_update, '%Y-%m-%d'
)
ORDER BY
DATE_FORMAT( t. last_update, '%Y-%m-%d'
)
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *t* | NULL | ALL | NULL | NULL | NULL | NULL | 600 | n% | ☠️ **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库的city表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列city添加索引,散粒度为: n%; 为列last\_update添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`city\` add index \`idx\_city\_last\_update\` (\`city\`,\`last\_update\`) ;
## 建议使用 AS 关键字显示声明一个别名
* **Item:** ALI.001
* **Severity:** L0
* **Content:** 在列或表别名(如"tbl AS alias")中, 明确使用 AS 关键字比隐含别名(如"tbl alias")更易懂。
## 不建议使用前项通配符查找
* **Item:** ARG.001
* **Severity:** L4
* **Content:** 例如 "%foo",查询参数有一个前项通配符的情况无法使用已有索引。
## ORDER BY 的条件为表达式
* **Item:** CLA.009
* **Severity:** L2
* **Content:** 当 ORDER BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。
## GROUP BY 的条件为表达式
* **Item:** CLA.010
* **Severity:** L2
* **Content:** 当 GROUP BY 条件为表达式或函数时会使用到临时表,如果在未指定 WHERE 或 WHERE 条件返回的结果集较大时性能会很差。
## ORDER BY 多个列但排序方向不同时可能无法使用索引
* **Item:** KEY.008
* **Severity:** L4
* **Content:** 在 MySQL 8.0之前当 ORDER BY 多个列指定的排序方向不同时将无法使用已经建立的索引。
# Query: C11ECE7AE5F80CE5
★ ★ ☆ ☆ ☆ 45分
```sql
create table hello. t (id int unsigned)
```
## 建议为表添加注释
* **Item:** CLA.011
* **Severity:** L1
* **Content:** 为表添加注释能够使得表的意义更明确,从而为日后的维护带来极大的便利。
## 请为列添加默认值
* **Item:** COL.004
* **Severity:** L1
* **Content:** 请为列添加默认值,如果是 ALTER 操作,请不要忘记将原字段的默认值写上。字段无默认值,当表较大时无法在线变更表结构。
## 列未添加注释
* **Item:** COL.005
* **Severity:** L1
* **Content:** 建议对表中每个列添加注释,来明确每个列在表中的含义及作用。
## 未指定主键或主键非 int 或 bigint
* **Item:** KEY.007
* **Severity:** L4
* **Content:** 未指定主键或主键非 int 或 bigint,建议将主键设置为 int unsigned 或 bigint unsigned。
## 请为表选择合适的存储引擎
* **Item:** TBL.002
* **Severity:** L4
* **Content:** 建表或修改表的存储引擎时建议使用推荐的存储引擎,如:innodb
# Query: 291F95B7DCB74C21
★ ★ ★ ★ ☆ 95分
```sql
SELECT
*
FROM
tb
WHERE
data >= ''
```
## 不建议使用 SELECT * 类型查询
* **Item:** COL.001
* **Severity:** L1
* **Content:** 当表结构变更时,使用 \* 通配符选择所有列将导致查询的含义和行为会发生更改,可能导致查询返回更多的数据。
# Query: 084DA3E3EE38DD85
★ ★ ★ ★ ★ 100分
```sql
ALTER TABLE
tb alter column id
DROP
DEFAULT
```
# Query: B48292EDB9D0E010
★ ★ ☆ ☆ ☆ 40分
```sql
SELECT
maxId, minId
FROM
(
SELECT
MAX( film_id) maxId, MIN( film_id) minId
FROM
film
WHERE
last_update > '2016-03-27 02:01:01'
) as d
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | ** | NULL | system | NULL | NULL | NULL | NULL | 1 | ☠️ **100.00%** | O(1) | NULL |
| 2 | DERIVED | *film* | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | n% | ☠️ **O(n)** | Using where |
### Explain信息解读
#### SelectType信息解读
* **DERIVED**: 用于from子句里有子查询的情况. MySQL会递归执行这些子查询, 把结果放在临时表里.
* **PRIMARY**: 最外层的select.
#### Type信息解读
* **system**: 这是const连接类型的一种特例, 该表仅有一行数据(=系统表).
* ☠️ **ALL**: 最坏的情况, 从头到尾全表扫描.
#### Extra信息解读
* **Using where**: WHERE条件用于筛选出与下一个表匹配的数据然后返回给客户端. 除非故意做的全表扫描, 否则连接类型是ALL或者是index, 且在Extra列的值中没有Using Where, 则该查询可能是有问题的.
## 为sakila库的film表添加索引
* **Item:** IDX.001
* **Severity:** L2
* **Content:** 为列last\_update添加索引,散粒度为: n%;
* **Case:** ALTER TABLE \`sakila\`.\`film\` add index \`idx\_last\_update\` (\`last\_update\`) ;
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## MySQL 对子查询的优化效果不佳
* **Item:** SUB.001
* **Severity:** L4
* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。
## 不建议在子查询中使用函数
* **Item:** SUB.006
* **Severity:** L2
* **Content:** MySQL将外部查询中的每一行作为依赖子查询执行子查询,如果在子查询中使用函数,即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。
# Query: 4A39009B402BAD9B
★ ★ ☆ ☆ ☆ 50分
```sql
SELECT
maxId, minId
FROM
(
SELECT
MAX( film_id) maxId, MIN( film_id) minId
FROM
film) as d
```
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | PRIMARY | ** | NULL | system | NULL | NULL | NULL | NULL | 1 | ☠️ **100.00%** | O(1) | NULL |
| 2 | DERIVED | *NULL* | NULL | NULL | NULL | NULL | NULL | NULL | 0 | n% | NULL | Select tables optimized away |
### Explain信息解读
#### SelectType信息解读
* **DERIVED**: 用于from子句里有子查询的情况. MySQL会递归执行这些子查询, 把结果放在临时表里.
* **PRIMARY**: 最外层的select.
#### Type信息解读
* **system**: 这是const连接类型的一种特例, 该表仅有一行数据(=系统表).
#### Extra信息解读
* **Select tables optimized away**: 仅通过使用索引,优化器可能仅从聚合函数结果中返回一行。如:在没有 GROUP BY 子句的情况下,基于索引优化 MIN/MAX 操作,或者对于 MyISAM 存储引擎优化 COUNT(*) 操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
## 最外层 SELECT 未指定 WHERE 条件
* **Item:** CLA.001
* **Severity:** L4
* **Content:** SELECT 语句没有 WHERE 子句,可能检查比预期更多的行(全表扫描)。对于 SELECT COUNT(\*) 类型的请求如果不要求精度,建议使用 SHOW TABLE STATUS 或 EXPLAIN 替代。
## MySQL 对子查询的优化效果不佳
* **Item:** SUB.001
* **Severity:** L4
* **Content:** MySQL 将外部查询中的每一行作为依赖子查询执行子查询。 这是导致严重性能问题的常见原因。这可能会在 MySQL 5.6 版本中得到改善, 但对于5.1及更早版本, 建议将该类查询分别重写为 JOIN 或 LEFT OUTER JOIN。
## 不建议在子查询中使用函数
* **Item:** SUB.006
* **Severity:** L2
* **Content:** MySQL将外部查询中的每一行作为依赖子查询执行子查询,如果在子查询中使用函数,即使是semi-join也很难进行高效的查询。可以将子查询重写为OUTER JOIN语句并用连接条件对数据进行过滤。