MySQL相关知识【索引, 锁, 事务...】(个人总结)

    声明: 1. 本文为我的个人复习总结, 并那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
              2. 由于是个人总结, 所以用最精简的话语来写文章
              3. 若有错误不当之处, 请指出

一、基础:

一个SQL语句的执行流程:

连接器 -> 缓存 -> 解析器 -> 优化器 -> 执行器 -> 存储引擎 -> 文件

缓存 不推荐使用, 因为更新缓存时, 一表数据变动, 整表的缓存都失效, 粒度太大

解析器: 词法分析, 语义分析

优化: 优化索引等

存储引擎: 将执行器的指令落实到数据文件上, 有InnoDB, MyISAM, Memory, Archive(档案, 只增不改不删)等

删除操作:

detele 一行一行删除指定行数据, 可回滚

truncate 直接删除表全部数据所以效率高, 不可回滚

drop 删除表结构

MySQL8新特性:

  1. 支持开窗
  2. with t1 as select … 语句,相当于临时表
  3. DDL语句也支持事务了

BinLog日志:

MySQL的BinLog三种模式:

  1. STATEMENT 记录更新的操作日志 (对于一些系统函数如Date( ),主从执行结果不一致)

  2. ROW 记录更新后实际的每一行数据

  3. MIXED 混合, 优先用STATEMENT, 若问题再用ROW, 但是这个功能现在实现不完善

主从复制:

主从复制, 读写分离

优点

  1. 提高了并发性能
  2. 从机对主机相当于有备份 提高了数据安全性, 提供了高可用 高可靠保障

缺点:

  1. 数据一致性问题
  2. 延时问题

用到的线程:

master: binlog线程, 记录下所有改变了数据库数据的语句,放进master上的binlog中

slave: io线程, 在输入start slave之后,从master上拉取 binlog 内容放进自己的relay log中

slave: sql执行线程, 执行relay log中的语句

三范式:

  1. 第一范式: 字段不可再分 , 比如5台电脑这个字段, 可以拆成 商品和数量 两个字段

  2. 第二范式: 在第一范式的基础上, 其他字段必须全部依赖主键,而不能部分依赖; 比如(a,b)->y, 那么a就是y的部分的依赖; x->y, x就是y的全部依赖

  3. 第三范式: 在第二范式的基础上, 其他字段必须直接依赖主键,而不能间接依赖, 比如a->b->c, 那a就是c的部份依赖

三范式好处: 规范表设计,避免产生冗余字段

坏处:

  1. 一些冗余字段可以提高效率,比如统计月销售额,难道每一遍都sum一下? 而冗余字段sumGMV, 可以做一个累加记录,查月销售额时直接select sumGMA where 某一行就可以了
  2. 表拆分的太散,维度表大大增加, join维度表时时很耗时耗资源的

触发器:

类似于监听器 和 钩子程序, 当你对A表操作时, 触发器可以自动往B表里进行相应的同步(保持相对关系)操作

为什么避免使用外键:

  1. 在主表从表没有全部更新完时, 这行数据被锁住, 降低并发
  2. 外键字段不可使用触发器进行监听
  3. 迁移数据库时, 如把MySQL迁移到HBase; 而HBase不支持外键,这时没有外键约束了,而又没有开发人员额外编写应用程序维护,就会出问题

所以, 逻辑上认为它是外键, 但别显示建外键; 约束啥的靠开发人员额外编写应用程序维护, 在JavaEE的Service层里去校验是否约束合法

MySQL中MyISAM与InnoDB的区别:

  • 事务: InnoDB支持事务,而MyISAM不支持事务

  • 乐观锁: InnoDB支持MVCC, 而MyISAM不支持

  • 外键: InnoDB支持外键,而MyISAM不支持

  • 锁粒度: InnoDB支持行级锁,而MyISAM支持表级锁

  • 聚簇索引方面: 在InnoDB里, 主索引是聚簇索引, 二级索引/辅助索引 是非聚簇索引

    ​ 在MyISAM里, 主索和二级索引 全都是非聚簇索引

主键一般用自增ID还是UUID?

使用自增ID的好处:

  • 字段长度较uuid会小很多。
  • 数据库自动编号,按顺序存放,利于检索

使用自增ID的缺点:

  • 因为是自增,在某些业务场景下,容易被其他人查到业务量。
  • 竞争自增锁会降低数据库的吞吐能力

UUID:通用唯一标识码,是基于当前时间、计数器和硬件标识等数据计算生成的。

使用UUID的优点:

  • 无需担心业务量泄露的问题。
  • 可以在应用层生成,提高数据库的吞吐能力。

使用UUID的缺点:

  • 因为UUID是随机生成的,所以会发生随机IO,查找时不方便
  • UUID占用空间较大
  • UUID之间比较大小 比 自增ID的比较大小慢

一般情况MySQL主键为自增ID(username设成unique key, 用额外的自增id作为主键)

因为在MySQL的InnoDB存储引擎中,主键索引是一种聚簇索引;

主键索引的B+树的叶子节点按照顺序存储了主键值及数据,

如果主键索引是自增ID,只需要按顺序往后排列即可顺序IO,

而UUID太离散不利于排序和 查找,只能随机IO

字段为什么要设置成not null?

  • NULL占用空间, 空串不占用空间, 所以优先用not null + 空串 的方式
  • NULL值会影响一些函数的统计, 如count遇到NULL值, 这条记录不会统计在内
  • NOT IN子查询 在有NULL值的情况下, 返回的结果都是空值

SQL执行顺序:

MySQL相关知识【索引, 锁, 事务...】(个人总结)_第1张图片

二、SQL编写:

琐碎的知识点:

  • ON DUPLICATE KEY,没有就插入, 否则就更新
INSERT INTO app_use_report (device_id)
VALUES (1),
ON DUPLICATE KEY
UPDATE device_id= VALUES(2)
  • 分组前会先排序

  • union会去重, 同一个表的元素也会去重

  • group by有去重功能

  • distinct 只能对后面紧跟的一个字段去重, 跟了两个时其实只对第一个有效; 想去重多个字段就用group by

  • =用来判断非NULL值才有效

  • <=>是既能判断NULL值,又能判断非NULL值

  • 函数可以嵌套使用, 如count(if(score>=60,1,0))

  • 分组后的select字段, 只能为3种情况:

    1. 分组字段
    2. 聚合函数
    3. 常量表达式, 如 select 1+2 from …
  • select 任何值+NULL 和都为NULL

  • sum(字段) 会忽略空值, avg, count…等聚合函数也一样

  • sum(*) 不会忽略空值

  • MyISAM存储引擎,count(*)最高
    InnoDB存储引擎, count(*)和count(1)效率差不多,都>count(字段)

    因为count(字段)还要每一行都去判断是否为NULL, 所以降低了执行速度

常用函数:

  1. 字符函数
    concat: 连接
    substr: 截取子串
    upper: 变大写
    lower: 变小写
    replace: 替换
    length: 获取字节长度
    trim: 去前后空格
    lpad: 左填充
    rpad: 右填充
    instr: 获取子串第一次出现的下标

  2. 数学函数
    ceil: 向上取整
    round: 四舍五入
    mod: 取模
    floor: 向下取整
    truncate: 截断
    rand: 获取随机数,返回0-1之间的小数

  3. 日期函数

    now: 返回当前日期+时间
    year: 返回年
    month: 返回月
    day: 返回日
    date_format: 将日期转换成字符
    curdate: 返回当前日期
    str_to_date: 将字符转换成日期
    curtime: 返回当前时间
    hour: 小时
    minute: 分钟
    second: 秒
    datediff: 返回两个日期相差的天数
    monthname: 以英文形式返回月

  4. 其他函数
    version: 当前数据库服务器的版本
    database: 当前打开的数据库
    user: 当前用户
    password(‘字符’):返回该字符的密码形式
    md5(‘字符’): 返回该字符的md5加密形式

5、流程控制函数

①if(score>=60,‘success’,‘fail’)
②case两种写法:

  1. case 变量或表达式或字段
    when 常量1 then 值1
    when 常量2 then 值2

    else 值n
    end

  2. case
    when 条件1 then 值1
    when 条件2 then 值2

    else 值n
    end

视图:

视图相当于临时表, 但只能读

-- 创建视图
create view 视图名
as
查询语句;

-- 修改视图
方式一:
create or replace view 视图名
as
查询语句;alter view 视图名
as
查询语句

-- 删除视图
drop view 视图1,视图2,...;

-- 查看视图
desc 视图名;
show create view 视图名;

变量:
一、系统变量

默认是session级别

  1. 查看系统变量

    show (global/session) variables like ‘xxx’

​ select @@(global/session).变量名 / select @@变量名

  1. 为系统变量赋值

    set **@@global.**变量名=值;
    set @@变量名=值

    或 set (global/session) 变量名=值

二、自定义变量
都是session级别
位置:begin end里面,也可以放在外面
使用:

①声明并赋值 / 更新:

  1. set @变量名=值
  2. set @变量名:=值
  3. select @变量名:=值

②更新值
另一种方式: select xxx into @变量名 from 表

③查看变量
select @变量名

局部变量:
只能放在begin end中,而且只能放在第一句

存储过程&函数:

存储过程既可以读, 又可以写

但一般函数用作读, 存储过程用作写

  1. 存储过程:

一、创建

create procedure 存储过程名(参数模式 参数名 参数类型)
begin
		存储过程体
end

注意:

  1. 参数模式:in、out、inout,其中in可以省略
  2. 存储过程体的每一条sql语句都需要用分号结尾

二、调用
call 存储过程名(实参列表)

  1. 调用in模式的参数:call sp1(’@name’);

  2. 调用out模式的参数:

    set @name; 
    call sp1(@name);
    select @name;
    
  3. 调用inout模式的参数:

    set @name=; 
    call sp1(@name); 
    select @name;
    

三、查看
show create procedure 存储过程名;
四、删除
drop procedure 存储过程名;

  1. 函数:

一、创建

create function 函数名(参数名 参数类型) returns  返回类型
begin
	函数体
end

注意:函数体中肯定需要有return语句

二、调用
select 函数名(形参列表);

三、查看
show create function 函数名;
四、删除
drop function 函数名;

三、事务:

ACID:

  1. 原子性 这里的原子并非原语操作, 而是说事务里的多个命令同生共死, 要么都执行, 要么都不执行
  2. 一致性 一个事务可以使数据 从一个一致状态切换到另外一个一致的状态, 比如转账时 总金额不变
  3. 隔离性 一个事务不受其他事务的干扰,多个事务互相隔离的
  4. 持久性 一个事务一旦提交了,则永久的持久化到本地, 不可撤销

insert、update、delete本身就是一种隐式的事务,只不过它们是单条命令且自动提交

事务操作:

  1. 开启事务

    set autocommit=0;
    (begin) 或者 (start transaction) 可以省略

  2. 设置回滚点(非必需): savepoint 回滚点名

  3. 提交:commit
    回滚:rollback

    rollback to 回滚点名

并发事务问题: 多个事务 同时 操作 同一个数据库的相同数据时会引发问题:

  1. 脏读: 一个事务读取了其他事务还没有提交的数据
  2. 不可重复读: 一个事务多次读取,结果不一样, 感知到了其他事务"update"的数据
  3. 幻读: 感知到了其他事务"insert"的数据

隔离级别:
脏读 不可重复读 幻读
read uncommitted:读未提交 × × ×

read committed: 读已提交 √ × ×
repeatable read(默认): 可重复读 √ √ ×
serializable: 串行化 √ √ √

四、锁:

常见的是7种锁,还有一种不常见的预测锁

  • 行锁(Record Locks) 行级锁,悲观锁

    单个行记录上的锁

    被锁的字段必需得建立了索引并且是主键索引或唯一索引, 否则会退化成临键锁

    查询语句必须为精准匹配=,不能为>、<、like等,否则也会退化成临键锁

    若语句中没用到任何索引,则会退化成表锁

    select * from user where id = 1 for update

  • 间隙锁(Gap Locks) 行级锁,悲观锁

    锁定一个区间,但不包括记录本身

    被锁的字段必需得建立了唯一索引, 否则会退化成临键锁

    若语句中没用到任何索引,则会退化成表锁

    select * from user where id betwen 1 and 10 for update;

    锁定了(1,10)区间

  • 临键锁(Next-key Locks) 行级锁,悲观锁

    锁定一个区间,并且锁定记录本身

    被锁的字段必需得建立了非唯一索引

    若语句中没用到任何索引,则会退化成表锁

    如已有age={10,24,32,45}时潜在的临键锁: (-∞, 10], (10, 24], (24, 32], (32, 45], (45, +∞]

    操作age=24, select * from user where age = 24 for update时, InnoDB 会获取该记录行的临键锁 (10,24], 左开右闭

    临建锁=特殊间隙锁+行锁, 且间隙不是直接指定,而是根据表中已有的数据间接自动指定的

  • 意向共享锁/意向排他锁(IS/IX) 表级锁,悲观锁; 是取得共享锁/排他锁的前置条件

    • (IS)意向共享锁 Intention Shared Lock:当事务准备在某条记录上加S锁时,需要先在表级别加一个IS锁。
    • (IX)意向排他锁 Intention Exclusive Lock:当事务准备在某条记录上加X锁时,需要先在表级别加一个IX锁。

    意向锁和意向锁之间是不会有竞争冲突的

  • (读)共享锁/(写)排他锁(S/X) 行级锁,悲观锁

    • 共享锁是一个事务并发读取某一行记录所需要持有的锁。针对同一份数据,多个读操作可以同时进行而不会互相影响;
    • 排他锁是一个事务并发更新或删除某一行记录所需要持有的锁。当前写操作没有完成前,它会阻断其他写锁和读锁;

    读锁会阻塞写,但是不会阻塞读。而写锁则会把其他线程的读和写都阻塞

    S锁和IS锁之间是不会有竞争冲突的, S锁和S锁之间也不会有竞争冲突

    其他的 X & X, X & IX, X& IS, S & IX 都是有竞争冲突的

  • 插入意向锁(Insert Intention Locks) 特殊的间隙锁,悲观锁

  • 自增锁(Auto-inc Locks) 表级锁;

    AUTO_INCREMENT自增字段, 是一种特殊间隙锁,但不同于间隙锁的是,该锁只用于并发插入操作。

    如果说间隙锁锁住的是一个区间,那么插入意向锁锁住的就是一个

划分锁:

  • 锁粒度来分:(锁定粒度依次递增, 锁开销递增, 并发度递减)
    • 行级锁 InnoDB 中的行级锁的实现依赖于索引,一旦加锁操作没用到索引,那么就会退化为表锁
      1. 行锁
      2. 间隙锁
      3. 临键锁
    • 页级锁 一次锁定相邻的一组记录
    • 表级锁
  • 共享策略来分:
    • 共享锁
    • 排他锁
    • 意向共享锁
    • 意向排他锁
  • 按 加锁策略来分:
    • 乐观锁
    • 悲观锁
  • 其他:
    • 自增锁

MySQL 如何实现悲观锁和乐观锁?

  • 乐观锁:更新时带上版本号(CAS更新, MVCC)
  • 悲观锁:使用共享锁和排它锁,select...lock in share mode, select…for update

MySQL如何解决不可重复读?

用MVCC(基于乐观锁)来解决!

日志:

  • undolog日志 (回滚)
  • redolog日志 (事务内记录日志操作(BinLog是事务提交后), 实现重做操作,保证事务的持久性)

MVCC在每条数据后面加了隐藏的两列(创建版本号和删除版本号),每个事务在开始的时候都会有一个递增的当前事务版本号

为了避免查询到旧数据或已经被其他事务更改过的数据,需要满足如下条件

  1. 查询时当前事务的版本号需要>=创建版本号create_version

  2. 查询时当前事务的版本号<删除的版本号delete_version,或者当前删除版本号delete_version=NULL

即 (create_version <= current_version < delete_version)

或 (create_version <= current_version && delete_version-=NULL)

MySQL如何解决幻读?

幻读问题:

在T2 insert 并提交 后, T1直接select是不会查到新数据, 这里没有幻读问题
在T2 insert 并提交 后, T1 update可以看到跟新数据有关,这里有幻读问题, 并且之后的select就可以看到上次update时同步的最新数据

  • 快照读:生成一个事务快照(ReadView),之后都从这个快照获取数据。普通 select 语句就是快照读。

    对于快照读,MVCC因为从ReadView读取, 所以必然不会看到新插入的行, 所以就解决了幻读的问题

  • 当前读:读取数据的最新版本。常见的 update/insert/delete、还有 select ... for updateselect ... lock in share mode 都是当前读

    MVCC 是无法解决的, 需要使用悲观锁 Next-Key Lock 或 Gap Lock (MySQL根据sql语句 自动选择用哪种锁)来解决

    select * from user where id < 10 for update 排他锁

    当使用了 Gap Lock 时,Gap 锁会锁住 id < 10 的整个范围,因此其他事务无法插入 id < 10 的数据,从而防止了幻读。

死锁:

发生条件: 两个事务互相把持对方的锁, 且都不愿意释放已有的锁, 对于内层新的锁 也不愿意tryLock而是一根筋非要Lock

如何避免死锁:

  1. 不同事务读取相同的多张表时, 按顺序读取
  2. 使用较低的隔离级别
  3. 合理选择事务的大小
  4. 尽量命中索引, 使行级锁能工作,缩小锁的粒度减少冲突

五、索引:

索引, 是一类数据结构, 使无序数据变得有序可寻, 将随机IO变成顺序O, 避免全表扫描, 提高查找效率

索引缺点:

  • 索引要占用磁盘空间, 而且还较大
  • 当对表的数据进行增删改的时候,也要修改索引,执行速度受到影响

没用上索引时会导致:

  1. 查询慢
  2. 行锁退化为表锁(InnoDB的行锁是基于索引进行实现的)

索引数据结构:

  1. 哈希索引(InnoDB用到): 精确查找最快, 但范围查找模糊查找时由于HashCode没规律, 就只能全表扫描了
  2. B树索引: 多叉树查找, 相当于二分或三分查找, 但是范围查找时要退回到父级节点再找兄弟结点, 回溯效率低, 非叶子节点也存数据
  3. B+树索引(InnoDB用到): 在B树的基础上, 只有叶子结点存实际数据, 叶子节点间构成了一个单链表, 范围查找很快; 缺点是树的高度比B树略高

B树B+树同时也是平衡树, 是2-3树(有的节点存两个值,有3个孩子) 是为了降低树的高度, 使查询更快, 节约IO损耗

红黑树是一种平衡不是那么严格的平衡搜索树, 是二叉树, 树的高度较高, 查询慢些, IO损耗大, 所以不适合作为索引结构

InnoDB存储引擎, 有自适应哈希索引的特点; 比如精确查找时它会使用哈希索引, 范围查询时使用B+树索引

哪些情况下适合建索引:

  1. 频繁作为查询条件的字段
  2. 与其他表关联的, 逻辑上的外键这种字段
  3. 要排序的字段
  4. 要分组的字段

哪些情况下不适合建索引:

  1. 表很小时
  2. 经常增删改的表或字段
  3. where过滤里用不到的字段
  4. 过滤性不好的字段, 如sex, 不是男就是女

索引的分类:

  1. 主键索引: 非空, 唯一
  2. 唯一索引: 唯一,但允许有空值(Null),但只允许有一个空值(Null)
  3. 单列索引: 又叫单值索引、普通索引
  4. 复合索引:一个索引可以包含多个列,多个列共同构成一个复合索引!全都命中时效率>多个单列索引
  5. 全文索引:Full Text, 支持全文查找(5.6版本InnoDB开始支持, MyISAM的话早就支持了)

约束:

  • 非空约束

  • 唯一约束

  • 主键约束

  • 外键约束

  • 自增约束

  • 默认约束 有默认初始化值

  • 检查性约束 CHECK 保证列中的数据必须符合指定的条件;

    ​ ALTER TABLE tb_emp7 ADD CONSTRAINT <检查约束名> CHECK(<检查约束>)

聚簇索引和非聚簇索引:

在InnoDB里, 主索引是聚簇索引, 二级索引/辅助索引 是非聚簇索引

在MyISAM里, 主索和二级索引 全都是非聚簇索引

基于InnoDB讨论:

  1. 存储位置特点上: 将数据存储与索引放到一起, 将数据与索引分开存储

    • 聚簇索引: 叶子结点存储整行的数据, 将数据和索引存储到了一起
    • 非聚簇索引: 叶子结点存储主键索引, 数据和索引分开存储
  2. 查询时: 如果使用辅助索引查数据,实质是先找主索引,再通过主键索引查找其他要select的字段(即回表查询, 增大了IO次数)

    当要select的字段恰好仅是主索引字段时,便不需要再全表扫描了(即覆盖索引了)

    主索引要用的字段:

    1. 默认是主键
    2. 如果表中没有主键索引, 那么InnoDB会选择一个唯一且非空的索引代替主键作为主索引
    3. 如果还没有, 那InnoDB就会隐式定义一个主键(类似于Oracle中的RowId)来做为主索引

    一个表中只能有1个聚簇索引

聚簇索引的优点:

可以把相关数据保存在一起,不用找两次, 减少磁盘的IO

为什么主键(聚簇索引)建议使用自增id?

利于查找相邻数据, 寻址方便, 只要索引是相邻的,那么在磁盘上索引对应的行数据存放地址也是相邻的

为什么聚簇索引不建议使用自增id?

不要使用UUID方式,因为UUID的值离散太过极端,不适合排序, IO寻址起来麻烦

索引失效:

  1. 如果a>1 or b<2的a命中索引,而b没命中索引,那便会索引失效全表扫描,

    因为MySQL觉得 即便a使用了索引,但b还是要全表扫描, 所以干脆全都全表扫描一遍

  2. 模糊查询时, 以%作为开头

  3. 根据最左前缀原则, (a,b,c),前面的字段没用上索引,后面的就也不能用了

  4. 使用!= 或者 <> 导致索引失效

  5. 类型不一致导致索引失效

  6. 函数导致的索引失效, 如 WHERE DATE(create_time) = ‘2020-09-03’;

  7. 运算符导致的索引失效, 如WHERE age - 1 = 20;

  8. NOT IN、NOT EXISTS导致索引失效

覆盖索引: 是指你查找的字段恰好就是索引字段, 直接就找到了

前缀索引: 因为一些字段数据太长, 为节省内存 所以用前缀部分当一个大概的索引, 前缀长度的确定可由

​ select count (distinct(left(str, length)))/count(*) 来确定length多少合适,使区分度较高

索引下推:

类似于谓词下推, 不过谓词下推针对的是自己编写sql 提前用where过滤; 而索引下推,是基于存储引擎提早执行多个过滤条件, 而不是返回给了MySQL服务端再进一步过滤

这个功能是默认开启的

优点: 尽早排除非法数据从而减少没必要的额外操作(比如尽量减少回表查询), 减少IO消耗

六、执行计划:

explain 出来的表结构里的 重要列

  1. type 判断索引是否高效命中

    性能关系: ALL < index < range ~ index_merge < ref < eq_ref < const < system

    const(主键索引 或者 唯一二级索引 进行等值匹配的情况下)

    ref(普通的 二级索引列与常量 进行等值匹配)

    index(扫描全表索引的覆盖索引)

    All(全表扫描数据)

  2. key 当前查询用到了哪种索引

  3. rows 预估值要扫描多少行

  4. Extra 额外信息, 比如是否using where之类的

七、优化:

  1. 谓词下推: 能过滤则先过滤

  2. 只select 必需的字段, 不用select *

  3. 小表驱动(join)大表: 新版本已经会自动优化这个了; 大表 left join 小表时(根据左边去查找右边), 左边大表索引用不到, 右边小表才能用到, 把索引命中放在了小表上 吃亏了

  4. exists和in:

    exists 适合子查询中表数据较大时:

    ​ 是对外表做loop循环,每次loop循环再对内表(子查询)进行查询,那么内表的查询能使用到索引,内表是 大表时大量使用了索引优势

    in 适合外部表数据较大时:

    ​ in是把外表和内表做hash连接, 先查询内表, 再把内表结果与外表匹配,对外表使用索引,外表是 大表时大量使用了索引优势

    而not in 和 not exists时,都用不到索引

    对于not exists,内表有空值对查询结果没有影响, 外表有空值时则这条记录最终会输出

    对于not in, 内表有空值将导致最终的查询结果为空, 外表有空值时则这条记录最终将不被输出

  5. 索引优化: 调整字段顺序使尽量满足符合最左前缀原则能用上索引

你可能感兴趣的:(MySQL,数据库,database)