MySQL45讲
09讲普通索引和唯一索引
假设,执行查询的语句是 select id from T where k=5
查询过程
对于普通索引来说,查找到满足条件的第一个记录(5,500)后,需要查找下一个记录,直到碰到第一个不满足k=5条件的记录。
对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。
那么,这个不同带来的性能差距会有多少呢?答案是,微乎其微。
你知道的,InnoDB的数据是按数据页为单位来读写的。也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存。在InnoDB中,每个数据页的大小默认是16KB。
因为引擎是按页读写的,所以说,当找到k=5的记录的时候,它所在的数据页就都在内存里了。那么,对于普通索引来说,要多做的那一次“查找和判断下一条记录”的操作,就只需要一次指针寻找和一次计算。
当然,如果k=5这个记录刚好是这个数据页的最后一个记录,那么要取下一个记录,必须读取下一个数据页,这个操作会稍微复杂一些。
但是,我们之前计算过,对于整型字段,一个数据页可以放近千个key,因此出现这种情况的概率会很低。所以,我们计算平均性能差异时,仍可以认为这个操作成本对于现在的CPU来说可以忽略不计。
更新过程
change buffer:当更新一个数据的时候,如果这个数据所在的数据页在内存里面,那么就会直接更新掉。如果不在内存里面的时候,
那么会先将更新的操作记录在change buffer里面。等到下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。
将change buffer的操作记录应用到原数据页,这个过程称为merge,除了访问数据页会触发merge,系统也会有后台线程定期merge。确保数据的完整性。
唯一索引:由于唯一索引每一次更新数据的时候,都需要将数据页查询出来,判断数据是否重复,索引唯一索引不会触发change buffer。
将数据从磁盘读入内存涉及随机IO的访问,是数据库里面成本最高的操作之一。change buffer因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。
change buffer适用于写多读少的业务。
总结:如果业务上可以确保数据不会重复,那么就用普通索引,可以利用change buffer,性能上会比较好。
如果业务上不能确保数据不会重复,必须借助唯一索引来判断,那么没办法,就使用唯一索引。
如果在大数据量插入的情况下,发现效率慢,那么唯一索引也是一个排查的方向。
13讲为什么表数据删掉一半,表文件大小不变
我给你简单描述一下引入了Online DDL之后,重建表(alter table t engine = InnoDB)的流程:
建立一个临时文件,扫描表A主键的所有数据页;
用数据页中表A的记录生成B+树,存储到临时文件中;
生成临时文件的过程中,将所有对A的操作记录在一个日志文件(row log)中,对应的是图中state2的状态;
临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表A相同的数据文件,对应的就是图中state3的状态;
用临时文件替换表A的数据文件。
- 从MySQL 5.6版本开始,alter table t engine = InnoDB(也就是recreate;
- analyze table t 其实不是重建表,只是对表的索引信息做重新统计,没有修改数据,这个过程中加了MDL读锁;
- optimize table t 等于recreate+analyze。
24讲MySQL是怎么保证主备一致的
MySQL的主从同步是通过binlog日志来实现的。MySQL对于数据库所有的修改的修改命令都会记录在binlog日志里面。
格式有两种:statement和row
statement:记录的是真实的命令
row:记录的是原有的数据信息,以及真实数据的主键ID。
建议使用row格式,虽然row格式会占用更多的磁盘空间,消耗IO磁盘,执行速度会慢一点。
但是row格式可以支持误操作后恢复数据。
25讲MySQL是怎么保证高可用的
主备延迟的来源
有些部署条件下,备库所在机器的性能要比主库所在的机器性能差。
第二种常见的可能了,即备库的压力大
大事务,比如一次性的delete大量的数据。
大表DDL
由于主备延迟的存在,所以在主备切换的时候,就相应的有不同的策略。
可靠性优先策略
- 判断备库B现在的seconds_behind_master,如果小于某个值(比如5秒)继续下一步,否则持续重试这一步;
- 把主库A改成只读状态,即把readonly设置为true;
- 判断备库B的seconds_behind_master的值,直到这个值变成0为止;
- 把备库B改成可读写状态,也就是把readonly 设置为false;
- 把业务请求切到备库B。
可用性优先策略
如果我强行把步骤4、5调整到最开始执行,也就是说不等主备数据同步,直接把连接切到备库B,并且让备库B可以读写,那么系统几乎就没有不可用时间了。这个切换流程的代价,就是可能出现数据不一致的情况。
总结:在满足数据可靠性的前提下,MySQL高可用系统的可用性,是依赖于主备延迟的。延迟的时间越小,在主库故障的时候,服务恢复需要的时间就越短,可用性就越高。
26讲备库为什么会延迟好几个小时
备库并行复制能力。
备库通过io_thread读取binlog日志写到relay log 里面,然后通过sql thread 从relay log 里面读取日志执行写到数据磁盘里面去。
备库并行复制能力:通过coordinator拆成多个线程,coordinator就是原来的sql_thread, 不过现在它不再直接更新数据了,只负责读取中转日志和分发事务,真正更新日志的,变成了worker线程。
所以,coordinator在分发的时候,需要满足以下这两个基本要求:
- 不能造成更新覆盖。这就要求更新同一行的两个事务,必须被分发到同一个worker中。
- 同一个事务不能被拆开,必须放到同一个worker中。
各个版本的多线程复制,都遵循了这两条基本原则。
MySQL不同版本对于备库的并行复制能力做了不同的优化,目的都是通过多线程复制,降低备库的延迟时间。
28讲读写分离有哪些坑
由于主从延迟会导致过期读,比如插入或者修改了数据,在从库上查询不到数据,或者数据没有修改。
删除了数据,在从库上依然能够读到数据。
解决方案有:
- 强制走主库。业界使用最多。
- Sleep方案。比如页面上先给个提示。
- 判断主备无延迟方案
- 等主库位点方案
- GTID方案
33讲我查这么多数据,会不会把数据库内存打爆
由于MySQL是边查询边发送的逻辑,所以对于数据量很大的查询,不会再server端保存完整的结果集。
如果客户端读数据不及时,会堵住MySQL的查询过程,但是不会把内存打爆。
此外,全表扫描也是非常耗费IO的。
34讲到底可不可以使用join
可以使用join,但不建议多个表进行join。并且join的使用尽量使用小表座位驱动表,join的大表字段上需要建立索引。
言而总之,还是尽量少用join吧。sql尽量简单、清晰。
39讲自增主键为什么不是连续的
唯一键冲突
事务
自增锁的优化:MySQL在select … insert批量插入数据的时候,会批量申请ID,每次申请到id都是上一次的两倍。
这两个原有都会导致,主键是自增但是非连续的。