声明: 1. 本文为我的个人复习总结, 并非那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
2. 由于是个人总结, 所以用最精简的话语来写文章
3. 若有错误不当之处, 请指出
一个SQL语句的执行流程:
连接器 -> 缓存 -> 解析器 -> 优化器 -> 执行器 -> 存储引擎 -> 文件
缓存 不推荐使用, 因为更新缓存时, 一表数据变动, 整表的缓存都失效, 粒度太大
解析器: 词法分析, 语义分析
优化: 优化索引等
存储引擎: 将执行器的指令落实到数据文件上, 有InnoDB, MyISAM, Memory, Archive(档案, 只增不改不删)等
删除操作:
detele 一行一行删除指定行数据, 可回滚
truncate 直接删除表全部数据所以效率高, 不可回滚
drop 删除表结构
MySQL8新特性:
BinLog日志:
MySQL的BinLog三种模式:
STATEMENT 记录更新的操作日志 (对于一些系统函数如Date( ),主从执行结果不一致)
ROW 记录更新后实际的每一行数据
MIXED 混合, 优先用STATEMENT, 若问题再用ROW, 但是这个功能现在实现不完善
主从复制:
主从复制, 读写分离
优点
缺点:
用到的线程:
master: binlog线程, 记录下所有改变了数据库数据的语句,放进master上的binlog中
slave: io线程, 在输入start slave之后,从master上拉取 binlog 内容放进自己的relay log中
slave: sql执行线程, 执行relay log中的语句
三范式:
第一范式: 字段不可再分 , 比如5台电脑这个字段, 可以拆成 商品和数量 两个字段
第二范式: 在第一范式的基础上, 其他字段必须全部依赖主键,而不能部分依赖; 比如(a,b)->y, 那么a就是y的部分的依赖; x->y, x就是y的全部依赖
第三范式: 在第二范式的基础上, 其他字段必须直接依赖主键,而不能间接依赖, 比如a->b->c, 那a就是c的部份依赖
三范式好处: 规范表设计,避免产生冗余字段
坏处:
触发器:
类似于监听器 和 钩子程序, 当你对A表操作时, 触发器可以自动往B表里进行相应的同步(保持相对关系)操作
为什么避免使用外键:
所以, 逻辑上认为它是外键, 但别显示建外键; 约束啥的靠开发人员额外编写应用程序维护, 在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?
SQL执行顺序:
琐碎的知识点:
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种情况:
select 任何值+NULL
和都为NULL
sum(字段) 会忽略空值, avg, count…等聚合函数也一样
sum(*) 不会忽略空值
MyISAM存储引擎,count(*)最高
InnoDB存储引擎, count(*)和count(1)效率差不多,都>count(字段)
因为count(字段)还要每一行都去判断是否为NULL, 所以降低了执行速度
常用函数:
字符函数
concat: 连接
substr: 截取子串
upper: 变大写
lower: 变小写
replace: 替换
length: 获取字节长度
trim: 去前后空格
lpad: 左填充
rpad: 右填充
instr: 获取子串第一次出现的下标数学函数
ceil: 向上取整
round: 四舍五入
mod: 取模
floor: 向下取整
truncate: 截断
rand: 获取随机数,返回0-1之间的小数日期函数
now: 返回当前日期+时间
year: 返回年
month: 返回月
day: 返回日
date_format: 将日期转换成字符
curdate: 返回当前日期
str_to_date: 将字符转换成日期
curtime: 返回当前时间
hour: 小时
minute: 分钟
second: 秒
datediff: 返回两个日期相差的天数
monthname: 以英文形式返回月其他函数
version: 当前数据库服务器的版本
database: 当前打开的数据库
user: 当前用户
password(‘字符’):返回该字符的密码形式
md5(‘字符’): 返回该字符的md5加密形式5、流程控制函数
①if(score>=60,‘success’,‘fail’)
②case两种写法:
case 变量或表达式或字段
when 常量1 then 值1
when 常量2 then 值2
…
else 值n
endcase
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级别
查看系统变量
show (global/session) variables like ‘xxx’
select @@(global/session).变量名 / select @@变量名
为系统变量赋值
set **@@global.**变量名=值;
set @@变量名=值
或 set (global/session) 变量名=值
二、自定义变量
都是session级别
位置:begin end里面,也可以放在外面
使用:
①声明并赋值 / 更新:
②更新值
另一种方式: select xxx into @变量名 from 表
③查看变量
select @变量名
局部变量:
只能放在begin end中,而且只能放在第一句
存储过程&函数:
存储过程既可以读, 又可以写
但一般函数用作读, 存储过程用作写
一、创建
create procedure 存储过程名(参数模式 参数名 参数类型) begin 存储过程体 end
注意:
- 参数模式:in、out、inout,其中in可以省略
- 存储过程体的每一条sql语句都需要用分号结尾
二、调用
call 存储过程名(实参列表)
调用in模式的参数:call sp1(’@name’);
调用out模式的参数:
set @name; call sp1(@name); select @name;
调用inout模式的参数:
set @name=值; call sp1(@name); select @name;
三、查看
show create procedure 存储过程名;
四、删除
drop procedure 存储过程名;
一、创建
create function 函数名(参数名 参数类型) returns 返回类型 begin 函数体 end
注意:函数体中肯定需要有return语句
二、调用
select 函数名(形参列表);三、查看
show create function 函数名;
四、删除
drop function 函数名;
ACID:
insert、update、delete本身就是一种隐式的事务,只不过它们是单条命令且自动提交
事务操作:
开启事务
set autocommit=0;
(begin) 或者 (start transaction) 可以省略
设置回滚点(非必需): savepoint 回滚点名
提交:commit
回滚:rollback
rollback to 回滚点名
并发事务问题: 多个事务 同时 操作 同一个数据库的相同数据时会引发问题:
隔离级别:
脏读 不可重复读 幻读
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) 表级锁,悲观锁; 是取得共享锁/排他锁的前置条件
意向锁和意向锁之间是不会有竞争冲突的
(读)共享锁/(写)排他锁(S/X) 行级锁,悲观锁
读锁会阻塞写,但是不会阻塞读。而写锁则会把其他线程的读和写都阻塞
S锁和IS锁之间是不会有竞争冲突的, S锁和S锁之间也不会有竞争冲突
其他的 X & X, X & IX, X& IS, S & IX 都是有竞争冲突的
插入意向锁(Insert Intention Locks) 特殊的间隙锁,悲观锁
自增锁(Auto-inc Locks) 表级锁;
AUTO_INCREMENT自增字段, 是一种特殊的间隙锁,但不同于间隙锁的是,该锁只用于并发插入操作。
如果说间隙锁锁住的是一个区间,那么插入意向锁锁住的就是一个点。
划分锁:
MySQL 如何实现悲观锁和乐观锁?
select...lock in share mode
, select…for update
MySQL如何解决不可重复读?
用MVCC(基于乐观锁)来解决!
日志:
- undolog日志 (回滚)
- redolog日志 (事务内记录日志操作(BinLog是事务提交后), 实现重做操作,保证事务的持久性)
MVCC在每条数据后面加了隐藏的两列(创建版本号和删除版本号),每个事务在开始的时候都会有一个递增的当前事务版本号
为了避免查询到旧数据或已经被其他事务更改过的数据,需要满足如下条件
查询时当前事务的版本号需要>=创建版本号create_version
查询时当前事务的版本号<删除的版本号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 update
、select ... 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
如何避免死锁:
索引, 是一类数据结构, 使无序数据变得有序可寻, 将随机IO变成顺序O, 避免全表扫描, 提高查找效率
索引缺点:
没用上索引时会导致:
索引数据结构:
B树B+树同时也是平衡树, 是2-3树(有的节点存两个值,有3个孩子) 是为了降低树的高度, 使查询更快, 节约IO损耗
红黑树是一种平衡不是那么严格的平衡搜索树, 是二叉树, 树的高度较高, 查询慢些, IO损耗大, 所以不适合作为索引结构
InnoDB存储引擎, 有自适应哈希索引的特点; 比如精确查找时它会使用哈希索引, 范围查询时使用B+树索引
哪些情况下适合建索引:
哪些情况下不适合建索引:
索引的分类:
约束:
非空约束
唯一约束
主键约束
外键约束
自增约束
默认约束 有默认初始化值
检查性约束 CHECK 保证列中的数据必须符合指定的条件;
ALTER TABLE tb_emp7 ADD CONSTRAINT <检查约束名> CHECK(<检查约束>)
聚簇索引和非聚簇索引:
在InnoDB里, 主索引是聚簇索引, 二级索引/辅助索引 是非聚簇索引
在MyISAM里, 主索和二级索引 全都是非聚簇索引
基于InnoDB讨论:
存储位置特点上: 将数据存储与索引放到一起, 将数据与索引分开存储
查询时: 如果使用辅助索引查数据,实质是先找主索引,再通过主键索引查找其他要select的字段(即回表查询, 增大了IO次数)
当要select的字段恰好仅是主索引字段时,便不需要再全表扫描了(即覆盖索引了)
主索引要用的字段:
一个表中只能有1个聚簇索引
聚簇索引的优点:
可以把相关数据保存在一起,不用找两次, 减少磁盘的IO
为什么主键(聚簇索引)建议使用自增id?
利于查找相邻数据, 寻址方便, 只要索引是相邻的,那么在磁盘上索引对应的行数据存放地址也是相邻的
为什么聚簇索引不建议使用自增id?
不要使用UUID方式,因为UUID的值离散太过极端,不适合排序, IO寻址起来麻烦
索引失效:
如果a>1 or b<2的a命中索引,而b没命中索引,那便会索引失效全表扫描,
因为MySQL觉得 即便a使用了索引,但b还是要全表扫描, 所以干脆全都全表扫描一遍
模糊查询时, 以%作为开头
根据最左前缀原则, (a,b,c),前面的字段没用上索引,后面的就也不能用了
使用!= 或者 <> 导致索引失效
类型不一致导致索引失效
函数导致的索引失效, 如 WHERE DATE(create_time) = ‘2020-09-03’;
运算符导致的索引失效, 如WHERE age - 1 = 20;
NOT IN、NOT EXISTS导致索引失效
覆盖索引: 是指你查找的字段恰好就是索引字段, 直接就找到了
前缀索引: 因为一些字段数据太长, 为节省内存 所以用前缀部分当一个大概的索引, 前缀长度的确定可由
select count (distinct(left(str, length)))/count(*) 来确定length多少合适,使区分度较高
索引下推:
类似于谓词下推, 不过谓词下推针对的是自己编写sql 提前用where过滤; 而索引下推,是基于存储引擎提早执行多个过滤条件, 而不是返回给了MySQL服务端再进一步过滤
这个功能是默认开启的
优点: 尽早排除非法数据从而减少没必要的额外操作(比如尽量减少回表查询), 减少IO消耗
explain 出来的表结构里的 重要列
type 判断索引是否高效命中
性能关系: ALL < index < range ~ index_merge < ref < eq_ref < const < system
const(主键索引 或者 唯一二级索引 进行等值匹配的情况下)
ref(普通的 二级索引列与常量 进行等值匹配)
index(扫描全表索引的覆盖索引)
All(全表扫描数据)
key 当前查询用到了哪种索引
rows 预估值要扫描多少行
Extra 额外信息, 比如是否using where之类的
谓词下推: 能过滤则先过滤
只select 必需的字段, 不用select *
小表驱动(join)大表: 新版本已经会自动优化这个了; 大表 left join 小表时(根据左边去查找右边), 左边大表索引用不到, 右边小表才能用到, 把索引命中放在了小表上 吃亏了
exists和in:
exists 适合子查询中表数据较大时:
是对外表做loop循环,每次loop循环再对内表(子查询)进行查询,那么内表的查询能使用到索引,内表是 大表时大量使用了索引优势
in 适合外部表数据较大时:
in是把外表和内表做hash连接, 先查询内表, 再把内表结果与外表匹配,对外表使用索引,外表是 大表时大量使用了索引优势
而not in 和 not exists时,都用不到索引
对于not exists,内表有空值对查询结果没有影响, 外表有空值时则这条记录最终会输出
对于not in, 内表有空值将导致最终的查询结果为空, 外表有空值时则这条记录最终将不被输出
索引优化: 调整字段顺序使尽量满足符合最左前缀原则能用上索引