MySQL45讲


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)的流程:

  1. 建立一个临时文件,扫描表A主键的所有数据页;

  2. 用数据页中表A的记录生成B+树,存储到临时文件中;

  3. 生成临时文件的过程中,将所有对A的操作记录在一个日志文件(row log)中,对应的是图中state2的状态;

  4. 临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表A相同的数据文件,对应的就是图中state3的状态;

  5. 用临时文件替换表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是怎么保证高可用的

主备延迟的来源

  1. 有些部署条件下,备库所在机器的性能要比主库所在的机器性能差。

  2. 第二种常见的可能了,即备库的压力大

  3. 大事务,比如一次性的delete大量的数据。

  4. 大表DDL

由于主备延迟的存在,所以在主备切换的时候,就相应的有不同的策略。

可靠性优先策略

  1. 判断备库B现在的seconds_behind_master,如果小于某个值(比如5秒)继续下一步,否则持续重试这一步;
  2. 把主库A改成只读状态,即把readonly设置为true;
  3. 判断备库B的seconds_behind_master的值,直到这个值变成0为止;
  4. 把备库B改成可读写状态,也就是把readonly 设置为false;
  5. 把业务请求切到备库B。

可用性优先策略

如果我强行把步骤4、5调整到最开始执行,也就是说不等主备数据同步,直接把连接切到备库B,并且让备库B可以读写,那么系统几乎就没有不可用时间了。这个切换流程的代价,就是可能出现数据不一致的情况。

总结:在满足数据可靠性的前提下,MySQL高可用系统的可用性,是依赖于主备延迟的。延迟的时间越小,在主库故障的时候,服务恢复需要的时间就越短,可用性就越高。

26讲备库为什么会延迟好几个小时

备库并行复制能力。

备库通过io_thread读取binlog日志写到relay log 里面,然后通过sql thread 从relay log 里面读取日志执行写到数据磁盘里面去。

备库并行复制能力:通过coordinator拆成多个线程,coordinator就是原来的sql_thread, 不过现在它不再直接更新数据了,只负责读取中转日志和分发事务,真正更新日志的,变成了worker线程。

所以,coordinator在分发的时候,需要满足以下这两个基本要求:

  1. 不能造成更新覆盖。这就要求更新同一行的两个事务,必须被分发到同一个worker中。
  2. 同一个事务不能被拆开,必须放到同一个worker中。

各个版本的多线程复制,都遵循了这两条基本原则。

MySQL不同版本对于备库的并行复制能力做了不同的优化,目的都是通过多线程复制,降低备库的延迟时间。

28讲读写分离有哪些坑

由于主从延迟会导致过期读,比如插入或者修改了数据,在从库上查询不到数据,或者数据没有修改。

删除了数据,在从库上依然能够读到数据。

解决方案有:

  1. 强制走主库。业界使用最多。
  2. Sleep方案。比如页面上先给个提示。
  3. 判断主备无延迟方案
  4. 等主库位点方案
  5. GTID方案

33讲我查这么多数据,会不会把数据库内存打爆

由于MySQL是边查询边发送的逻辑,所以对于数据量很大的查询,不会再server端保存完整的结果集。

如果客户端读数据不及时,会堵住MySQL的查询过程,但是不会把内存打爆。

此外,全表扫描也是非常耗费IO的。

34讲到底可不可以使用join

可以使用join,但不建议多个表进行join。并且join的使用尽量使用小表座位驱动表,join的大表字段上需要建立索引。

言而总之,还是尽量少用join吧。sql尽量简单、清晰。

39讲自增主键为什么不是连续的

  1. 唯一键冲突

  2. 事务

  3. 自增锁的优化:MySQL在select … insert批量插入数据的时候,会批量申请ID,每次申请到id都是上一次的两倍。

    这两个原有都会导致,主键是自增但是非连续的。


  目录