日志相关
binlog、redolog和undolog区别?
redolog 是重启日志,当MySQL宕机之后恢复的时候就是读取这个文件,里面记录的是修改后的数据,恢复的时候执行一下这个文件里面的修改
undo log 事务回滚时执行的日志,用来实现事务回滚和MVCC,里面记录的是修改前的旧值,回滚的时候拿里面的数据去覆盖就行,同时也支撑着MVCC的多版本功能
binlog 用来做备份和主从同步,记录所有DDL(操作行数据)/DML(操作表结构)的语句,使用追加写的方式,用来做主从复制和同步
redolog 和 binlog为什么不能合并成一个?主从同步的时候为什么不用redolog?宕机恢复用的是redolog 还是 binlog?
redolog 记录的是物理日志,类似于 在第123号数据页,偏移40字节,把值改成100,它是针对当前机器的,不能跨机器
binlog 记录的是逻辑日志,类似于 UPDATE user set name='xx' where id=1,这个是能跨机器的
所以binlog 用来做主从复制和数据备份,而redolog用来做节点宕机之后的数据恢复
此外redolog是存储引擎层的(InnoDB),而binlog是server层的
undolog 和 redolog的区别?
redolog 是用来保证事务的持久性,主要用来宕机恢复,而 undolog用来保证事务的原子性和一致性,主要用来事务回滚
Redo Log 记录了事务的所有数据更改(这些日志不仅仅记录了数据更改的最终结果,而且还记录了实现这些更改的具体操作)。而Undo log记录的是事务执行前的内容。
宕机恢复规则:
- redo prepare、binlog 没写:回滚事务
- redo prepare、binlog 写完:补提交事务
MySQL的binlog有几种格式
三种
STATEMENT(语句模式)
这个模式是直接记录完整的SQL
优点:日志小,网络IO小
缺点:可能会造成主从不一致,并且如果一些带了函数的sql会出现数据不一致(比如now())
这个基本不用了
ROW(行模式)
不记录sql,只记录哪一行,怎么变的
记录:行的旧值+新值
优点:绝对的主从一致
缺点:网络IO占用高
用的最多且默认的就是这个
MIXED(混合模式)
普通sql走STATEMENT,可能不一致的走ROW
基本没有人用
一个注意点:
RR隔离级别下,可以用STATEMENT和ROW
但是RC就只能用ROW,用STATEMENT可能会数据不一致
因为RC是每次查询拿到最新的数据,这个就很危险,假设主从同步的时候出现顺序错乱,那你可能就错误的修改了
MySQL5.7中的组提交
组提交就是把多个事务的提交打包成一组,一次性刷盘,减少频繁的磁盘IO
对二阶段提交的影响,redolog进入prepare之后,不能立即 fsync,需要先攒一波,再统一刷盘+commit
说白了就是批量刷盘
什么是bufferpool?
bufferpool 就是 InnoDB 在内存中开的一大块缓存区域,用来缓存磁盘上的数据页和索引页的
查询数据的时候先查询bufferpool,如果命中直接返回,没命中就从磁盘缓存到bufferpool
修改数据的时候,直接修改bufferpool,再通过异步刷盘的方式写回磁盘
bufferpool的读写过程是怎么样的?
读流程
- 先去 Buffer Pool 查找数据页
- 命中则直接从内存返回
- 未命中则从磁盘加载对应页到 Buffer Pool,再返回
写流程
- 先将数据页加载到 Buffer Pool
- 在内存中直接修改,不立即落盘
- 修改过的页标记为 脏页(Dirty Page)
- 后台线程异步刷回磁盘,常见触发条件:
-
- 脏页比例达到阈值
- redo log 写入速度较快
- 数据库空闲、正常关闭
MySQL架构与执行流程
MySQL的HashJoin是什么?
其实就是表连接时的一个优化算法
比如没有HashJoin之前使用的方式都是双重遍历
SELECT
student_name,school_name
FROM
students LEFT JOIN schools ON students.school_id=schools.id;
也就是上面这段代码会先到student表中遍历一遍,然后再到school表中遍历拿到符合的记录
有了HashJoin之后,就变成区分构建表和探测表
具体就是先把schools判定为构建表,然后对他的id字段建立hash表,然后只需要遍历一遍students,然后到hash表中匹配对应的记录,也就是一层循环就搞定了
作为构建表的选择有几个规则:
- 更倾向于选择数据量小的
- 左连接和右连接的话,需要数据全的那个不会作为构建表,比如左连接的左表就不能用来构建,否则数据不全
总结:HashJoin就是原本你需要开两层for进行匹配,现在可以把其中一层通过预处理的方式变成hash表,直接快速定位
MySQL的limit+orderby为什么会数据重复?
这个其实是这样的,数据重复的原因就是当你limit+orderby 后面的字段的值不唯一的情况下,会出现每次对同一个分页取出来的结果不一样,也就是随机,因为orderby后面的字段出现了数据重复,所以结果会随机,那会造成一个问题,可能同一条记录你在上一页和当前页都能看到
其实不只是这样,单纯只用limit的话,同样可能会出现每次查询结果不一样的情况
所以我们是推荐limit+orderby,但是orderby后面的字段必需是具有数值唯一性的,否则还是存在那个问题
MySQL的select会用到事务吗?
会。
首先即使我们没有手动开启事务,InnoDB 也会开启一个隐形事务,可以说InnoDB 的所有操作都是在事务上下文中执行的,包括修改和读取
不过select虽然是在事务上下文执行,但是因为没有修改操作,所以不会加锁
MySQL的并行复制原理
MySQL 的并行复制时在主从复制的时候
以前从库只有一个线程来执行sql,如果主库写的比较快的情况下,从库会出现跟不上的情况,产生延迟。
而并行复制就是个从库开多个线程,同时执行sql,这样从库就能追上
为什么需要并行复制?
因为主库一般都是多线程在写,从库只有一个线程进行同步,显然是不可理的
不同的并行复制方案:
第一代是一库一线程,多库就实现并行复制,但是单个库之间还是单线程,大部分公司还是单库,所以基本等于没用
第二代是主库一起提交的事务,从库也能并行执行,因为能一起提交说明是不冲突的
第三代是把每一行数据都记录出hash值,只要两个事务的hash值不重复,说明可以并行复制,这样即使主库是串行,从库依然能并行
这个第三代具体流程:
主库修改的时候计算出这一行记录的Hash值( Hash值依据:库名 + 表名 + 主键/唯一键值 )
然后事务会把自己修改的这些记录的hash值写入到 writeSet 里面,并写入binlog
从库拿到之后,会对比两个事务的writeset
- 看事务 A 和事务 B 的 WriteSet 有没有重叠
- 无重叠 → 没改同一行 → 分给不同 worker 并行执行
- 有重叠 → 改了同一行 → 必须串行
MySQL的驱动表是什么?MySQL怎么选的?
驱动表就是build表,也就是先扫描的表
被驱动表就是probe表,就是后扫描的表
这一块说的其实是join的一个连接流程
join就是先读取驱动表,然后拿着结果到被驱动表里面找匹配
MySQL 选取的原则就是:
优化器选驱动表只有一个目标:更快、更少数据、更少 IO
规则分两种:
① INNER JOIN(内连接)
谁过滤后数据量小,谁当驱动表
小表驱动大表最快
优化器自动算,自动选
② LEFT JOIN / RIGHT JOIN
原则上遵循连接方向:
LEFT JOIN:左表驱动
RIGHT JOIN:右表驱动
但!优化器可能会改!如果右表极小,MySQL 可能会偷偷改成右表驱动,因为更快。
MySQL的数据存储一定是基于硬盘的吗?
不一定,可以基于内存
比如存储引擎 MEMORY 就是基于内存的,数据不会落库,但是一旦宕机就没了
存储引擎就是规定如何存储和读取数据以及怎么支持事务的
存储引擎是表级别的,可以单独执行不同的表为不同的存储引擎
mysql> show create table tb_blog;
--------------------------------------------------------------------------------+
| tb_blog | CREATE TABLE `tb_blog` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`shop_id` bigint NOT NULL COMMENT '商户id',
`user_id` bigint unsigned NOT NULL COMMENT '用户id',
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标题',
`images` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '探店的照片,最多9张,多张以","隔开',
`content` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '探店的文字描述',
`liked` int unsigned DEFAULT '0' COMMENT '点赞数量',
`comments` int unsigned DEFAULT NULL COMMENT '评论数量',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT
MySQL获取主键id的瓶颈在哪里?如何优化?
主要是每次获取都需要加锁,因为通过MySQL来自增Id的话,需要通过 AUTO-INC 来加锁,确保不会出现Id重复的情况
优化的思路:直接业务端主动塞入Id,不要让MySQL 帮我们生成
- 基于号段模式,也就是另外起一张表记录当前的Id使用到哪个区间
- redis自增Id
- 本地直接UUID或是引入一些生成雪花算法的组件
MySQL是AP的还是CP的系统?
A:可用性
C:一致性
P:分区容错
MySQL 有不同的部署方式:
单机/主备(只有一个节点对外暴露服务)
这种情况谈不上P,也就是没有分区,不可用这套理论来判断,不过会更倾向于CP吧,主打强一致性
读写分离:
主库写,从库读
这种情况下会有一定的延迟,但是最终一致性,所以是AP
MySQL是如何保证唯一性索引的唯一性的?
插入或是修改时,会先到唯一索引进行查询是否存在这个值
也就是多一次查询的过程
高并发下要对加了唯一索引的列进行修改/插入,为了避免你修改完会造成不唯一,会通过加锁的方式,确保同一时间只有一个事务能操作
唯一索引是允许NULL,的在mysql中,null代表未知,所以可以有多个null,这一点和hashmap不太一样
唯一索引查询的时候通常会更快,因为找到就可以结束,而普通索引会继续找,因为可能存在多条
唯一索引有什么缺点?
插入更慢,每次都需要先检查唯一性
更新更慢,更新唯一索引相当于先删旧值再插入新值
并发冲突更多
MySQL怎么做热点数据高效更新?
热点本质上就是大量的请求打到同一条记录上
解决思路:
- 把记录拆分成多条,比如库存记录能不能拆分成10条,这样就大大分散压力
- 请求合并,每来一个请求往一张额外的任务表插入,然后开定时任务一段时间就去进行扫描之后批处理
- 改造MySQL 底层,让它能识别热点记录,一旦识别到直接升级为排队模式
MySQL执行大事务会存在什么问题?
大事务就是执行时间很长或是数据量很大,长时间不提交的那些事务
大事务会导致长时间占用数据库连接,那么其它的请求就处理不过来
回滚会很慢,因为你的事务做的越多,一旦错误,要回滚的内容也越多
锁占用太久,并发爆炸,事务里面只要有更新操作就会占用锁,并且在你事务提交之前是不会释放的
大事务产生的binlog会比较多,好像超过一定的大小是会报错的
解决的思路就是尽量减少事务的粒度,把远程RPC以及查询等放到事务之外执行
MySQL中like的模糊查询如何优化
首先B+树的索引的按照前缀建立的,所以我们进行模糊匹配的话一定不能把%放在前面
-- 能走索引(前缀匹配)
like 'abc%'
-- 不能走索引(左边不确定)
like '%abc'
like '%abc%'
那如果一定要能查询呢?
那就只能再额外建立一个列用来存储原字符串反转之后的字符串,并且建立列
那么匹配'abc%'可以使用匹配'cba%',然后再把匹配之后的结果反转返回
MySQL中如何查看一个SQL的执行耗时
通过EXPLAINANALYZE
mysql> EXPLAIN ANALYZE SELECT * FROM tb_blog;
+---------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------+
| -> Table scan on tb_blog (cost=0.65 rows=4) (actual time=27.4..27.9 rows=4 loops=1)
|
+---------------------------------------------------------------------------------------+
1 row in set (0.16 sec)
cost 是预估成本,这个值越小越好
actual time 是真正执行时间 27.4..27.9 (
- 27.4 = 开始到第一行数据返回的时间
- 27.9 = SQL 全部执行完的时间
)
MySQL主从复制的过程
1、从服务器在开启主从复制后,会创建出两个线程:I/O线程和SQL线程
2、从服务器的I/O线程,会尝试和主服务器建立连接,相对应的,主服务中也有一个binlog dump线程, 是专门来和从服务器的I/O线程做交互的。
3、从服务器的I/O线程会告诉主服务的dump线程自己要从什么位置开始接收binlog
4、主服务器在更新过程中,将更改记录保存到自己的binlog中,根据不同的binlog格式,记录的内容可能不一样。
5、在dump线程检测到binlog变化时,会从指定位置开始读取内容,然后会被slave的I/O线程把他拉取过去。
6、从服务器的I/O线程接收到通知事件后,会把内容保存在relay log中。
7、从服务器还有一个SQL线程,他会不断地读取他自己的relay log中的内容,把他解析成具体的操作,然后写入到自己的数据表中。
注意:数据复制的过程是从节点主动到主节点去拉取
复制的方式:
- 同步:等待所有从节点的ack
- 异步:无需等待从节点ack,直接返回成功给客户端,可能丢数据
- 半同步:等待其它至少一个从库ack
MySQL自增主键用完了会怎么样?
主要分两种情况吧
如果我们有指定了主键,比如:
id INT AUTO_INCREMENT PRIMARY KEY
那么当用到上限的时候,这个时候会出现插入不了的情况,
也就是插入数据直接报错,不会丢失数据
当我们没有指定主键,会使用默认的row_id,6字节
当用到上限的时候,会重新从0开始,也就是会出现覆盖原来数据的情况
这种就是覆盖导致数据丢失
那如果真的用完了怎么办?
- 数据归档,把旧的数据进行迁移,或是直接创建新的表,然后旧的表作为历史数据存储起来
- 使用大一点的字段类型,比如使用bigint,8字节,基本用不完
- UUID,用不完,但是性能差
SQL语句如何实现insertOrUpdate的功能?
通过 ON DUPLICATE KEY 连接两个sql
mysql> select * from student;
+----+-------+------+
| id | name | age |
+----+-------+------+
| 1 | Alice | 20 |
+----+-------+------+
1 row in set (0.00 sec)
mysql> INSERT INTO student (id,name,age)
-> VALUES (1,'Alice',20)
-> ON DUPLICATE KEY UPDATE
-> name='tom', age=20;
Query OK, 2 rows affected (0.01 sec)
mysql> select * from student;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | tom | 20 |
+----+------+------+
1 row in set (0.00 sec)
执行原理,直接执行insert,如果发现插入不了,就改成执行update部分
所以原来的表一定需要有主键或是唯一索引列,否则永远都是插入成功
类似的还有:
REPLACE INTO 有就先删除旧数据,再插入新数据
INSERT IGNORE 有就忽略,不插也不更
需要注意的是ON DUPLICATE KEY会出现id跳号。因为它是先申请id,再判断是否冲突,冲突也不会还回去
ON DUPLICATE KEY 会加临键锁高并发下非常容易死锁
SQL执行计划分析的时候,要关注哪些信息?
只需要看4个字段,其它的都是辅助, type → key → rows → Extra
快不快就看type
- const :用主键 / 唯一索引,最快
- ref :用普通索引,良好
- range :索引范围查询,还行
- index :遍历整个索引树,慢
- ALL :全表扫描,最差,必须优化
key,看有没有用索引
- key为NULL,说明没用到索引,走的全表扫描
- key不为NULL,用到了索引
rows,扫描多少行
这个值越小越好,多了说明索引没建好说是条件不可理
Extra,看有没有说明警告信息
比如:
- **Using temporary,**用到了临时表(group by 没走索引)
- **Using filesort,**用到了文件排序(order by 没走索引)
这两个就一定要看看有没有办法优化
Using index 才是比较好的指标
SQL中PK、UK、CK、FK、DF是什么意思?
PK:Primary Key ,主键约束
UK:Unique Key, 唯一约束
CK: check(), 检查约束
FK:Foreign Key, 外键约束
DF:default ,默认约束
truncate、delete、drop的区别?
DELETE 用于删除表中的一行或多行记录,它可以与 WHERE 子句一起使用来指定要删除的记录。
TRUNCATE 用于快速删除表中的所有记录 ,并重置任何自增的计数器(如自增的主键)
DROP 用于删除整个表结构及其数据。
这三个操作中,DROP是最彻底的,他不仅删除表中的数据,还删除表结构,并且操作不可撤销。
这三个操作中,DELETE是最慢的 ,因为他再操作过程中会记录binlog,并且他是在事务中的,可以做回滚。
这三个操作中,DELETE可以和WHERE一起用,可以设置筛选条件,二DROP和TRUNCATE是不可以增加筛选条件的。
这三个操作中,TRUNCATE和DROP是DDL(数据定义语言)操作,二DELETE是DML(数据操作语言)操作。
Usingfilesort能优化吗,怎么优化?
可以优化
Usingfilesort 一般是你使用了order by 但是没有给排序字段建立索引,或者未达到走索引的条件(顺序不对,不符合最左匹配原则)
解决思路:
-
给 WHERE + ORDER BY 的字段建联合索引,并且顺序完全一致。
SELECT * FROM t WHERE a=? ORDER BY b,c;
建立索引:INDEX idx_a_b_c (a,b,c)
-
调大一下 sort_buffer_size(治标不治本)
sort_buffer_size = 2M
Usingfilesort 可能会在内存排序,也可能使用临时缓存文件排序,调大sort_buffer_size就是为了让更多数据在内存排序
Usingfilesort 不一定要优化,因为当结果集比较小的时候,大概率会走缓存的排序,速度并不慢,可以不优化
uuid和自增id做主键哪个好,为什么?
UUID:
优点
- 全局唯一,多机器、多库生成不会重复
- 不好猜,安全性好一点
- 分布式系统方便,不用依赖数据库自增
缺点(致命,MySQL 不适合)
- 占空间大,字符串通常 36 位,比 bigint 大很多。
- 索引又大又慢,索引树更胖,内存装不下,磁盘 IO 变多。
- 写入性能差,UUID 是随机的,新数据会随机插入 B+ 树叶子节点,导致频繁页分裂、节点挪动,插入极慢。
- 排序、范围查询拉胯,无序,没法靠主键做高效分页、范围查询。
- 可读性差,给前端、日志都不友好
自增id:
优点:
- 占空间极小,int 4 字节,bigint 8 字节。
- 索引极小、极快
- 写入极快,总是往 B+ 树末尾追加,几乎没有分裂,写入性能最高。
- 范围查询、分页、排序天然友好
- 可读性强,方便排查问题
缺点:
- 分库分表会重复,多个库各自自增,ID 会冲突,需要分布式 ID(雪花算法)。
- ID 连续可预测,爬虫能顺着 ID 扒数据。
- 理论上会用完(但 bigint 几乎不可能)。
真实的场景一般是单表:自增Id,分表,雪花算法或是Id生成器
阿里的数据库能抗秒杀的原理
处理的时候不是单个请求进行处理,而是先把请求全部集中起来,以批处理的形式
每一批请求进行处理的时候只有第一个请求进行拿锁,后续的请求直接一个一个进行处理,无需拿锁
对于热点行会缓存到内存中,无需每次都查索引
提交的时候也是按照批处理的时候,一组数据一次提交
是否支持emoji表情存储,如果不支持,如何操作?
看字符集
MySQL 自带的 utf8 是假的(最多 3 字节),存不了 emoji。要存 emoji(4字节),必须用 utf8mb4(4 字节)。
数据库加密后怎么做模糊查询?
加密后做模糊匹配,直接查询肯定是查不出来的
解决方法:
- 另外存储明文,自欺欺人,不可用
- 全部查询出来,在内存进行解密并匹配。不可用,大部分情况下加密是单向的,无法解密,并且数据量大容易OOM
- 分词加密,就是另外多几个列,把原数据拆分之后单独加密存储,好用,但是局限性也高,只能查询特定位置的,比如匹配前4个字符这样子
数据库怎么做加密和解密?
三种加密方法:
- 服务端业务加密,也就是代码里面加密完再塞入数据库
-
- 优点:安全,密钥自己保管
- 缺点:模糊查询比较麻烦
- 数据库自带的加密函数 AES_ENCRYPT、 AES_DECRYPT
-
- 优点:写起来方便
- 缺点:密钥写在sql里面,不安全,性能一般
说一说MySQL一条SQL语句的执行过程?
- 连接器:建立连接、权限校验
- 查询缓存:8.0 已移除,就是检查之前是否执行过一样的sql,但是表一更新,缓存就失效
- 解析器:语法语义解析,检查合法性
- 优化器:生成执行计划,选择索引(判断怎么执行最快)
- 执行器:执行计划,调用存储引擎(调用InnoDB 的接口)
- 存储引擎:读取数据并返回结果( 从 buffer pool 或磁盘读取 )
为什么MySQL8.0要取消查询缓存?
原本缓存就是用来把每一次查询之后的数据缓存起来,下次只要执行一摸一样的sql,就可以直接把结果返回,不用查表
存在几个致命问题:
- 缓存失效太频繁,只要任何一行记录出现变动,就会直接失效
- 必需一摸一样的sql才能命中,哪怕多个空格都不行
- 内存开销大,并且查询缓存的时候还需要加全局互斥锁,因为可能会出现查询并构建缓存的情况,在高并发下,多线程抢锁反而性能下降
- 可能会执行一些不确定结果的函数,比如now()、rand(),这一类每次执行结果都是不一样的,所以不会缓存
总的来看,缓存的性价比不高,并且维护成本大于它的实用性
取消缓存之后,现在缓存用什么?
- 业务层自己缓存,比如使用redis或是本地缓存等
- InnoDB 的 buffer pool
为什么不建议使用存储过程?
存储过程就是把业务写在数据库里面,但是比较难维护,而且不好调试和拓展,所以现在基本不推荐用这个
为什么不推荐使用外键?
外键就是让数据库帮我们强制关联完整性,也就是当你的一条业务记录是涉及多个表的时候,那么当你要进行删除的时候就需要判断其它表中该相关记录是否也存在
但是判断的过程是需要加锁的,会导致性能降低
此外就是不支持分库分表,因为外键只能在一个数据库实例里面,一旦分库分表就没办法用了
此外就是和逻辑删除冲突,逻辑外键只认物理存在,不认逻辑删除,会导致一些删除场景比较僵硬
所以一般情况下,都是把外键改造成逻辑外键的思想,在业务层面去保证
为什么大厂不建议使用多表join?
因为性能差,多表join的底层是嵌套循环判断,多少张表就是多少层for循环,时间复杂度会爆炸增长,此外就是分库之后,join不生效
MySQL 的三种Join算法
① Simple Nested Loop(暴力匹配)
逐条对比,全表扫描,超级慢
② Index Nested Loop(有索引快一点)
被驱动表有索引 → 变成 O (N * logM)但依然是循环,只是快一点
③ Block Nested Loop(内存批量匹配)
放内存里批量匹配,快一些,但数据大了还是崩
虽然有HashJoin进行优化,但是HashJoin只能在等值匹配的时候才能生效
一个查询语句的执行顺序是怎么样的?
执行顺序:
- FROM + JOIN:先确定查哪几张表,把表关联起来
- WHERE :过滤数据(行级过滤)
- GROUP BY:对过滤后的数据分组
- HAVING :过滤分组后的结果(分组后过滤)
- SELECT:选出最终要展示的列
- ORDER BY:对结果排序
- LIMIT:最后截取前 N 条
where和HAVING的关系?能不能直接合并成一个?
不能。两者的执行时机和功能完全不一样
where是在分组之前进行过滤,并且不能使用聚合函数,只能过滤原始数据
having是分组之后进行过滤,是对分组的结果再次进行过滤,可以使用聚合函数
举个例子:要查询员工数量大于10的部分
SELECT dept_name, COUNT(*)
FROM emp
GROUP BY dept_name
HAVING COUNT(*) > 10;
这个就是正确的写法,但是能不能替换成where呢?
SELECT dept_name, COUNT(*)
FROM emp
WHERE COUNT(*) > 10 -- 直接报错
GROUP BY dept_name;
这里会直接报错,因为where不能使用聚合函数,而且这里进行统计数量也不合理,因为where是分组前执行,此时还没执行,肯定是不能统计个数的
什么时候两个可以互换?
只有当你的过滤条件和分组以及聚合函数无关的时候
SELECT dept_name, COUNT(*)
FROM emp
WHERE salary > 5000
GROUP BY dept_name
HAVING dept_name = '技术部';
可以写成:
SELECT dept_name, COUNT(*)
FROM emp
WHERE salary > 5000 and dept_name = '技术部'
GROUP BY dept_name;
有了关系型数据库,为什么还需要NOSQL?
关系型数据库擅长事务、一致性、复杂查询,但是并发能力会比较弱,拓展性也会差一点
而NoSQL 比较擅长高并发, 高扩展、灵活结构,但牺牲部分一致性。
一个系统往往会涉及到不同的场景,对于不可丢失的数据或是一致性要求比较高的数据,一般都是需要关系型数据库来保证,但是为了提高吞吐一般会采用NoSQL ,所以一般都是混合架构
a,b两个单独索引,where a=xx and b=xx走哪个索引?为什么?
正常情况下就是选择一个作为本次查询的索引。
选择的规则就是估算,会优先选择能过滤掉数据多的那个索引,比如区分度高的
总结就是谁筛选能力强就选谁
特殊情况下会触发索引合并
也就是分别走a和b,然后把结果放在一起求出并集进行返回
但是无论是上面的哪一种,性能都远远比不上联合索引
count(1)、count(*)与count(列名)的区别
count(1) 和 count(*) 基本是等价的,性能上没什么区别,但是统计结果会包含值为null的记录
count(*) 底层做了优化,当我们这么写的时候,底层会自动选择最小的索引,这类索引扫描最快,成本最低
而count(列名)同样是扫描,但是会统计值不为null的列,值为null的会直接跳过,并且是全表扫描+判断是否为null,不一定可以走索引,除非你这个列有索引
优先推荐使用count(*)
什么是OnlineDDL
Online DDL = 让 MySQL 在改表结构(加字段、加索引)时,不阻塞业务读写的技术。
DDL:修改表结构的语句,
- CREATE TABLE
- ALTER TABLE(加字段、加索引、改字段)
- DROP TABLE
- TRUNCATE
DML:修改数据的语句,比如增删改查
为什么需要 Online DDL?
MySQL 5.6以前,改表会锁表,那所有的操作就会直接被卡死
而且大表改表需要锁住比较久,会导致业务直接停摆
OnlineDDL 做到了改表的时候大部分时间都是不需要锁表的,业务正常执行,只有开始和结束的时候会短暂的锁表。(开始和结束的时候会短暂的加MDL锁【字典锁】)
Online DDL 核心特点:
- 大部分时间不锁表,允许读写
- 开始和结束的瞬间,会短暂加MDL 锁(字典锁)
- 会记录执行期间的业务增量操作,最后统一应用
- 不是所有 ALTER 都能走 Online DDL
三种DDL算法:
INSTANT:(最快)
- 只改表结构元数据,不动数据
- 耗时:毫秒级
- 支持:加列、删列、重命名列
- 完全不影响业务
INPLACE:(中等,Online DDL 主流)
原地修改,不拷贝全表
分两种:
- no-rebuild:不加列、不改主键 → 超快
- rebuild:加列、改主键 → 需要重建表,但依然不锁表(也就是需要拷贝数据,但是这个过程中无需锁表)
这就是真正的 Online DDL
COPY(最慢,老方法)
- 新建临时表 → 全量拷贝数据 → 替换旧表
- 全程锁表,阻塞业务
- 现在基本不用
默认选择顺序:INSTANT → INPLACE → COPY
Online DDL 执行原理:
准备 → 执行 → 提交
① Prepare(准备阶段)
- 短暂加排他锁(禁止读写)
- 确定用哪种算法
- 开一个日志空间,记录期间业务写入
- 极快,毫秒级
② Execute(执行阶段)
- 降级为共享锁 → 允许读写!
- 真正做改表、建索引、重排数据
- 期间所有写入都会被记录下来
- 这一步时间最长,但业务完全不影响
③ Commit(提交阶段)
- 再短暂加排他锁
- 把刚才记录的增量写入应用上去
- 替换新表结构
- 释放锁,完成
什么是MySQL的字典锁?
MDL = Metadata Lock = 元数据锁
可以这样理解:
- 数据锁(行锁、表锁):锁的是表里的数据
- MDL 字典锁:锁的是表的结构定义(字段、索引、列名)
MDL 就是保证在你进行读写的时候不会出现读取到一般,表结果被改变
MDL有两种:读模式和写模式
SHARED-MDL 共享锁(读锁)
谁会加:普通查询、增删改 DML
效果:
- 允许多个线程同时加
- 允许并发读写数据
- 禁止修改表结构(DDL)
读锁可以一起读 ,但不准改结构。
EXCLUSIVE-MDL 排他锁(写锁)
谁会加:ALTER、DROP、TRUNCATE 等 DDL
效果:
独占
- 禁止任何人读、写、改结构
- 所有 SQL 都会阻塞
要改结构,所有人都得等。
锁升级过程:
- 一开始,大家都在查询,加的是 SHARED-MDL(共享)
- 突然有人要执行 ALTER,需要 EXCLUSIVE-MDL(排他)
- 数据库必须等所有共享锁都释放,才能升级成排他锁
升级期间,DDL需要等待,新来的查询请求也需要等待, 这就是著名的 MDL 锁等待问题
MDL的坑:
- 当有一个长事务在跑(持有 SHARED-MDL)
- 这时来了一条 ALTER/DDL,需要 EXCLUSIVE-MDL
- DDL 拿不到锁,开始等待
- 重点来了 :后面新来的所有查询、插入、更新,都会排在 DDL 后面即使它们只需要 SHARED-MDL,也必须等
结果:整个表瞬间卡死,所有请求阻塞,CPU 打满,连接数爆掉。
什么是数据库的主从延迟,如何解决?
就是主节点和从节点的数据之间不是实时同步,而是有一定的延迟
常见的原因:
- 网络延迟,这个不可避免,只能尽量把主从节点部署的近一点,最好是同一个网络下
- 从节点性能不行,从节点没那么强的拉取能力和接收能力,自然会出现一定的延迟
- 从节点开的复制线程太少,数据回放比较慢,解决方法就是使用MySQL提供的并行复制功能
什么是数据库范式,为什么要反范式?
范式就是拆表,去掉冗余数据,少重复,保证数据不乱,核心设计目的就是尽可能节省空间,避免冗余,并且数据一致,好维护
但是遵循范式主要是写入比较简单,而读取麻烦,经常需要查询多个表才能极其自己想要的数据,而现代互联网更多是读多写少
所以反范式就是通过冗余数据来减少多表联查的次数,让读取更快,性能更好,但是写操作比较麻烦,会涉及多个表的修改
如何优化一个大规模的数据库系统?
对热点查询列建立索引,然后如果压力比较大的话,可以考虑使用主从读写读写分离和分表的模式
如果仅仅靠这还是不够的话,可以引入redis做缓存
分表一定能提高性能吗?
不一定,分表提升性能一般是体现在表数据已经过多的情况,此时检索比较慢,所以分表能获取比较不错的性能
为什么不推荐select *?
因为select *就像是黑盒操作,你多加和少加字段都会影响结果,可能会导致一些敏感字段返回。
innodb_flush_log_at_trx_commit、sync_binlog 双一标准是什么?为什么电商金融必须开双一?性能损耗在哪?redo 写缓冲、脏页刷盘机制简单说下?
innodb_flush_log_at_trx_commit 是用来控制 redo log 的刷盘策略
取值有三个:0、1、2
1)= 1(最安全,双一要求)
- 每次事务提交:redo log buffer → 刷入 OS cache → 立即 fsync 落盘
- 特点:事务提交就永久落地,宕机绝不丢已提交事务
- 性能:最慢
2)= 2(折中)
- 每次提交:redo log buffer → 写入 OS cache,但不立即 fsync
- 刷盘时机:由操作系统每秒批量刷一次
- 特点:MySQL 挂了不丢数据;OS 崩溃可能丢 1 秒数据
- 性能:比 1 快很多
3)= 0(性能最高,最不安全)
- 提交时啥也不刷,每秒批量刷一次
- 特点:MySQL 崩溃可能丢最多 1 秒事务
- 性能:最快
sync_binlog 是binlog的刷盘策略
取值:0、1、N(正整数)
1)= 1(最安全,双一要求)
- 每次事务提交,binlog 立即 fsync 落盘
- 特点:主从不一致风险最低,崩溃不丢 binlog
- 性能:最慢
2)= 0(默认)
- 不主动刷盘,交给 OS 自己刷
- 特点:宕机可能丢已提交的 binlog,主从可能不一致
- 性能:高
3)= N(比如 100、1000)
- 每 N 次事务提交,刷一次盘
- 特点:宕机最多丢 N 个事务的 binlog
- 性能:介于 0 和 1 之间
强一致性场景要求必需设置为双1就是事务一旦提交就立刻写入磁盘,保证数据强一致性
性能损耗主要是在大量的刷盘操作,磁盘IO成为性能瓶颈
redo log 写缓冲机制
- 事务执行时,先写 redo log buffer(内存)
- 按策略刷到 os cache
- 再通过 fsync 真正落盘到 redo log 文件(ib_logfile0/1)
- redo log 是顺序写,极快,所以即使双一也能扛一定并发
脏页刷盘机制
- 修改数据时,先改 buffer pool 中的页,此时页是脏页
- 不是实时刷盘,而是异步刷盘
- 触发刷盘的场景:
-
- buffer pool 不够用,需要淘汰脏页
- 主线程后台定时刷(innodb_page_cleaners)
- 脏页比例达到阈值
- 关闭 MySQL、checkpoint 推进
- 脏页刷盘是随机 IO,比 redo log 慢很多