索引规约
来自部门分享--- 分享人(yangkai)
整理:(sunsijia)
1.【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。 说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明 显的;另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律, 必然有脏数据产生。
2. 【强制】 超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询 时,保证被关联的字段需要有索引。 说明:即使双表 join 也要注意表索引、SQL 性能。
3. 【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度。
4. 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。 说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索 引。
参考sql
select * from log_opers where remark like '修改%'
select * from log_opers where name = '应用程序'
5. 【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合 索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。 正例:where a=? and b=? order by c; 索引:a_b_c 反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序
参考sql
select * from (select ROW_NUMBER () OVER (ORDER BY id) page,* from log_opers)a where a.page BETWEEN 1100000 and 1105000
select
a.*
from
log_opers a,
(select id from (select ROW_NUMBER () OVER (ORDER BY id) page,id
from log_opers)a where a.page BETWEEN 1100000 and 1105000) b
where a.id = b.id
7. 【推荐】利用延迟关联或者子查询优化超多分页场景。 说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过 特定阈值的页数进行 SQL 改写。 正例:先快速定位需要获取的 id 段,然后再关联: SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
8. 【推荐】建组合索引的时候,区分度最高的在最左边。 正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即 可。 说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>? and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列
参考sql
select * from(
SELECT
ROW_NUMBER () OVER (ORDER BY kssj) page,
scholar,
mss,
webs,
bh,
kcdm,
kcmc,
kkcs,
bjlx,
CONVERT (VARCHAR(100), kssj, 23) kssj,
jssj,
kczt,
tjsj,
lbmc,
jsxm,
jsemail,
bjid
FROM
rt_class_list) a
WHERE
a.kczt < 4
10. 【参考】创建索引时避免有如下极端误解: 1)误认为一个查询就需要建一个索引。 2)误认为索引会消耗空间、严重拖慢更新和新增速度。
11.SQL 规约
- 【强制】不要使用 count(列名)或 count(常量)来替代 count(*),count(*)就是 SQL92 定义 的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。 说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
- 【强制】count(distinct col) 计算该列除 NULL 之外的不重复数量。
参考sql
select count(DISTINCT name,update_at) from log_opers where account='123'
12. 【强制】当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题。 正例:可以使用如下方式来避免 sum 的 NPE 问题:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table
参考sql
select count(name) cou,name from log_opers GROUP BY name order by cou desc
select sum(times) from log_opers where id>1 and id<10
select isnull(sum(times),0) from log_opers where id>1 and id<10
select isnull(null,0)
13.【强制】不得使用外键与级联,一切外键概念必须在应用层解决。 说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。 如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,则为级联更新。 外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数 据库更新风暴的风险;外键影响数据库的插入速度。
14. 【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
15. 【强制】数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执 行更新语句。
16. 【推荐】in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控 制在 1000 个之内。
17. 【参考】如果有全球化需要,所有的字符存储与表示,均以 utf-8 编码,那么字符计数方法 注意: 说明:
SELECT LENGTH("轻松工作"); 返回为 12
SELECT CHARACTER_LENGTH("轻松工作"); 返回为 4
如果要使用表情,那么使用 utfmb4(nvarchar) 来进行存储,注意它与 utf-8 编码的区别。
18. 【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。 说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
19.并发处理
- 【强制】获取单例对象需要保证线程安全,其中的方法也要保证线程安全。 说明:资源驱动类、工具类、单例工厂类都需要注意。
- 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。 正例: public class TimerTaskThread extends Thread { public TimerTaskThread(){ super.setName("TimerTaskThread"); ... }
20. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资 源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换”的问题。
21. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
22. 【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能 锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
23. 【强制】对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造 成死锁。 说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序 也必须是 A、B、C,否则可能出现死锁。
24. 【强制】并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在 数据库层使用乐观锁,使用 version 作为更新依据。 说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次 数不得小于 3 次。
25. 【强制】多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获 抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。