SQL编写建议¶
编写高效的SQL语句往往能够获得更好的执行性能,下面将提供一些关于编写SQL语句的启发式建议,供大家参考。
| 名称 | 用 例 | 说 明 |
|---|---|---|
| 建议使用AS关键字显式声明别名 | SELECT * FROM t1 laft JOIN t2 ON TRUE; | 在当前语句中laft是t1表的别名,可见在列或者表别名中使用AS关键字显式声明别名代替隐式别名更易懂。 |
| 不建议给列通配符'*'设置别名 | SELECT a FROM (SELECT t1.* AS a FROM t1); | 列通配符设置别名会存在逻辑错误,导致别名无法被使用。 |
| 别名不要与表或列的名字相同 | SELECT name FROM t1 AS t1 WHERE id < 100; | 表或列的别名与其真实名称相同, 这样的别名会使得查询更难去分辨。 |
| 不建议使用前项通配符查找 | SELECT a,b FROM t1 WHERE name LIKE '%foo'; | 查询参数有一个前项通配符的情况,会遍历全部索引,影响查询性能。 |
| LIKE查询不建议使用'列名||字符串' | SELECT a,b FROM t1 WHERE name LIKE c||'foo%'; | 使用‘列名||字符串’形式进行匹配会导致查询无法走索引,影戏查询性能。 |
| 不建议使用没有通配符的LIKE查询 | SELECT a,b FROM t1 WHERE name LIKE 'foo'; | 不包含通配符的 LIKE 查询可能存在逻辑错误,因为逻辑上它与等值查询相同。 |
| 避免WHERE在子句中进行NULL值判断 | SELECT a FROM t1 WHERE a IS NULL; | 使用IS NULL或IS NOT NULL可能导致引擎遍历全部索引,建议将a上设置默认值,避免NULL值。 |
| 不建议使用HINT | SELECT /*+ index(t1 i1) */ a FROM t1 ORDER BY a; | HINT是用来强制SQL按照某个执行计划来执行,但随着数据量变化无法保证当初的判断是正确的。 |
| 不建议一次INSERT太多数据 | INSERT INTO t1(a) VALUES (1), (2); | 单条INSERT语句批量插入大量数据性能较差,甚至可能导致从库同步延迟。 |
| 不建议使用ORDER BY RANDOM | SELECT a FROM t1 WHERE id < 1000 ORDER BY RANDOM(); | ORDER BY RANDOM 是从结果集中检索随机行的一种非常低效的方法,因为它会对整个结果进行排序。 |
| 不建议对常量进行GROUP BY/ORDER BY | SELECT a,SUM(b) FROM t1 WHERE id < 1000 GROUP BY 1; | 如果在GROUP BY/ORDER BY子句中使用数字,而不是表达式或列名称,当查询列顺序改变时,可能会导致问题。 |
| 不建议使用 HAVING 子句 | SELECT a FROM t1 GROUP BY a HAVING a <> 1 ORDER BY a; | 将查询的HAVING子句改写为WHERE中的查询条件,可以在查询处理期间使用索引。 |
| 删除全表时建议使用 TRUNCATE | DELETE t1; | 删除全表时建议使用TRUNCATE替代DELETE,TRUNCATE在功能上与不带WHERE的DELETE语句相同,但速度更快。 |
| 谨慎使用TRUNCATE操作 | TRUNCATE t1; | TRUNCATE无法返回被删除的行数,需要返回被删除的行数建议使用DELETE语法。TRUNCATE删除的数据无法回滚 |
| UPDATE建议指定WHERE条件 | UPDATE t1 SET a = 1; | UPDATE不指定WHERE条件一般是致命的,请您三思后行。 |
| 不建议使用 SELECT * 类型查询 | SELECT * FROM t1 WHERE a = 1; | 当表结构变更时,使用*通配符选择所有列将导致查询的含义和行为发生更改,可能导致查询返回更多的数据。 |
| INSERT建议指定列名 | INSERT INTO t1 VALUES(1,'name'); | 当表结构发生变更,如果 INSERT 请求不明确指定列名,请求的结果将会与预想的不同。 |
| 谨慎使用NULL | SELECT a,b,c FROM t1 WHERE a IS NULL OR a <> 1; | 在NULL上进行运算、拼接操作,会得到NULL值,AND、OR和NOT操作如果涉及NULL,结果也让很多人感到困惑。 |
| 当列不能有缺失值时才使用NOT NULL | CREATE TABLE t1(id int NOT NULL); | 将一列声明为NOT NULL时,这列中的每一个值都必须存在。TEXT、BLOB和JSON字段不建议为NOT NULL。 |
| 避免在WHERE中使用函数或其他运算符 | SELECT a FROM t1 WHERE SUBSTRING(c,1,3)='ABC'; | 函数查询无法利用表中已经建立的索引,建议列名写在运算符左侧,不建议在比较条件两侧书写多余的括号。 |
| 指定WHERE条件,不建议使用COUNT操作 | SELECT a, COUNT(*) FROM t1 WHERE a<2000 GROUP BY a ORDER BY b; | WHERE条件,COUNT操作需要扫描大量的行才能获取精确的结果,性能也因此不佳。 |
| 不建议使用COUNT(列名)代替COUNT(*) | SELECT COUNT(a) FROM t1; | 不建议使用COUNT(列名)或COUNT(常量)来替代COUNT(*),COUNT(*)会统计NULL行,COUNT(列名)不会 |
| 建议将COUNT(*)>0 改写成EXISTS | SELECT * FROM t1 WHERE (SELECT COUNT(*) FROM t2 WHERE t2.a = t1.a) > 0; | 可改写成SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.a = t1.a); |
| 使用SUM时需注意列值为空的情况 | SELECT SUM(a) FROM t1; | 某一列值全是NULL时,SUM(col)返回NULL值。可通过SELECT IF(ISNULL(SUM(col)),0,SUM(col)) FROM t1;避免 |
| 不建议对等值查询列使用GROUP BY | SELECT a FROM t1 WHERE a = 3 GROUP BY a; | GROUP BY中的列在前面的WHERE条件中使用了等值查询,对这样的列进行GROUP BY意义不大。 |
| 同一张表不建议连接两次 | SELECT t1.a FROM t1,t2 AS tb2 JOIN t2 ON TRUE; | 相同的表在FROM子句中至少出现两次,可以简化为对该表的单次访问。 |
| 建议减少JOIN的数量 | SELECT t1.a FROM t1 JOIN t2 ON TRUE JOIN t3 ON TRUE JOIN t4 ON TRUE; | 连接的表越多,消耗时间越长,严重影响性能,可把连接表拆开成较小的几个执行。 |
| 建议将嵌套查询重写为JOIN | SELECT a FROM t1 WHERE a IN (SELECT a FROM t2); | 将嵌套查询重写为JOIN通常会导致更高效的执行和更有效的优化。 |
| 建议将索引属性顺序与查询对齐 | CREATE INDEX idx1 ON t1(a,b); | 若为列创建复合索引,请确保查询属性与索引属性的顺序相同,以便DBMS在处理查询时使用索引。 |
| 不建议为表建立太多索引 | CREATE INDEX idx1 ON t1(a);...CREATE INDEX idxi ON t1(i); | 在表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。 |
| 主键中的列不建议过多 | CREATE TABLE tb1(a INT,b INT,c INT, PRIMARY KEY(a,b,c)); | 当表结构变更时,主键中的列过多会影响性能 |
| 使用LIMIT时建议使用ORDER BY | SELECT a,b FROM t1 WHERE a > 1 LIMIT 10; | 没有ORDER BY的LIMIT会导致非确定性的结果,这取决于查询执行计划。 |
| UPDATE/DELETE不建议指定ORDER BY | UPDATE t1 SET a = 12 WHERE b = 'abc' ORDER BY b; | 对UPDATE/DELETE的数据使用ORDER BY意义不大。 |
| UPDATE语句可能存在逻辑错误 | UPDATE t1 SET a = 1, b = 2 WHERE c='A'; | 在一条UPDATE语句中,如果要更新多个字段,字段间不能使用 AND ,而应该用逗号分隔。 |
| 建议注意永远不真的比较条件 | SELECT * FROM t1 WHERE 1 != 1; | 查询条件永远非真,如果该条件出现在WHERE中可能导致查询无匹配到的结果。 |
| 建议使用UNION ALL替代UNION | SELECT * FROM t1 WHERE a<3 UNION SELECT * FROM t2 WHERE a>5 | 与去除重复的UNION不同,UNION ALL允许重复元组。如果您不关心重复元组,使用UNION ALL将更快。 |
| 执行计划中嵌套连接深度不建议过深 | SELECT * FROM t1 WHERE a IN (SELECT a FROM (SELECT a FROM t1)); | 外部查询中的每一行作为依赖子查询执行子查询,影响查询性能。 |
| 不建议在子查询中使用函数 | SELECT * FROM t1 WHERE a IN (SELECT MAX(a) FROM t2); | 外部查询中的每一行作为依赖子查询,如果在子查询中使用函数,即使是SEMI-JOIN也很难进行高效的查询。 |
| LIMIT的UNION下内查询建议添加LIMIT | (SELECT * FROM t1 LIMIT 20) UNION (SELECT * FROM t2 LIMIT 20) LIMIT 20; | 数据库无法将限制条件从外层“下推”到内层,使得原本可以限制返回结果条件无法应用到内层查询的优化上。 |