• 【SQL笔记】基础(一)


    个人学习笔记分享,当前能力有限,请勿贬低,菜鸟互学,大佬绕道

    如有勘误,欢迎指出和讨论,本文后期也会进行修正和补充

    前言

    SQL学习中的笔记,重点为4.2和5,题长不看的请直接空降

    1.基础知识

    增、删、改、查、连表、索引,新手教程一大堆,不多做叙述,菜鸟教程啥都有。。。

    2.书写规范

    • 表名:应有意义,使用小写英文、数字和下划线组成

      Linux对大小写敏感,故方便起见,推荐统一表名的英文全部小写

    • 注释:单行注释使用-- (注意有个空格)或者#标记行首,多行注释使用/**/标记头尾

    • 缩进和空格:同Java,Python等编程语言,保持代码可读性

    • 大小写:关键字用大写,表名、字段名用小写(关键词大写为习惯,非强制)

    3.基础技巧

    3.0.explain(重中之重)

    • 解释:分析sql语句的执行情况,可以分析出当前使用的查找模式,索引和临时表的使用情况等等
    • 使用场景:分析执行情况,调整sql或者索引来达到优化的目的
    • 优点:可以借此方法对sql进行调试,知晓其执行情况,从而进行调整
    • 备注:相关资料很多,为性能优化的基本知识

    3.1.case...when..

    • 解释:条件选择,类似于Java的switch语句,详情查看新手教程

    • 使用场景:当需要对不同数据采取不同方案的情况下,

    • 使用范例

      • 查询性别;gender字段的值为1返回“男”,为2时返回“女”,否则返回“未知”

        SELECT CASE gender 
        	WHEN 1 THEN '男'
        	WHEN 2 THEN '女'
        	ELSE '未知' END AS genderStr
        FROM student_info;
        
      • 降薪;对salary字段更新,高于1W的降低20%,其余降低10%

        UPDATE salary_info
        SET salary = CASE 
        	WHEN salary >10000 THEN salary * 0.8
        	ELSE salary * 0.9 END;
        
    • 优点

      • 省去数据的前置处理或者后置处理
      • 避免使用多句sql,导致数据错乱。如范例2若对两种情况分别执行sql,将出现错误的结果

    3.2.having

    • 解释:对结果追加条件筛选,常与GROUP BY结合使用,但其实也可以单独使用

    • 使用场景:对数据进行再次筛选

    • 使用范例

      • 统计学生的所有学科成绩,但仅返回平均分低于60的学生

        SELECT COUNT(mark_record.marks),student_info.name,COUNT(mark_record.marks)
        FROM student_info
        LEFT JOIN mark_record ON mark_record.stu_id = student_info.id
        GROUP BY student_info.id
        HAVING AVG(mark_record.marks) < 60
        LIMIT 1,10
        
    • 优点

      • 直接对结果数据直接追加处理,也就不必使用子表啦,节省了大量资源~如范例的常规方案是先查出学生的所有学科,存为子表,再进行筛选

    3.3.self join

    • 解释:自连接。即自己与自己连接,仅作为一种方案,并不是SQL关键词

    • 使用场景:需要引用自己身数据的情况下

    • 使用范例

      • 查询与自己同班同学人数

        SELECT
        	si1.id,
        	si1.NAME,
        	count(*) 
        FROM
        	student_info si1
        	LEFT JOIN student_info si2 ON si1.grade_id = si2.grade_id 
        GROUP BY
        	si1.id
        
      • 查询某学科的所有成绩并排名

        SELECT
        	mr1.*,
        	count( mr2.id )+ 1 
        FROM
        	mark_record mr1
        	LEFT JOIN mark_record mr2 ON mr2.mark > mr1.mark 
        WHERE
        	mr1.subject_id = 1 
        GROUP BY
        	mr1.id
        
    • 优点:emm没看出来,只能说是一种方案吧

    • 备注:注意连接条件字段最好用索引优化,否则两个全表查询,很容易查到天荒地老...

    3.4.COALESCE

    • 解释:返回第一个非空值,语法为COALESCE(value,...)

    • 使用场景:结果可能为空时返回其他值,可以是默认值,也可以是其他字段

    • 使用范例

      • 查询学生手机号,若为空则返回家长手机号,也为空则返回“无联系方式”

        SELECT 
        	name,
        	COALESCE ( phone, parent_phone, '无联系方式' ) 
        FROM
        	student_info
        
    • 优点

      • 不必再写大量的case...when来达到上述的效果

      • 设定默认值,情况下返回NULL是很不友好的,若不对空值特殊处理则很容易出现空指针,那为什么不在sql就处理好呢?

    4.基础性能优化

    此处仅指出最最最基础的部分优化,实际的性能优化是一门大学问,慢慢来

    4.1.使用子查询时,尽量使用exists替代in

    • 原因:子表比主表大的情况下exists的性能远比in高

    • 分析

      • 每次in都会查询子表中所有满足条件的结果,并将其缓存,再与主表匹配,即每次都会遍历子表全部数据
      • exists会每次从子表中查询是否满足条件,对结果不关心,故不需要缓存
      • 若主表有m条数据,子表有n条数据,那么in查询最多需要m*n,而exists始终是m
    • 范例

      • 查询所有持有证书的教师

        -- in方法
        SELECT
        	* 
        FROM
        	teacher_info 
        WHERE
        	id IN ( SELECT person_id FROM certificate_record );
        	
        -- exists方法
        SELECT
        	* 
        FROM
        	teacher_info ti 
        WHERE
        	EXISTS (
        	SELECT
        		1 
        	FROM
        		certificate_record cr 
        	WHERE
        	cr.person_id = ti.id 
        	)
        
    • 优点:当子表数据远比主表大的时候,性能优化将会很明显

    • 备注:说得好,我选择join连接。。。

    4.2.合理使用索引,避免触发排序(重点)

    • 原理:索引的必要性就不多说了,排序会造成额外的内存消耗,且对无索引字段排序会导致全表查询

    • 分析:在无排序的情况下sql会自动选取最优方案(他自己认为最优,一般是覆盖索引)

    • 部分触发排序的函数

      • GROUP BY 子句、ORDER BY 子句
      • 聚合函数(SUM、COUNT、AVG、MAX、MIN)但5.6以后COUNT也使用辅助索引,并不慢!
      • DISTINCT
      • 集合运算符(UNION、INTERSECT、EXCEPT)
      • 窗口函数(RANK、ROW_NUMBER 等)。
    • 解决方案

      • 集合运算符可以使用ALL

        当然前提是不需要去重

      • EXISTS代替DISTINCT

      • order bygroup by或极值字段添加索引

      • 能用where就不用having

      • 将索引字段置于=左侧,并避免进行运算

        会导致放弃索引

      • 避免否定形式,如<>!=NOT IN

        均会放弃索引

      • 进行默认的数据类型转换

        否则会放弃索引

        mybatis可指定数据类型

      • 减少中间表的使用,比如使用haiving

        当然能用where最好。。

      • 对同一个表的多个字段使用in时,可以汇总到一处

        所以为什么不使用exists呢。。

        SELECT *
        FROM student_info
        WHERE id || grade_id
        IN (SELECT id || grade_id
           FROM dormitory_info);
        
      • 用延迟查询(覆盖索引)优化limit

        适用于offset过大情况下

        -- 常规查询
        SELECT id,name FROM student_info WHERE gender=1 LIMIT 100000,10
        -- 优化后,前提是对gender字段添加索引
        SELECT id,name FROM student_info INNER JOIN
        (SELECT id FROM student_info WHERE gender=1 LIMIT 100000,10)
        as x using(id);
        
      • 利用limit 1取得唯一行

        发现结果即停止扫描

      • 注意组合索引的最左匹配原则

        否则将失效

      • 使用like时,仅有符合最左匹配原则的索引生效

        业务要求全匹配那就只能放弃索引了。。

        -- 不生效实例
        SELECT * FROM student_info WHERE NAME LIKE "%叶子"  
        SELECT * FROM student_info WHERE NAME LIKE "%叶子%"
        -- 生效实例
        SELECT * FROM student_info WHERE NAME LIKE "叶子%"
        
      • 尽量用_替换%

        当然前提是业务允许

      • 尽量用自增id作为主键,且尽量保证其小,而且不要修改

        而且为了统一规范也请使用自增id

      • 使用count统计数据

        会自行使用辅助索引

      • 避免使用SELECT *,且尽量对查询结果使用覆盖索引进行优化

        SELECT *可能会回表

        如果查询结果和条件均使用了索引,那性能就更好了

      • 有必要的情况下使用force index()强制使用索引

        非强制索引的时候,MYSQL会自行选择索引,但不一定是你想要的,通常是选择主键id

      • 用批量插入替代逐条插入,更不可在业务层循环插入

        逐条插入会无端增加日志和事务,效率偏低,但见得少

        业务层循环插入是真滴多,效率那就不是一般的低了

    5.补充(重点)

    5.1.善用explain

    explain作为执行情况分析工具,是性能优化的基础,都不知道怎么执行的,那咋优化?

    相关资料很多,建议先打好基础

    5.2.注意mybatis和mybatisPlus对代码进行的调整

    大部分项目会使用mybatismybatisPlus插件,请注意实际执行的语句是否符合预期,可能根本就不是你想要的样子

    相关日志会打印在控制台,推荐使用插件mybatis sql log查看实际执行的sql,IDEA插件市场可以搜到

    5.3.注意避免造成锁表

    主要是基于ACID特性的考虑,数据更新时会锁住相关的数据

    一旦造成锁表,会导致表内数据无法访问,相关操作将会无法执行,请尽可能避免

    试想线上项目某核心操作无法执行,比如下单,长达十几分钟甚至几个小时,会发生什么。。大概率是要准备面试了

    以下两种情况会造成锁表,如有更多情况,欢迎提出补充

    1. insert into...select操作未使用索引,将会导致锁表,建议完全禁止相关操作

      示例:

      INSERT INTO record_new
      SELECT *
      FROM record_old
      WHERE created_time < '2020-08-04 00:00:00'
      

      分析:

      上述语句进行全表扫描的时候,为保持ACID特性,将会锁死record_old表中扫描到的数据,即全部数据,进而导致整个表被锁

      解决方案:

      • 方案1:理论上对条件添加索引即可,但范围查询结果超过总数据的30%(实际约15%),将会放弃索引,依旧全表扫描,所以建议完全禁止相关操作

      • 方案2:可以借助外部工具进行数据迁移

      • 方案3:将表内数据查出,再进行数据更新操作(请通过其他渠道保持ACID特性)

    2. 同一事务执行多个SQL更新操作过程,会将相关数据锁住,如果异常终止,事务没有释放锁,可能导致数据一直被锁住

      示例:事务执行A和B两个sql数据更新操作,执行成功前会将A和B相关的表锁住,若发生意外,如断电、服务崩溃、服务被强制关闭等,可能会导致表一直被锁住

      解决方案:重启mysql服务(反正项目都得重启了。。。)

    BB两句

    SQL是一门很大的学问,虽然大部分时间我们只用到了一小部分,但人毕竟是要进步的,浅尝辄止的做crud,怎能完成我的肥仔大梦(虽然我不肥)


    作者:Echo_Ye

    wx:Echo_YeZ

    email :echo_yezi@qq.com

    个人站点:在搭了在搭了。。。(右键 - 新建文件夹)

  • 相关阅读:
    servlet简介
    synchronized锁的升级过程
    volatile的作用及原理
    redis数据类型
    mysql的主从复制
    redis的缓存穿透,缓存击穿,缓存雪崩
    网络从io到多路复用
    mysql索引
    mysql的执行计划
    mysql 常用函数
  • 原文地址:https://www.cnblogs.com/silent-bug/p/13563191.html
Copyright © 2020-2023  润新知