第二弹来了兄弟们,为了避免错别字我又通读了一遍,应该没错别字了。
自增主键为什么不连续?
有三种情况会导致自增主键不连续:唯一键冲突、事务回滚、自增锁(AUTO-INC Locks)的优化。
本来如果在发生唯一键冲突或者事务回滚的时候,能回退自增主键的话,就不会出现自增主键不连续的情况。但是这样做的代价太大了,所以InnoDB没有这样设计。因为如果允许主键回退,那么并发事务的情况下就可能出现主键冲突(例如事务A申请了2,事务B申请了3,这时候事务A回滚,自增主键回退到2,接下来执行的事务申请到3的时候就会出现主键冲突)。要解决主键冲突,要么检测并跳过冲突主键,要么就让事务顺序执行,但是这两种方案效率都很低。
由于INSERT...SELECT这类批量插入语句无法精确计算出需要多少个主键,因此自增主键要在用到的时候申请,数据量大的时候逐个申请效率会很低,InnoDB采用的方案是第一次申请一个,之后每次申请上一次的两倍数量,所以会存在申请了用不上的情况。(参考innodb_autoinc_lock_mode,0是语句锁,1是语句锁+轻量级锁、2是轻量级锁,默认是1。默认配置下如果预先知道要插入的条数会走轻量级锁)
自增id用完怎么办?
单个表的自增id达到上限后,自增值就不会改变了,之后如果再继续插入就会报主键冲突。
隐藏的row_id达到上限后会被归0,如果同一个表中出现相同的row_id,新写入的数据会直接覆盖旧数据,不会报错。
Xid是server层维护的,占8个字节,达到最大值之后会归0。Xid会保存到binlog文件里,所以理论上一个实例执行2^64个语句后binlog文件里就会出现重复的Xid,但是这个值太大了,所以几乎不可能重复。加上重启实例后会生成新的binlog文件,Xid从0开始重新开始计数,达到最大值的可能性就更低了。
InnoDB的max_trx_id占8个字节,每次MySQL重启时会持久化,达到最大值之后会被归0。如果max_trx_id达到最大值之后被归0,会出现脏读的情况(归0后新事务的trx_id小于已开启的事务的trx_id,假设原来存在一个事务A,归0后新开启的是事务B。事务B的开启时间是晚于事务A的,但是我们会发现在事务A中可以看到事务B中插入的数据)。
为什么有的语句kill不掉?
kill语句有kill connection和kil query两种,前者内部包含后者,不带参数默认执行的是kil connection。不管是哪一种,本质上都是修改线程状态,等待线程进入埋点位置处理终止逻辑。(原理)
如果innodb_thread_concurrency(InnoDB并发线程数)设置的过小,线程得不到执行机会就会卡住。这时候使用kill query就会发现语句执行不生效,但是使用kill connection可以看到连接被终止。虽然可以看到连接被终止,但是使用show processlist仍然会看到线程处于killed状态。这是因为连接虽然被断开了,但是线程依然需要等待执行机会,之后才能开始处理终止逻辑。这种问题可以通过调大并发线程数解决。(场景一)
还有一种情况是因为终止逻辑耗时过长,因为终止逻辑需要回收资源。大事务、大查询、DDL等操作都有可能导致终止逻辑执行时间过长。(场景二)
MySQL为什么会出现抖动?
主要有三种情况会导致MySQL出现服务抖动:redo log被写满、内存不足、innodb_io_capacity配置错误。
redo log被写满后需要推进checkpoint,此时所有的写操作都会被阻塞,这种情况很少发生,除非redo log空间设置的过小。
内存不足的情况下,加载新数据页到内存需要淘汰旧数据页,如果旧数据页是脏页的话就需要刷盘,这里的随机写入是比较费时间的,如果innodb_flush_neighbors参数开启的话那可能会更慢,因为临近的脏页会一起刷盘,而且这个功能还允许连续触发。
innodb_io_capacity用于告诉InnoDB所在系统的磁盘能力,如果在磁盘能力很强的情况下使用了默认配置的话,就会导致磁盘发挥不出真正的能力而让服务出现抖动。另外可以关注下脏页比例innodb_max_dirty_pages_pct,不要让脏页比例超过75%。
普通索引和唯一索引怎么选择?
由于唯一索引数据的唯一性,唯一索引读到第一条记录后即可返回结果,但是普通索引读到第一条记录后还需要向后读取,直到碰到第一条不符合条件的记录。但是我们知道数据库的数据是以数据页为单位加载到内存的,所以普通索引只多一次寻址和计算,因此查询性能两者差别不大。(读取)
从更新性能上来看的话,普通索引比唯一索引好,因为普通索引可以用到InnoDB的change buffer,在内存中记录更新后即可返回。但是唯一索引由于要保证唯一性,需要从磁盘加载数据页判断是否存在重复。(更新)
InnoDB里数据的修改基于聚簇索引,change buffer优化的是表中包含二级索引的情况。如果没有change buffer,每次更新二级索引数据页都要从磁盘加载数据,使用change buffer可以减少随机IO的次数。(change buffer)
将change buffer应用到数据页的过程称为merge,merge会在三种场景下执行:访问数据页、后台定时任务、数据库正常关闭。(merge)
写多读少的业务场景建议开启change buffer,可以提高系统性能。写入后马上就会读取的场景建议关闭change buffer,因为这样会导致每次访问都必须执行merge,这种情况下change buffer反而是一个负担。
为什么数据删掉之后表文件大小不变?
因为InnoDB会复用数据行和数据页的空间,删除数据时,由于要支持MVCC功能,数据是标记删除的,等到数据库中现存Read View的trx_id都大于数据行的trx_id时,该数据就可以真正删除了。这时候会把该数据行移入数据页里的垃圾链表中,等待被复用。数据页里的所有记录被删除后,也会被标记为可复用,不会真的释放磁盘空间。(空间复用)
如果想要在删除表数据后缩小表文件的大小,可以使用两种方式:ALTER TABLE tbl ENGINE=InnoDB 或者 OPTIMIZE TABLE tbl。前者是重建表,后者是重建表和表对应的索引。(释放空间)
MySQL是怎么保证数据不丢的?
MySQL本身并不支持crash-safe,提供crash-safe功能的是InnoDB,MySQL本身的binlog只能提供备份功能。不使用InnoDB的话,如果出现数据库崩溃或者机器宕机,内存中没来得及刷盘的数据会丢失。
InnoDB通过redo log来保证数据库的crash-safe,即使出现数据库崩溃或者机器宕机,redo log也能保证数据库重启后正常恢复数据。控制redo log刷盘机制的参数是innodb_flush_log_at_trx_commit,可选配置有0、1、2。0表示只写入内存不刷盘,1表示每次写入内存的时候都刷盘,2表示只写入系统缓冲区(os page cache),操作系统负责刷盘。
Server层通过sync_binlog参数来控制binlog的刷盘机制,可选配置有0、1,N。0表示只写入内存不刷盘,1表示每次写入内存的时候都刷盘,N表示每N次事务提交后才会刷盘。
这两个参数都设置为1时最安全,但是相对的性能也最差。线上环境尽量还是配置成双1,一般来讲对系统性能不会有很大影响,可靠性很高。
误删数据后如何恢复?
误删行可以用Flashback工具来恢复,但要确保binlog_format=ROW和binlog_row_image=FULL。建议在备份出来的新数据库上执行,确认数据恢复后再利用备份恢复整库。误删库/表的情况可以利用整库备份加增量日志来恢复。如果是在HA集群中使用rm命令删除了数据的话,HA系统会自动选出新的主库。
要避免误删数据可以用两个方法:1.创建一个指定延迟时间的备库,只要在这个时间内发现数据误删,就可以通过这个备库来恢复数据。2.账号权限分离,业务操作不要给删除库/表的权限。
业务中可不可以用JOIN?
业务中,如果连接字段能够用上被驱动表的索引,可以大大减少需要扫描的数据行数,提高执行效率,这种情况下是可以使用JOIN语句的。
数据库进行JOIN的时候,如果在连接字段上没有索引,默认会使用Block Nested Loop Join(BNL)算法,这个算法会把驱动表的数据放入join buffer,以block(块)的方式和被驱动表进行比较。如果连接字段上存在索引的话,那么会使用Index Nested Loop Join(NLJ)算法,这个算法不使用join buffer,但是在访问被驱动表时可以用上索引,所以速度会比较快。
以上两种算法中BNL是不推荐使用的,NLJ是推荐使用的。如果想要更高的性能的话,NLJ依然有进一步优化的空间。MySQL里面有一个优化叫Multi-Range Read(MRR),在使用二级索引进行范围查询时通过对id进行递增排序后再回表,可以利用顺序读的优势提高查询效率,JOIN语句的终极优化算法Batched Key Access(BKA)中用的就是MRR。另外BKA算法在用到MRR的同时还使用了join buffer进一步提升了执行效率。
需要注意的是join buffer中会放入所有查询列表和查询条件,SELECT *会导致join buffer只能放入少量数据,所以JOIN的时候尽量避免使用SELECT *语句。
数据库调优有哪些手段?
遵循最左前缀原则,按照索引定义的字段顺序写SQL
使用覆盖索引
使用联合索引
利用索引下推
尽量用普通索引少用唯一索引,这样可以用上change buffer的优化。
使用前缀索引,既能优化查询效率,也能减少空间占用。
控制脏页比例,默认75%,一般默认即可。
优化刷盘策略,关注innodb_io_capacity和innodb_flush_neighbors参数。
条件字段不要执行函数和计算操作,可能会导致不走索引,因为系统会认为执行计算可能破坏索引的有序性。
隐式编码转换也可能导致索引失效,同一个库的编码尽量保持一致。
8.0以下版本记得排除查询缓存的干扰
好了,这篇就先发这些吧,后面还有两篇加锁和索引相关的。如果没研究过MySQL的话,文章里面可能有些关键字你都不知道是啥,我也不详细解释了,可以在电脑上打开看,边看边搜哈哈。
推荐阅读:
文档推荐:
https://dev.mysql.com/doc/refman/5.7/en/




