Mysql优化小议(数据库设计、命名规范、索引优化、mysql面试、java面试)

写在最前面,本篇内容是在公司内部培训的一次内容,今天分享出来,希望能够对需要的朋友起到帮助,如果内容有错误,也请留言指出,感激不尽,转载请注明出处。

另外以后我会整理一个关于Mysql知识点的系列文章,感兴趣的朋友可以扫描下面的二维码加群。
Mysql优化小议(数据库设计、命名规范、索引优化、mysql面试、java面试)
------------ 废话不多说,开始正文 -----------------

一、数据库设计

  • 三范式简单理解
    – 第一范式(1NF):字段具有原子性,不能再分(所有关系型数据库系统都满足第一范式)
    – 第二范式(2NF):一个表必须有主键,即每行数据都能被唯一的区分
    – 第三范式(3NF):一个表中不能包涵其他相关表中非关键字段的信息,即数据表不能有沉余字段

    注:为了使用方便,很难完全符合 第三范式,适当冗余是需要的
    
  • MySql 存储引擎(InnoDB, MyISAM)
    – MyISAM:不支持事物, 写入和读取速度比较快,适合写少,读多的场景
    – InnoDB:支持事物,行锁

  • 命名
    – 表 名:下划线连接全小写单词,例:product,product_order

  • 字段名:
    – 所有字段都不允许为NULL(特殊考虑:date、datetime)
    – 默认值:应考虑哪些字段不应允许有默认值
    – 小写驼峰,其中:

    • bool型:is* 或 allow*,如isDisabled(是否禁用)或allowClose(允许关闭)
    • datetime类型:*Time,如startedTime
    • date类型(YYYY-MM-DD):*Date,如endDate

    – 关联字段名为对应AR类名首字母小写+关联字段名,如:user_id,order_id

  • 主键:建议使用非业务字段做主键(id,int 自增)

  • 外键:不使用外键(由程序保证约束)

注:要控制表的字段数量,根据业务规则适当的分表

二、SQL执行顺序=

  1. FROM:对FROM子句中前两个表执行笛卡尔积生成虚拟表vt1
  2. ON:对vt1表应用ON筛选器只有满足< join_condition> 为真的行才被插入vt2
  3. 如果 FROM 包含两个以上表则对上一个联结生成的结果表和下一个表重复执行步骤1 和 步骤2
  4. WHERE:对vt2应用 WHERE 筛选器只有使< where_condition> 为true的行才被插入vt3
  5. GROUP BY:按GROUP BY子句中的列对vt3中的行分组生成vt4
  6. HAVING:对vt4应用HAVING筛选器只有使< having_condition> 为true的组才插入vt5
  7. SELECT:处理select列表产生vt6
  8. DISTINCT:将重复的行从vt6中去除产生vt7
  9. ORDER BY:将vt7的行按order by子句中的列列表排序生成一个游标vc8
  10. LIMIT:从vc8的开始处选择指定数量或比例的行生成vt9 并返回调用者

参考: https://www.cnblogs.com/annsshadow/p/5037667.html

三、索引

类型:

  • 主键索引:primary,数据的物理存储顺序
  • 唯一索引:unique index
  • 普通索引:index
  • 复合索引:最左前缀原则

选择索引的数据字段:

  • 越小的数据类型通常更好
  • 简单的数据类型更好
  • 尽量避免NULL

无法使用索引的情况:

  • 以%开头的like查询

    例如:
    explain select * from order where number like '%201703'
    
  • 数据类型出现隐式转换的时候也不会使用索引

    例如:explain select * from order where number = 20170328104740498530
    
  • 复合索引的情况下,不满足最左前缀原则,则不会使用索引

    例如:explain select a.* from order as a where a.uid = 1 and a.status = 2
    
  • 用 or 分割开的条件,如果 or 前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到

    例如:explain select a.* from order as a where a.uid = 1 or a.status=2
    
  • 字段进行运算、或格式转化后,将无法使用索引

    例如:explain select a.* from order as a where from_unixtime(a.create_time, '%Y%m%d') = '20170101'
    
  • != 、 not in 无法使用索引

    例如:explain select * from order  where create_time not in (13423424232)
    
  • 查询条件使用了索引,就一定会用到吗?

    如果mysql估计使用索引扫描比全表扫描更慢,则不使用索引。(扫描数据超过30%,都会走全表)	
    

参考:
https://dev.mysql.com/doc/refman/5.5/en/optimization-indexes.html
https://www.cnblogs.com/tgycoder/p/5410057.html

四、SQL语句建议

  • 书写SQL时要注意缩进

    select 
    from table1 as t1 
    	 inner join table2 as t2 on t1.col1 = t2.cols
    where 
    group by 
    having
    order by
    limit
    
  • INSERT:格式 NSERT INTO TABLE1(col1, col2) VALUES (‘yayun’,23)

  • 大量数据写入,一定要使用批量的方式:INSERT INTO TABLE1(col1, col2) VALUES (‘yayun’,23),(‘tom’,26),(‘atlas’,32),(‘david’,25)…

  • 避免使用 SELECT * 语句, 只返回需要的数据字段

  • 当在SQL语句中连接多个表时, 为每个表都起别名

    Select A.ID, A.col1, B.col2
    from table1 A 
    	inner join table2 B on A.ID=B.ID 
    where 。。。
    
  • 合理写WHERE子句,不要写没有 WHERE 条件的 SQL 语句

  • 杜绝不必要的子查询和连接表,多余排序

    select count(*) 
    from userinfo as a
    	left join user as b on a.id = b.uid 			#这个连接真的需要吗?
    	left join user_bank as c on a.id = c.uid		#一个还不够,又来一个?
    where uid in (7704,11027,.....)
    order by a.reg_time desc			#这个排序有用吗?
    
  • 合并对同一表同一条件的多次 UPDATE

    update tablename set col1 = '1111'  where id = 1
    update tablename set col2 = '2222'  where id = 1
    
    这两个语句应该合并成以下一个语句
    update tablename set col1 = '1111', col2 = '2222'  where id = 1
    
  • 避免 IN 子句,当子句中有多个值且表数据较多时,速度下降明显,可以采用表关联查询来代替

  • OR 改写为 IN (OR 的效率更低)

  • 使用 union all 替代 union (union有去重开销)

  • 能使用 inner join 就不使用 left join(思考:left join 后的 on 条件 和 where 条件 的区别

  • exists、not exists 当子查询的表很大时,性能很差,可改为 left join 或 inner join

  • SQL语句尽可能简单,复杂查询可以借助临时表或内存表

  • 简单的事务,事物会锁表/行,尽可能的让事物快速执行完成

  • 参数的传递:SQL 语句的编写,变量不要直接拼接到语句中,

结论:

  • 尽量少的查询数据
  • 使用子查询时,确保子查询数据量要小
  • 合理使用索引

五、explain及其输出

作用:

  • 显示mysql如何使用索引来处理select语句以及连接表

EXPLAIN 列的解释:

  • id:执行序号,数字大的先执行,数字相同,从上到下执行

  • select_type:查询的类型,主要是区别普通查询和联合查询、子查询之类的复杂查询
    – SIMPLE:简单查询,没有使用 UNION、子查询等

    例如:explain select * from user where id = 1
    

    – PRIMARY:在嵌套的查询中是最外层的SELECT语句,在UNION查询中是最前面的SELECT语句

    – SUBQUERY:子查询中的第一个 SELECT

    例如: 
    explain
    select o.number 
    from order as o
    where o.uid =  (
    	select a.id from user as a where a.id = 1
    )
    

    – DEPENDENT SUBQUERY:子查询中的第一个 SELECT, 并且依赖外部查询
    – UNION: 使用了 UNION 的第二个 select
    – DEPENDENT UNION:使用了 UNION 的第二个 select,并且依赖外部查询
    – UNION RESULT:UNION 结果

    例如:explain
    select o.number 
    from order as o
    where uid in (
    	select a.id from user as a where a.id in (1,10,20)
    	union  
    	select b.id from user as b where b.id in (11,21,31)
    )
    
    上面的写法会优化成下面这样执行
    explain 
    select o.number from order o 
    where exists  (
    	select a.id from user as a where a.id in (1,10,20) and a.id = o.uid
    	union  
    	select b.id from user as b where b.id in (11,21,31) and b.id = o.uid
    );
    

    – DERIVED:派生表,该临时表是从子查询派生出来的,位于FROM中的子查询

    例如:explain select b.number from (select * from order as a) as b
    
  • table:查询的表
    – <unionM,N>:UNION 查询结果
    – < derivedN>: 派生表,FROM 子查询结果

  • type:访问类型
    – system:这是const连接类型的一种特例,表中只有一行数据
    – const:表示只有一行匹配,只读取一次,查询前读取,当查询条件是主键或唯一索引字段时(高效)

    例如:explain SELECT * FROM `user` WHERE id = 501
    

    – eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描

    例如:explain SELECT * FROM user as a inner join `order` as b on a.id = b.uid
    

    – ref:非唯一性索引扫描,返回匹配某个单独值的所有行。常见于使用非唯一索引即唯一索引的非唯一前缀进行的查找

    例如:explain select * from `user`  WHERE  username = 'love001'
    

    – index_merge: 索引合并

    例如:explain SELECT * FROM `order` WHERE id = 10 and deal_id = 100 
    

    – range:限制范围的索引扫描,(=、 <>、>、>=、<、<=、BETWEEN 、 IN )

    例如:explain select * from `user`  WHERE  id > 10 and id < 20;
    

    – index:按索引次序扫描,先读索引,再读实际的行,不需要额外排序

    例如:explain select * from `user`  order by id desc;
    

    – All:全表扫描,性能最差

    例如:explain select * from user where create_time >= unix_timestamp('2017-01-01');
    
  • possible_keys:可能使用到的索引,如果是空的,没有相关的索引

  • key:实际使用到的索引,NULL 表示没有用到索引

  • key_len:显示使用的索引的长度,在不损失精确性的情况下,长度越短越好

  • ref:显示哪些列或常量和key中指定的索引一起做为筛选条件从表中选择数据

    例如:
    explain 
    select a.* 
    from order as a 
    	inner join user as b on a.uid = b.id
    where b.id <=10 
    
  • rows:查询时必须检查的行数, 预估值

  • Extra:额外补充信息
    – Using filesort:排序没有用到合适的索引排序,需要做一次额外排序操作
    – Using index:只使用索引中的信息,而不需要进一步读取实际的行来检索表中的信息
    – Using temporary:使用到了临时表, 通常使用了 GROUP BY 或 ORDER BY 时 用到
    – Using where:使用了where条件

参考:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

欢迎关注【程序员学习交流群】
Mysql优化小议(数据库设计、命名规范、索引优化、mysql面试、java面试)