MYSQL
MYSQL语法
COUNT主键和COUNT非主键结果有什么不同?
- COUNT()返回的是统计字段不为空的值
- 主键不为空,所以COUNT主键返回的是所有行记录
- COUNT字段可能为空,COUNT字段统计的是该字段非空的
- 性能排名 COUNT(1)=COUNT(*)>COUNT(主键字段)>COUNT(字段)
- COUNT(1)=COUNT(*) 走二级索引,不用判断字段是否为空;COUNT(主键字段)走二级索引,COUNT(字段)走主键索引,全表扫描
内连接和外连接有什么区别?
-
内连接返回两个表匹配(on)的数据: 可以用TABLE1 JOIN TABLE2 ON ,也可以直接WHERE
SELECT FROM TABLE1 JOIN TABLE2 ON CONDITION WHERE GROUP BY HAVING -
左外连接:LEFT JOIN ON
-
右外连接:RIGHT JOIN ON
连接时,WHERE 和ON的区别?
- 内连接时候,WHERE等价于ON
- 外连接时,ON在连接操作时过滤,WHERE在连接操作之后过滤
WHERE和HAVING有什么区别?
- WHERE在分组和聚合函数之前过滤
- HAVING在分组和聚合函数之后过滤
DELETE DROP TRUNCATE的区别?
- DELETE 删除表中行记录,可回滚
- DROP 删除表
- TRUNCATE 清除表中所有数据, 类似于REDIS中的FLUSHALL
- 性能上 DROP>TRUNCATE>DELETE
联合查询中UNION和UNIONALL的区别?
SELECT * FROM a
UNION/UNIONALL
SELECT * FROM b
- UNION 会删除重复记录
- UNIONALL 不会删除重复记录
存储引擎
说一下执行一条MYSQL的全部过程?
- 客户端发起SQL请求
- 连接器负责建立连接、校验用户身份、接受SQL语句
- 查询缓存,如果命中就直接返回,如果没有命中就继续执行,在MYSQL8以上版本,取消了这一步骤
- 解析器对MYSQL语句进行语法分析和词法分析,构建语法树
- 优化器会概率计算执行成本,选择成本最小的方案执行
- 执行器根据执行计划执行语句,从存储引擎获取记录
MYSQL的存储引擎有哪些
- INNODB MYSQL的默认引擎,支持事务、行级锁、日志、回滚等,支持聚簇索引,采用索引组织表,索引和数据存在同一个文件中【B+树叶子节点存储索引+数据,适合更新
- MYISAM 只支持表锁,锁的粒度大,不支持事务,采用堆表,不支持聚簇索引,索引和数据分开存储【B+树叶子节点存储索引+数据地址,适合查询
- MEMORY 数据存在内存中,不具备持久性
COUNT(*)在MYISAM和INNODB下哪个好?
- 如果没有WHERE条件:MYISAM好,因为MYISAM由表级锁直接维护了一个META信息记录总长度
- 如果由WHERE条件,则两个都差不多
MYSQL中的行数据
每条行记录主要包含字段:变长字段列表、NULL值字段列表、记录头信息、ROW_ID(行id)、TRX_ID(事务id)、ROLL_PTR(MVCC版本日志回滚指针)、ENRTY1、ENTRY2.......
NULL值是如何存储的?
每条行记录中会有至少1B的大小记录空值情况,每列占1BIT,1就是NULL,0就是非空,当全部为NOT NULL时 ,这部分字段就不存在,节省1B空间。
CHAR和VARCHAR有什么区别?
- CHAR是固大小字符串,在结构上没有可变字段列表,比如CHAR(10),即使只占用了B,也会补充空格成占了10 B
- VARCHAR是可变大小字符串,在结构上有1-2B的可变字段列表,VARCHAR(10)如果只占用了B,那么它就只会占用5B+1-2B的可变字段列表
- VARCHAR比CHAR更节约内存
对于占用5B的数据,VARCHAR(10)和VARCHAR(100)的区别?
在结构上包含可变字段列表,所以实际占用磁盘空间是5B+(1-2)B可变字段,但是慷慨是不明智的,因为MYSQL通常会分配固定大小的内存去保存内部值,更长的列MYSQL会分配更大的内存,在排序时也会占用更大的内存。
假如硬件内存特别大,MYSQL内存能否替代REDIS?
不能
- 在查询效率上,MYSQL需要先访问页表,最好情况在有索引的情况下 访问B+树的节点,加载进内存,而REDIS天然就是面向内存设计的NOSQL,并且有很多如SKIPLIST等高效的数据结构,在这面MYSQL不如REDIS
- MYSQL是面向磁盘页设计设计的数据库,更新操作要加锁,REDIS不要,另外MYSQL为了持久化要刷盘REDOLOG日志和BINLOG日志,REDIS可以选择不持久化。
索引
索引分类
- 按数据结构划分主要有:B+树索引、HASH索引、FULL-TEXT全文索引
- 按物理存储划分主要有:聚簇索引(主键和完整数据),二级索引(指定列和主键)
- 按字段特性划分主要有:主键索引,唯一索引,普通索引,前缀索引(长列只包含前缀关键部分)
- 按字段个数划分有:单列索引,联合索引(多个指定字段+主键)
INNODB的索引是什么?
B+树和FULL-TEXT索引
MYSQL为什么使用B+树?
- 相比较于平衡二叉树和红黑树,B+树是多叉树,它需要规定关键字叉树最少n/2,所以B+树更短,磁盘I/O次数也会更少,调整树形结构也会效率更高
- 相比较于跳表,跳表在极端情况下会退化成链表,跳表需要更多的内存,MYSQL优化器需要计算成本的,【REDIS使用跳表是因为红黑树和B+树需要调整树形结构,跳表实现简单,而且REDIS存储的是短平快的热点数据,多级概率分层,一般不会出现跳表退化成链表的情况】
- 相比较于B树,首先B树非叶子节点也存放了数据,可能会导致内存难以放下所有的非叶子节点,从而失去索引的效果;并且B+树叶子节点之间构成双向链表,支持范围查询,方便加锁等操作。
- 想比较于哈希表,哈希表虽然支持随机查询,但是哈希表不支持排序和范围查询,而且哈希表需要处理哈希冲突。
聚簇索引和二级索引有什么区别?
- 聚簇索引包含主键值和完整的数据记录,二级索引只包含索引值和主键值
- 如果用到二级索引,但查询的是非索引列和主键,就需要在二级索引中找到对应的主键值,再用主键值去主键索引里回表查询,需要扫描两次B+树
普通索引和唯一索引有什么区别?
- 普通索引支持重复字段,唯一索引不支持重复字段
- 普通所索引更新效率很更高一点,因为MYSQL更新是先更新到CHANGE_BUFFER中,然后定期刷脏,如果是普通索引,不要进行重复检查,直接更新CHAGE_BUFFER就行了,对于唯一索引,如果缓存中没有,就需要到磁盘中去读取出来,判断唯一性后,再更新,所以普通索引更新效率会更高。但是重复性很高的数据失去建立索引的价值
怎么设置主键,如果没有设置主键,主键索引如何形成?
- 给字段加上PRIMARY KEY标识索引
- 如果没有设置索引,会默认使用第一个非NULL的列作为主键索引项
- 如果既没有主键,也没有非空值,会使用默认的ROW_ID列来作为主键索引项
为什么要建索引?
- 缩短查找时间,没有建索引就需要全表扫描,时间复杂度O(N),如果建立了索引,时间复杂度O(LOG2N)
- 索引在磁盘上按顺序存放,所以可以让随机I/O变成顺序I/O,提高空间局部性
- 索引可以帮助快速定位到数据,并且自带增序,所以可以减少外部排序和内部临时表的使用
INSERT操作对B+树的改变是怎样的?
- 如果主键值是递增的,那么每次插入就只需要插入到叶子节点的最右边,执行插入操作,效率是很高的
- 如果主键值是非递增的,那么插入就可能在中间进行插入,那么就需要优化B+树的树形结构,可能会发生页分裂,进行数据页的复制,效率就会很低,并且造成内存碎片。
一般选择什么字段来建立索引?
- 频繁使用WHERE、GROUP BY、ORDER BY的字段
- 区分度不高的字段不使用索引
索引越多越好吗?
- 索引不是越多越好,因为每见一个索引就会维护一个B+树,会影响写性能,所以读少写多的场景不适合建索引
- 区分度不高的字段或者重复高的字段不适合索引
怎么优化索引?
-
覆盖索引优化,对于WHERE、GROUP BY、 ORDER BY常用的不频繁的字段建立联合索引进行覆盖优化,联合索引要符合最左匹配原则。
-
主键索引最好是自增的,提高INSERT效率
-
前缀索引可以减少索引项的大小
-
避免索引失效
- 左模糊/右模糊:LIKE %XXX或者LIKE%XXX%
- 在WHERE 、ORDER BY、GROUP BY 索引字段进行计算、函数、隐式转换等操作的时候 会失效
- 联合索引要符合最左匹配原则
- WHERE字段,如果OR之前的条件是索引,OR之后的条件不是索引,索引就会失效
建立了索引,一定会走索引吗?
-
如果走索引的成本高,优化器就会决定不走索引
-
索引失效
- 联合索引不符合最左匹配
- WHERE字段OR之前的是索引,OR之后的不是索引
- 索引字段进行了隐式转换(比如字符串转化成了时间或者数字),计算,函数等操作
WHERE TIME=20191108 ,TIME是VARCHAR,会走索引吗?
不会走索引,实际上用了CAST转换函数进行了隐式转换
MYSQL新版本解决了什么问题?
- 索引跳跃扫描,联合索引可以不遵循最左匹配原则
- 函数索引,新调用函数可以走索引了
什么是最左匹配原则?
-
比如有一个(a,b,c)联合索引,索引排序是先按A排序,再按B排序,再按C排序,左右符合这个顺序才能走索引覆盖
- 最左匹配原则下,如果b>1这种范围查询或者模糊查询后面的字段也无法走索引[只有等值查询后面的字段才能走索引]
-
索引下推,例子:通常情况下,数据库系统会首先使用 user_id 索引来定位符合条件的记录,然后再对每条记录应用 name = 'John' 的条件进行过滤。而索引下推则可以将 name = 'John' 的条件推送到索引层级,在索引层级就可以进行过滤,而不需要检索所有符合 user_id = 123 的记录再进行过滤。
建立联合索引有什么原则?
将区分度大的字段放在前面,因为如果第一个字段区分度不大的话,很可能就回表了
WHERE A>1 AND B=2 AND C<3怎么建立索引?
建立BAC或者BCA索引,字啊优化器优化下保证两个字段走索引,一个字段进行索引下推
SELECT ID,NAME FROM TABLE WHERE AGE>10 AND NAME LIKE 'XXX%',有(name和age的联合索引)
name走索引,age进行索引下推,B+树是先根据name排序然后再根据age排序的,以为name走了右模糊索引查询,所以age不是有序的了.age只能进行索引下推,也不需要回表
日志
什么是UNDO LOG?
- 可以通过BEGIN 和END来开启日志,MYSQL是开启了隐式日志的,在AUTOCOMMI里面进行控制,事务的原子性就是由UNDO LOG保证的
REDO LOG日志和BIN LOG日志有什么区别?
-
BIN LOG是SERVER层实现的日志,所有存储引擎都能使用,主要格式有 STATEMENT(默认格式)、ROW 、MIXED,BIN LOG日志是追加写,写满一个文件就新建,不会覆盖以前日志,是全量日志,BIN LOG主要用于备份恢复,主从复制
STATEMENT:记录和发送SQL语句,每一条修改过的SQL语句都会记录再BIN LOG逻辑日志里面,主从复制的SLAVE节点根据BIN LOG日志进行SQL语句再现,但是主从复制的过程中会有比如NOW()这种动态函数导致的数据不一致的问题。
ROW:发送行数据,只存储记录行最终是扫描样子,主从复制不会有数据不一致的情况,但是UPDATE每次修改的行记录都会存储,STATEMENT下只会存储最后一次的UPDATE
MIXED:包含STATEMENT和ROW,根据情况自动选择。
-
REDO LOG是INNODB存储引擎的日志,REDO LOG是物理日志,记录的是在XXX数据页做出了XXX修改,记录格式为:在XXX表对YYY数据页ZZZ便宜量做出了XXX修改,REO LOG日志是循环写,REDO LOG主要用于掉电恢复。
REDO LOG和BIN LOG在恢复数据上有什么区别?
- REDO LOG记录的是数据的修改情况,是循环写,主要用于掉电恢复
- BIN LOG记录的全量数据,不会覆盖写,主要用于备份恢复和主从复制
REDO LOG是怎么实现持久化的?
在MYSQL中,对数据做出的修改其实是在BUFFR POOL缓存池中,比如UPDATE其实是先修改CHANGE POOL中的数据,然后标记为脏页,在后台异步刷盘,如果事务提交时 数据库发生宕机·,数据没来得及刷盘,就会造成数据的丢失,所以在事务提交时,REDO LOG会先进行刷盘,REDO LOG 记录了数据修改位置情况,如果脏页丢失,也能进行掉电恢复。
REDO LOG除了掉电恢复 还能进行扫描?
REDO LOG写磁盘发生在事务提交前,REDO LOG写磁盘时追加写 即顺序写的形式,而BUFFER POOL数据页写磁盘时随机写的形式,先进行REDO LOG刷盘,可以让随机写变成顺序写,提高MYSQL的磁盘写入性能。
两段提交的过程?
-
两段提交就是将事务的提交分为准备阶段和提交阶段
- 准备阶段:REDO LOG状态标志位PREPARE,REDO LOG进行刷盘
- 提交阶段BIN LOG 进行刷盘,并将REDO LOG状态标志为COMMIT
-
在两段提交中,把BIN LOG刷盘成功的时机作为事务提交成功的标志,如果BINL LOG没有刷入磁盘,事务就会回滚,如果BIN LOG刷入磁盘但是REDO LOG 状态还是PREPARE,下次重启就会提交。
为什么要进行两段提交?
- 为了保证BIN LOG和REDO LOG日志里面的数据一致
- 在主从复制过程中,主库发生修改,但是只记录了REDO LOG ,BIN LOG 还没来得及刷盘,就会导致主数据库和从数据库之间数据不一致。
锁
MYSQL中锁的分类
根据锁的粒度不同,主要分为:全局锁、表级锁、行级锁
-
全局锁
#加锁 flush tables with read lock #解锁 unlock tables -
表锁:表锁、MDL元数据锁、意向独占锁/意向共享锁、AUTO_INC意向自增锁
-
表锁
#表级别的共享锁,也就是读锁; lock tables t_student read; #级别的独占锁,也就是写锁; lock tables t_stuent write; #解锁 unlock tables
-
MDL元数据锁
-
意向锁,意向锁的目的快速判断表里面是否有独占锁,意向锁只会和意向锁进行冲突,不会和行独占锁冲突,下面的语句是行独占锁和共享锁,但是会自动加表级的意向锁
#意向独占锁 select .... for update #意向共享锁 select .... lock in share -
AUTO_INC自增锁,虽然是表级锁,但是执行完立即释放
-
-
行锁:
-
行记录锁
#行独占锁 select .... for update #行共享锁 select .... lock in share -
间隙锁(3,5),间隙锁和间隙锁之间不会冲突,但是间隙锁和插入意向锁之间会冲突
select * from t_test where id>3 and id<5 for update; -
临键锁(3,5],next-key 相当于记录锁+间隙锁
-
插入意向锁,意向锁是用来快速判断这个表里面是否有独占锁的,是表级锁;插入意向锁是行级间隙锁,一个事务不能同时拥有间隙锁和插入意向锁,插入意向锁是插入等待
-
MYSQL如何实现乐观锁
加上版本号
update student set name='abc' and version=2 where sid='123456' and version=1
在线上修改表结构会发生什么?
会加上表级的元数据锁
一条UPDATE语句没有带WHERE会加什么锁
- 可重复度的隔离级别下,update会为每一个扫描过的字段加上记录锁和间隙锁,锁全表
- 读已提交的隔离级别下,update会为每一个扫描过的字段加上记录锁,所以不会锁插入操作
带了where条件没命中索引会加什么锁?
- 可重复读级别下,会加间隙锁和记录锁
- 读未提交级别下,会加记录锁
两条更新语句更新同一条记录,加的是什么锁?
-
如果是唯一索引
- 如果存在,锁的是这条记录和间隙锁
- 如果不存在,给扫描过的都加间隙锁
-
如果是非唯一索引
- 如果存在,给扫描到的第一条二级索引记录加记录锁和间隙锁
- 如果不存在,给第一个不符合条件的二级索引记录加间隙锁
-
如果没命中索引,就和上文一下,全部加记录锁和间隙锁
可重复读的场景下最容易发生死锁的是?
A事务更新操作加了记录锁和间隙锁,B事务执行插入意向锁,导致是死锁
你了解过死锁问题吗?
- 死锁的条件:互斥,不可剥夺,循环等待,请求保持
- 可重复度情况下,更新的间隙锁和插入的插入意向锁就会导致死锁(注意是不同事务,因为同一个事务就不存在互斥了,同一个事务可以在快照里修改)
MYSQL怎么排查死锁问题?
可以使用 show engin innodb status,然后根据日志来排查
MYSQL如何避免死锁
- 缩短持锁的时间:大事务拆分成小事务
- 减少间隙锁:将隔离级别改成读已提交,用二级索引减少加锁范围
- 设置MYSQL参数,比如说等待阈值
事务
MYSQL四大特性
ACID
- ATOMIC:原子性,由UNDO LOG日志保证的
- CONSISTENCE:一致性体现在 事务开始和结束 完整性得到保证,与现实的一致性,两个人银行转账的案例
ISOLATION:隔离性,由MVCC版本链和锁保证的
DURABILITY:持久性,由MYSQL的两段提交设计、REDO LOG日志和BIN LOG日志保证的
MYSQL事务和REDIS事务有什么区别?
- REDIS事务无法保证持久性,首先RDB快照写,是按照配置文件或者指定命令在特定时间才会写,AOF设置为ALWAYS,是先将数据放到AOF缓冲中,再写入,可能会出现丢死上一个事件循环中的数据的情况。
- REDIS无法保证原子性,无法回滚,主要命令有MULTI EXEC,开启事务能加快批量操作。
MYSQL事务隔离级别有哪些?分别解决什么问题?
- 事务级别对照表
| 事务级别 | 可能出现的并发问题 | 如何解决 |
|---|---|---|
| 读未提交 | 脏读、不可重复读、幻读 | |
| 读已提交 | 不可重复读、幻读 | MVCC版本链 |
| 可重复读 | 幻读 | MVCC+ReadVIEW+=临键锁 |
| 串行化 | 全是当前读,没有MVCC |
-
并发问题说明:
- 脏读:A事务读到B事务未提交的数据
- 不可重复读:强调读到了B事务修改的数据,A事务多次读取记录,在这个过程中B事务修改了数据并提交,导致A事务多次读的结果不一样
- 幻读:强调读到了B事务新增的数据,A事务多次读取记录,在这个过程中B事务新增了数据并提交,导致A事务读到的记录数不一样
什么是MVCC?
- MVCC是多版本并发控制,通过记录历史版本数据,解决读写并发冲突问题,避免加锁,提高MYSQL并发性能。
- 在行记录中,有事务ID和ROLL_PTR,MYSQL将历史数据存在UNDO LOG日志里面,ROLL_PTR指向UNDO_LOG日志,每次SELECT的时候会创建READVIEW快照,通过当前事务ID与历史数据比较,判断哪一条UNDO LOG可见。
MVCC中READVIEW可见性的规则?
-
READVIEW主要有四个字段:创建这条记录的事务ID、所有活跃未提交的事务IDS、活跃未提交的最小事务ID、下一个事务ID
- 如果记录的事务id小于ids 就说明这条记录已提交
- 如果记录的事务id在ids中,就说明这条记录未提交
- 如果记录的事务id大于ids,就说明这事务还没开始
读已提交和可重复读在MVCC上有什么区别?
主要区别是生成READVIEW的时机不同
- 读已提交,每次执行SELECT 都会生成一次READVIEW数据,然后通过MVCC,只读取事务id小于活跃未提交事务ids中的记录
- 可重复读,只在事务开始之前生成一次READVIEW,在本次事务中一直用这个READVIEW
MYSQL的默认隔离级别是什么?怎么实现的?
默认隔离级别是可重复读,是通过MVCC(每个事务唯一的事务id,修改在undo-log链里面)+当前读上加快照读+行级锁:间隙锁
为什么很多互联网公司使用读已提交隔离级别?
因为MYSQL读已提交是通过MVCC版本链实现的,更新数据没有间隙锁,可以减少死锁的几率
MYSQL是如何解决不可重复读的问题的,又是怎么解决幻读的?
- MYSQL的读取有两种方式,快照读:就是直接的SELECT ... ,当前读:SELECT ... FOR UPDATE
- 快照读的情况下,MYSQL可以直接利用MVCC+READVIEW来实现,事务创建之前就做一次快照读,之后所有的操作都在快照的undo里面,保证可重复读
- 当前读的情况下,就没办法用MVCC+READVIEW了,所以这时候就要加间隙锁来解决,因为间隙锁和插入意向锁不兼容,所以也一部分解决了幻读的问题。
可重复读为什么没有完全解决幻读的问题?
-
可重复读为了当前读实现可重读 所以选择了间隙锁的方式,一定程度上解决了幻读,因为间隙锁只能阻塞其他事务,比如以下场景
- A事务先进行快照读,没有加锁
- B事务插入了一条id=5的数据
- A事务更新id=5的数据,A事务更新会执行当前读,所以能读到id=5,更新完了之后,id=5的事务id就是A
- A再进行快照读,因为快照读是通过版本链进行的,所以这时候读到了B事务的数据
-
总结:
- 两次都是快照读就不会幻读,但第一次快照读,中间发生一次当前读,那就会造成幻读。
性能调优
如何看一个SQL语句是否走了索引?
- 可以通过explatin来查看这条SQL语句的执行计划,主要字段包括TYPE(扫描类型)、POSSIBLE_KEYS(可能的索引)、KEY(实际索引)、KEY_LEN(索引长度)、ROWS(扫描的数据行)
-
主要看TYPE 当前的扫描类型:
- ALL 全表扫描
- INDEX 全索引扫描
- RANGE 范围索引扫描
- REF 非唯一索引扫描
- EQ_REF 唯一索引扫描
- CONST 主键索引或者唯一索引扫描
EXTRA 字段
- USING INDEX 覆盖索引
- USING WHERE 将查询结果返回到service进行过滤,USING WHERE 不一定回表
- USING INDEX CONDITION 在索引层进行数据过滤,不用取出数据再返回service层了
怎么找到慢SQL
再SQL的配置文件中设置为3s
slow_query_log = 3
slow_query_log_file = /path/to/slow_query.log
long_query_time = 3
如何优化慢SQL
- 优化数据访问,是否有不必要的列(不用*),是否查询的列太多,加limit
- 切分查询,把大查询分段查询,每次只查一小部分,最后组合
- 分解联表查询
- 覆盖索引,并检查是否会出现索引失效,排序优化
如果SQL语句都没问题,怎么优化?
- 短平快的数据走REDIS旁路缓存
- 分批查询
- 分段查询
- 读多写少的用主从复制
- 合理分库
深分页查询如何优化?
-
深分页场景:SELECT ..... LIMIT 1000000 20
- 这种场景,从100万开始查,但返回20条,时间复杂度高,如果回表,前一百万条也要进行回表
-
场景优化
- 记录上一次最后的id WHERE ID>xl来查询
- 覆盖索引+子查询,先查询出来二级索引需要的主键值,然后用主键值去查主键索引
高可用
MYSQL主从复制的过程?
- 主库两段提交后,更新BIN LOG日志
- 从库复制BIN LOG日志到暂存日志中
- 从库回放暂存的BIN LOG日志
MYSQL 主从复制提供了几种模式?
- 同步复制
- 异步复制,可能会丢失数据,宕机后从库变成主库
- 半同步复制
MYSQL主从复制数据不一致怎么解决?
- 使用缓存,写入数据的时候先写入缓存,查询的时候先直接查询缓存
- 直接查询主库
MYSQL主从架构中,读写分离怎么实现?
- 增删改走主库,查询走从库,但是这样改动对原有代码有侵入性
- 使用中间件,或部署中间件MYCAT实现主从同步
主从复制中 主库挂了怎么办?
主从复制没有解决这个问题,需要其他的高可用组件比MHA,在主库发生宕机的时候,会自动剔除主库,选择新的主库,保证业务正常进行
什么是分库分表?
- 一般一张MYSQL表的数据量只能500万左右,所以为了存储更多的数据,要进行分表
- 分库是为了高可用,防止主数据库宕机,并且缓解并发请求,实现主从库的读写分离等
分库分表会有哪些问题?
- 分布式事务问题,使用分布式事务中间件
- 分表会有全局ID唯一性问题,雪花算法
- 跨库跨表读写问题