innodb存储引擎笔记(下)

mysql 5.6 optimizer_trace 查看执行计划选择的过程

MVCC

  • innodb的多版本使用undo&回滚段来构建
  • innodb是聚集索引组织表,每个行记录有3个额外属性:ROW_ID、TRX_ID、ROLL_PTR
  • undo记录了更改前的数据镜像,若事务未提交,对隔离级别大于等于read commit的其他事务,它们不应该看到已修改(未提交)的数据,而应一致读取老版本的数据
  • 在修改聚集索引记录时,总是同时存储了ROLL_PTR和TRX_ID,可通过该ROLL_PTR找到对应的undo记录,通过TRX_ID来判断该记录的可见性
  • 当旧版本记录中的TRX_ID指示对当前事务不可见时,则继续向前(更新的TRX_ID)构建,知道找到一个可见的记录。
  • innodb在表空间中保存行的旧版本信息,这些信息被保存在回滚段中
  • 事务标识符(TRX_ID)指示最后插入或更新这个行的事务标识符,删除标志也被认为是一个更新,因为它在提交前指示在杭商做了一个标记。
  • 回滚指针(DB_ROLL_PTR)指向一个由回滚段写入的undo日志记录。如果一个行被更新了,undo日志记录包含了重建这行更新前的信息的一些必要数据
  • 回滚段中的undo日志分为插入日志(主要是insert操作)和更新日志(包含update和delete)。插入日志仅在事务回滚的时候有用,事务提交之后就可以马上删除掉。更新日志在一致性读的时候需要使用,如果当前没有事务再可能使用回滚段中的记录的时候,这些记录就可以删除掉了。因此,最好以适当频率提交事务,否则innodb不能删除掉过期更新日志,回滚段越来越大。
  • 回滚段中undo日志记录的物理大小要比其对应的插入或者更新的行要小(只记录修改的列,不记录完成的列)
  • 当删除某一行时,改行并不会马上从数据库的物理文件上移除,只有当innodb可以清除更新日志记录的时候,那些行机器对应的索引记录才会真正从物理上删除掉,这个清楚操作成为purge。purge以前需要由主线程来掉地,现在5.6版本已经分离出来了

redo

  • ib_logfile文件个数由innodb_log_files_in_group配置决定(至少>=2),文件名序号从0开始,从ib_logfile0到ib_logfileN
  • 文件为顺序写入,循环使用,当达到最后一个文件末尾时,会从第一个文件开始顺序复用,redo文件切换时,会执行一次checkpoint(刷redo log,刷dirty page)
  • 实例重启的过程中,实例关闭以后(正常关闭,不是崩溃),redo是可以删掉的,实例开启之后会重新初始化redo,但是undo不能删
  • redo log用于记录事务操作变化,记录的是数据被修改之后的值(undo记录的是数据被修改之前的值)
  • 不会记录临时表空间上的变化(mysql 5.7起开始有独立的临时表空间)
  • redo记录的是逻辑操作,类似binglog,不像oracle是块记录。它需要应用到一个正确的page上面,如果该page本身被破坏了,则无法恢复出正确的数据,所以需要用到double write buffer
  • 会先放在log buffer(innodb_log_buffer_size)中,而不是立即写磁盘
  • LSN:log sequence number,递增的证书,表示redo总字节数
  • 每次写盘后是否flush,由参数innodb_flush_log_at_trx_commit控制
  • 从5.5开始,redo的大小不再影响crash recovery的耗时,只影响checkpoint频率,设置较大值可减少IO消耗

redo log buffer刷新条件

  • master thread每秒进行刷新
  • redo log buffer使用大于1/2进行刷新
  • 事务提交时进行刷新
    • innodb_flush_log_at_trx_commit={0|1|2}
  • 设置innodb_flush_log_at_timeout(默认1秒)

  • innodb_flush_log_at_trx_commit

    • 0,事务提交时不将redo log buffer写入磁盘
    • 1,事务提交时将redo log buffer写入磁盘
    • 2,事务提交时将redo log buffer些人操作系统缓存
    • 通常建议设置为1,并且设置sync_binlog=1,以保证数据可靠性(双1)
  • innodb_log_buffer
    • 通常8-32M就足够了
  • innodb_log_file_size
    • 一般设置为512M-4G
  • innodb_log_files_in_group
    • 至少两个文件

redo log和binlog的区别

  • redo是物理逻辑日志,binlog是逻辑日志
  • redo是发起时间顺序存储,而binlog是按事务提交时间顺序存储
  • redo log file循环使用,binlog每次新增一个文件
  • binlog更像oracle里的redo归档

这里写图片描述

undo

  • 用于实现MVCC以及回滚
  • 当我们对记录做了变更操作时就会产生undo记录,记录变更前的旧数据
  • undo记录中存储的是老版本的数据,当一个旧的事务需要读取老版本数据时,为了能读到老版本的数据,需要顺着undo链找到满足可见性的记录。当版本链很长时,通常可以认为这个是比较耗时的操作。
  • undo记录默认被记录到系统表空间(ibdata*)中,但是从5.6开始,也可以使用独立的undo表空间
  • 大多数对数据的变更操作包括insert/delete/update
  • 其中insert操作在事务提交前只对当前事务可见,因此产生的undo日志可以在事务提交后直接删除,归类为insert_undo
  • 而对于update/delete则需要维护多版本信息,在innodb里,update和delete操作产生的undo日志被归为一类,即update_undo
  • MySQL5.7在undo上面的变化
    • innodb_undo_logs,rollback seg数量,比如将2G的undo tablespace,切分成多少分rollback seg。默认128个,实例初始化后不可再修改。每个undo log seg可以最多存放1024个事务
    • innodb_undo_tablespaces,undo log文件数,每个文件默认10MB,数量默认0,最大95个,最小2个,因为在truncate一个undo log文件时,需要保证另外一个是可用的,这样就无需停止业务了
    • innodb_max_undo_log_size,控制最大undo tablespace文件的大小,超过这个值尝试truncate undo logs,truncate后的undo logs大小默认恢复为10M
    • innodb_purge_rseg_truncate_frequency,用于控制purge回滚段的频率,默认128,表示purge undo轮询128次后,进行一次undo的truncate
    • innodb_max_purge_lag = 10000 (如果值不为0,当目前所有未purge的undo,如果超过10000,那么新发生的DML都会被阻塞一小段时间)innodb_max_purge_lag 最好设置为0(即默认值)
    • innodb_max_purge_lag_delay = 10000(每10秒至少做一次purge)
    • 以上两个参数的案例,可以参考老叶茶馆的文章《是谁,把innodb表上的DML搞慢的?》
  • MySQL5.7之后
    • 支持在线truncate不用的undo logs
    • set global innodb_undo_log_truncate = 1,手工truncate undo log
    • 当undo超过innodb_max_undo_log_size时进行truncate
    • 总结:三种truncate操作:手工,128次,超过max size
  • show engine innodb status 里的history list length
    • 已提交事务,但未purge的update undo log,也就是等待purge的undo log的大小

事务在redo log、binlog中的逻辑过程

a)事务写入redo log buffer
b)将log buffer刷新到redo log,不过会先写TRX prepare标记
c)写binlog
d)在redo log写入TRX commit标记
e)将写binlog成功的标记写入redo log

  • 若binlog写入完成,则主从库都会正常完成事务;binlog没有写入,则主从库都不会完成事务。不会出现主从不一致的问题,除非trx_commit=0/2才有这个风险
  • slave上master&relay info repository必须是TABLE,且设置relay_log_recovery=1,另外master那边设置双1,才能保证主从数据一致性。

innodb后台线程

默认有15个
- master thread(1个)
- IO thread
- read/write thread(8个,读写默认各4个)
- insert buffer thread(1个)
- log io thread(1个)
- lock monitor thread(1个)
- error monitor thread(1个)
- purge thread(1个)
- page cleaner(flushing) thread(1个)
- MySQL5.6起,master thread的工作已被大大减轻,purge,page clean等成独立线程了

后台线程

  • master thread(主线程)的线程优先级别最高
  • 其内部几个循环(loop)组成:主循环(loop),后台循环(background loop),刷新循环(flush loop),暂停循环(suspend loop)
  • 会根据数据运行的状态在loop,background loop,flush loop和suspend loop中进行切换
  • loop成为主循环,因为大多数的操作都在这个循环中
  • loop循环通过thread sleep来实现,这意味着所谓的每秒一次或10秒一次的操作时不精确的
  • 在负载很大的情况下可能会有延迟

master thread

  • master thread
  • 2个循环
    • 每秒要做的事
    • 每10秒要做的事
  • 如何查看:

    • 5.6以后select * from performance_schema.threads limit 10;
    • 5.6以前show engine innodb status,从background thread里面查看
      这里写图片描述
  • 每秒要做的事

    • 刷新dirty page到磁盘
    • 执行insert buffer merge(change buffer)
    • 刷redo log buffer到磁盘
    • checkpoint
    • 检查dict table cache,判断有无需要删除table cache对象
  • 每10秒要做的事
    • 刷新dirty page到磁盘
    • 执行insert buffer merge
    • 刷redo log buffer到磁盘
    • undo purge
    • checkpoint
  • 实例关闭时
    • 刷redo log到磁盘
    • insert buffer merge
    • 刷redo log buffer 到磁盘
    • 执行checkpoint
  • 优化建议
    • 避免dirty page堆积,适当调整innodb_max_dirty_pages_pct(<=50)
    • 避免undo堆积,调整innodb_max_purge_lag/innodb_max_purge_lag_delay/innodb_purge_batch_size
    • 及时checkpoint,调整innodb_flush_log_at_trx_commit/innodb_adaptive_flushing/innodb_adaptive_flush_lwm/innodb_flush_neighbors/innodb_flush_avg_loops
    • 保持事务持续平稳提交,不要瞬间大事务,或者高频率小事务

checkpiont

  • 定期确认redo log落盘,避免数据丢失,并提高crash recovery效率
  • buffer pool脏数据太多,把脏页刷新到磁盘,释放内存
  • redo log快用完了,把脏页刷新到磁盘
  • redo log切换时,需要执行checkpoint
---
LOG
---
Log sequence number 693064238
Log flushed up to   693064238
Pages flushed up to 693064238
Last checkpoint at  693064238
Max checkpoint age    651585393
Checkpoint age target 631223350
Modified age          0
Checkpoint age        0
0 pending log writes, 0 pending chkp writes
8 log i/o's done, 0.00 log i/o's/second

checkpoint两种方式

  • sharp checkpoint
    • 将所有脏页都刷新回磁盘
    • 刷新时系统hang住
    • 比较暴力,只有在需要干净重启是才需要
    • innodb_fast_shutdown = 0
    • 0,slow,full purge,insert buffer merge(也就是不允许fast shutdown,要求做完整的关闭操作)
    • 1,默认,fast,skip these operation
    • 2,flush logs,cold status,like crashed
  • fuzzy checkpoint
    • 持续将脏页刷新回磁盘
    • 对系统影响较小,但可能刷新较慢,会有迟滞
    • innodb_max_dirty_pages_pct = 75
    • innodb_max_dirty_pages_pct_lwm = 0

问:什么情况下innodb_fast_shutdown要设为0(保证数据安全)
答:
1. 实例升级版本
2. 主从切换
3. 实例迁移
4. 物理关机

page cleaner(flushing)(脏页的刷新)

  • 将脏页刷新落地到硬盘
  • 有两种方式
    • LRU Flushing,基于LRU_list(基于最后访问时间的排序)的刷新顺序
    • Adaptive Flushing,基于Flush_list(严格按照最后修改时间的顺序,LSN)的刷新顺序,innodb_adaptive_flushing = 1
  • 扫描列表,并找到邻居页面(innodb_flush_neighbors = 1,机械盘适用,SSD盘可关闭),一起刷新
  • 刷新过程
    • 将脏页拷贝到double write buffer
    • 刷新double write buffer到文件
    • 同步double write buffer到磁盘
    • 写数据文件
    • 同步刷数据文件到磁盘,确保落地

undo purge

  • 简单说,就是GC(garabge collection)
  • purge都做啥
    • 删除辅助索引中不存在的记录
    • 删除已被打了delete-mark标记的记录
    • 删除不需要的undo log
  • 从5.6开始,将purge thread独立出来
    • –innodb_purge_threads = 1
    • –innodb_max_purge_lag = 0
    • –innodb_purge_batch_size = 300
  • 案例:删除大量旧数据后,统计min(pkid)很慢

insert buffer/change buffer

  • 将非唯一辅助索引上的IUD操作从随机变成顺序IO,提高IO效率
  • 官方测试号称约提高15倍
  • 工作机制
    • 先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入
    • 若不在,则先放入到一个change buffer对象中
    • change buffer也是棵树,B+树
    • 每次最多缓存2k的记录
    • 当读取辅助索引页到缓冲池,将insert buffer中该页的记录合并到辅助索引项
  • –innodb_change_buffer_max_size(这个是百分比,默认25%,表示最多有25%的buffer pool用来做change buffer)
  • –innodb_change-buffering(默认all,即包括insert update delete,none就是什么都不做,此外还有insert,update,delete)

    • 1.fast shutdown不进行insert buffer合并
    • 2.insert buffer进行合并插入时,tps会受影响
    • 3.insert buffer占用一部分buffer pool,如果辅助索引不多,可以考虑关闭或调低insert buffer
  • show engine innodb status查看insert buffer相关内容

-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 1289, seg size 1291, 316623 merges
merged operations:
 insert 249806, delete mark 1123127, delete 85482
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 2365399, node heap has 3863 buffer(s)
Hash table size 2365399, node heap has 2606 buffer(s)
Hash table size 2365399, node heap has 3849 buffer(s)
Hash table size 2365399, node heap has 2100 buffer(s)
Hash table size 2365399, node heap has 2409 buffer(s)
Hash table size 2365399, node heap has 6709 buffer(s)
Hash table size 2365399, node heap has 1118 buffer(s)
Hash table size 2365399, node heap has 3659 buffer(s)
37515.35 hash searches/s, 4553.64 non-hash searches/s

注释:
size 1 =>正在使用的page
free list len => 空闲的page
seg size =>总的insert buffer page数量size + free list len + 1
insert buffer的效果 = merges / (insert + delete mark + delete)

double write,双写

  • 目的/作用:保证数据写入的可靠性(防止数据页损坏,又无从修复)
  • 因为innodb有partial write问题
    • 16k的页只写入了部分数据时发生crash
    • redo里记录的是逻辑操作,不是物理块,无法通过redo恢复
  • 怎么解决partial write问题

    • 双写,double write
    • 2个1M的空间,共2M(既有磁盘文件,也有内存空间)
    • 页在刷新时首先顺序的写入到double write buffer
    • 然后再刷新回磁盘
    • 在可以保证原子写的硬件设备或文件系统下,可以被关闭
    • slave上也可以关闭
    • double write写入是顺序的,性能损失很小(SSD设备上损失则比较大)
    • MySQL5.7起,采用PCIe SSD设备是会自动判断,是否要关闭double write buffer
    • 先写double buffer,再写磁盘,如果double write写失败了,那么肯定没写入磁盘
      这里写图片描述
  • double write的状态统计(mysqladmin ext | grep -i dbl)

    • innodb_dblwr_pages_written(发起多少次写的请求)
    • innodb_dblwl_writes(实际写了多少次)
    • 理想比例是64:1,因为1M包含64个page,但是很难达到这高比例
  • 性能损失
    • –innodb_double = 0关闭,1打开
    • status
    • innodb_dblwr_pages_written
    • innodb_dblwr_writes

预热

  • buffer pool dump& restor 启动预热
    • innodb_buffer_pool_filename
    • innodb_buffer_pool_dump_now
    • innodb_buffer_pool_dump_at_shutdown
    • innodb_buffer_pool_load_now
    • innodb_buffer_pool_load_at_startup
  • 手工预热
    • select count(*) from t force index(primary)
    • select count(*) from t
    • select * from t
  • 可以防止数据库刚开起来因为承受不了瞬间到来的物理读请求而秒崩,强烈建议开启(5.6以后才有的功能,且可以在线动态开启)。

adaptive hash index

  • 对buffer pool中热点索引页数据再次进行索引
  • 目的:缓存索引中的热点数据,提高检索效率,O(1) VS O(N)(从对B+树的搜索变为对hash的搜索)
  • 对热点buffer pool建立AHI,非持久化
  • 只支持等值查询
    • idx_a_b(a,b)
    • where a = xx
    • where a = xx and b = xx
  • AHI很可能是部分长度索引,并非所有查询都能有效果
  • 设置innodb_adaptive_hash_index = 0 关闭
  • 设置innodb_adaptive_hash_index_parts 使用AHI分区/分片降低竞争提高并发
  • 个别场景下,开了AHI后,可能导致spin_wait lock 比较大,可以关闭掉
  • 评估自适应hash索引的作用(show engine innodb status)
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 1289, seg size 1291, 316623 merges
merged operations:
 insert 249806, delete mark 1123127, delete 85482
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 2365399, node heap has 3863 buffer(s)
Hash table size 2365399, node heap has 2606 buffer(s)
Hash table size 2365399, node heap has 3849 buffer(s)
Hash table size 2365399, node heap has 2100 buffer(s)
Hash table size 2365399, node heap has 2409 buffer(s)
Hash table size 2365399, node heap has 6709 buffer(s)
Hash table size 2365399, node heap has 1118 buffer(s)
Hash table size 2365399, node heap has 3659 buffer(s)
37515.35 hash searches/s, 4553.64 non-hash searches/s

注:AHI的作用 hash searches / (hash searches + non-hash searches)

crash recovery

  • redo,redo前滚结束后,server开始对外提供服务,后面的过程放在后台线程继续工作
  • 当实例从崩溃中恢复,需要将活跃的事务从undo中提取出来,对于ACTIVE状态的事务直接回滚,对于prepare状态的事务,如果该事物对应的binlog已经记录,则提交,否则回滚事务
  • change buffer merge
  • purge
  • xa recover

加快crash recovery速度

  • 升级到5.5以后的版本
  • 提高IO设备性能
  • 适当调低innodb_max_dirty_pages_pct,50以下
  • 设置innodb_flush_log_at_trx_commit = 1,让每个事务尽快提交,避免有其他事务等待,产生大量的undo,增加purge工作量
  • 5.7又进一步改进,crash recovery时无需扫描所有数据文件并创建内存对象(数据文件巨多时会产生严重性能问题),而只检查checkpoint+那些标记为被修改过的文件,从一个checkpoint点开始,可以找到所有崩溃恢复需要打开的文件,从而避免扫描数据目录

innodb引擎重点参数

  • innodb_buffer_pool_size
    • 最大的内存块,建议为物理内存的50-80%
  • innodb_max_dirty_pages_pct
    • buffer pool中dirty page最大占比,建议不超过50%
  • innodb_old_blocks_pct
    • buffer pool中old block sublist最大占比,默认3/8
  • innodb_change_buffering
    • change buffer 类型,ALL或其他
  • innodb_log_buffer_size
    • redo log buffer,能缓存5秒左右产生的redo就够,32MB基本管够
  • innodb_sort_buffer_size???(后续研究)
    • innodb往表中批量加载数据更新索引。以及Online DDL时,将当前发生的DML记录到临时log中???。(不要只看字面意思)
  • innodb_data_file_path
    • 共享表空间初始大小,建议至少1G以上
  • innodb_log_file_size
    • redo log大小,加大有助于减小checkpoint频率,提高tps
  • innodb_flush_log_at_trx_commit
    • redo log刷新机制,1最安全,0性能最好,2折中
  • innodb_io_capacity
    • innodb 后台线程最大iops上限
  • innodb_flush_methon
    • 刷新innodb data file和log file使用方式,推荐O_DIRECT
  • innodb_stats_on metadat = 0
    • 执行show table status / show index时是否更新统计信息
  • innodb_autoinc_lock_mod = 1
    • auto-inc锁模式,推荐1
  • innodb_file_per_table =1
    • 是否启用独立表空间,推荐1。5.5开始可动态修改
  • innodb_fast_shutdown = 0/1
    • 是否快速关闭,推荐1。需要版本升级或机器重启是,要改为0。
  • innodb_force_recovery = 0
    • innodb恢复级别,可选0-6,从最小开始尝试启动。默认一定要设置为0。

你可能感兴趣的:(mysql)