本文为极客时间《MySQL实战45讲》的总结笔记,如有侵权,请通知我,将立马删除。建议大家去购买这套课程,真的是物有所值。
9. 选择普通索引还是唯一索引?
9.1 查询过程
其实查询过程两者的时间差距是微乎其微的,普通索引要比唯一索引多一次判断下一条记录是否符合,但InnoDB 的数据是按数据页为单位来读写的,所以就算多读一次也占用不了多少时间
9.2 更新过程
普通索引可以使用change buffer,可以将一系列的更新写到change buffer中,后期再一次性写入到磁盘中,极大的提高了更新的效率,而唯一索引没有办法使用change buffer
9.3 change buffer 的使用场景
对于写多读少的业务来说,页面在写完以后马上被访问到的概率比小,此时 change buffer 的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。反过来,如果所有的更新后面,都马上伴随着对这个记录的查询,那么你应该关闭change buffer
10. MySQL为什么有时候会选错索引?
Mysql是根据扫描行数来判断选择哪个索引,扫描行数越少,则越容易被选择,查看扫描行数可以通过explain
关键字来查看。
1 | explain select * from user where sex = 1 |
既然Mysql是根据扫描行数来选择索引的,它选错索引肯定也是因为在判断扫描行数的时候出了问题,那Mysql又是怎么来判断扫描行数的呢?答案是:采样统计
是的就是这么不靠谱的采样,Mysql官方也说了这个误差可能会达到30%~50%
对于这种情况我们可以使用analyze table 表名
来重新统计索引信息达到让Mysql选择正确的索引。或者使用force index
来强制给它指定一个索引
1 | select * from user force index(sex) where sex = 1 |
11. 怎么给字符串字段加索引?
1. 利用前缀索引
如果字符串过长,而前面几个字段可以确定一个唯一值,比如邮箱,前面都是几位数字+@qq.com,我们不用给全部字段加上索引,而只需要索引前面几个数字即可,这样就极大的节省索引占的空间了。
1 | alter table user add index index_email(email,9) |
这个数字9怎么去确定呢,我们可以通过下面的语句,来尝试,如果查出来的值越少,就越好。
1 | select count(distinct left(email,7)) as L7, |
2. 反转字符串
有的时候字段前面都是一样,而后面是不一样的,比如身份证号,这时就不好利用前缀索引了,不过我们可以将身份证的倒序存储,这样就巧妙的再次利用前缀索引的优势了。
1 | select * from t where id_card = reverse('input_id_card'); |
3. 使用Hash
这种就是将字符串计算出一个hash值,然后给表新增一个字段将hash存储进去,下次查找时先将字符串换算为hash再去表中查找hash列,不过这种只适合等值查询,不能进行范围查询。
12. 为什么我的MySQL会“抖”一下?
当Mysql执行过程中会突然慢下来,过一会又好了,而且不是随机的,持续时间很短,看起来就好像Mysql“抖”了一下。这个过程其实是Mysql在刷”脏页”的过程。
当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“”脏页”,当把内存中的数据更新到硬盘之后这个页也就变成了“干净页”
大概下面四种情况会发生刷“脏页”过程:
- redo log 写满了。当redo log写满了之后,系统会停止所有更新操作,将redo log的checkpoint往前推进,让redo log可以留出空间继续写。
- 系统内存不足,淘汰脏页时。当需要新的内存页,而内存不足时就需要淘汰一些数据页空出内存给别的数据页使用,如果淘汰的是“脏页”,那么就需要把“脏页”数据写入到磁盘中。
- 当系统空闲时。当系统空闲时就会将一点一点的将脏页数据刷新到磁盘中
- 当Mysql正常关闭时。Mysql正常关闭时会将内存中的脏页数据全部刷新到磁盘中。
我们可以通过设置innodb_io_capacity
参数来控制Mysql刷脏页的速度,如果你用的是SSD,那么建议你把这个参数设置大点。这个参数在information_schema
数据库中的GLOBAL_VARIABLES
表中设置。
13. 为什么表数据删掉一半,表文件大小不变?
当innodb_file_per_table
的参数为OFF时,表的数据会放到共享内存中,也就是和数据字典放一块。而为ON时,表的数据存储在以.ibd
为后缀的文件中,当我们使用drop table
删除表时,会直接删除这个文件达到回收的目的,而如果数据是放到了共享内存中,那么即使表删除了,空间也是不会回收的。所以我们一般都将此参数设置为ON,MySQL5.5.6版本之后默认就是ON了。
13.1 删除流程
当我们删除某一行记录时,其实MySQL只是把此行记录标记为了“可复用”,但磁盘大小是不会变的,所以通过delete
表中记录是达不到回收表空间的。这些被标记为“可复用”而没有使用的空间看起来就像是“空洞”,其实不止删除会造成空洞,插入一样可以,如果我们不是按顺序插入,而是随机插入,那么就可能造成页分裂,而之前那一页末尾可能还有未使用的空间。
13.2 怎么回收表空间
我们可以通过重建表来达到回收表空间,可以使用下面这个命令:
1 | alter table 表名 engine = InnoDB |
三种重建方式对比:
recreate重建表
1
alter table 表名 engine = InnoDB
重新统计索引信息
1
analyze table 表名
recreate + 重新统计索引信息
1
optimize table 表名
14. count(*)这么慢,我该怎么办?
以下的讨论都是没有where条件判断的,如果有条件判断,则不适用。
- 对于MyISAM引擎,它会将一个表的总行数存储在磁盘中,所以它的count(*)效率很高
- 而对于InnoID引擎,由于MVCC多版本并发控制,它必须一行一行的去读取然后计算总数。
执行速度比较
count(其他字段) < count(主键) < count(1) ≈ count(*)
解决方案
- 将总数存储在Redis中(不推荐,可能会导致数据不一致)
- 单独存储到MySQL一张表中(可使用事务,来避免数据不一致等情况)
15. 日志和索引相关问题
15.1 MySQL 怎么知道 binlog 是完整的?
答: 一个事务的 binlog 是有完整格式的
- statement 格式的 binlog,最后会有 COMMIT;
- row 格式的 binlog,最后会有一个 XID event
15.2 redo log 和 binlog 是怎么关联起来的?
答:它们有一个共同的数据字段,叫 XID。崩溃恢复的时候,会按顺序扫描 redo log
- 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
- 如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。
15.3 处于 prepare 阶段的 redo log 加上完整 binlog,重启就能恢复,MySQL 为什么要这么设计?
答:其实,这个问题还是跟我们在反证法中说到的数据与备份的一致性有关。在时刻 B,也就是 binlog 写完以后 MySQL 发生崩溃,这时候 binlog 已经写入了,之后就会被从库(或者用这个 binlog 恢复出来的库)使用。所以,在主库上也要提交这个事务。采用这个策略,主库和备库的数据就保证了一致性。
15.4 能不能只用redo log,不要binlog?
回答:如果只从崩溃恢复的角度来讲是可以的。你可以把binlog
关掉,这样就没有两阶段提交了,但系统依然是crash-safe
的。
但是,如果你了解一下业界各个公司的使用场景的话,就会发现在正式的生产库上,binlog
都是开着的。因为binlog有着redo log无法替代的功能。
- 一个是归档。
redo log是循环写,写到末尾是要回到开头继续写的。这样历史日志没法保留 ,redo log也就起不到归档的作用。
- 一个就是MySQL系统依赖于binlog。
binlog作为MySQL一开始就有的功能,被用在了很多地方。其中,MySQL系统高可用的基础,就是binlog复制。
- 还有很多公司有异构系统(比如一些数据分析系统)
这些系统就靠消费MySQL的binlog来更新自己的数据。关掉binlog的话,这些下游系统就没法输入了。
总之,由于现在包括MySQL+高可用在内的很多系统机制都依赖于binlog,所以“鸠占鹊巢”redo log还做不到。