InnoDB

InnoDB

MySQL-InnoDB

架构

CheckPoint

已经被flush到页上的LSN。

刷盘策略

  • 缩短数据库恢复时间
  • 缓冲池不够用时,将脏页刷到磁盘
  • 重做日志不够用时,刷新脏页

Sharp CheckPoint

  • 关闭数据库时

  • innodb_fast_shutdown=0

    • full purge
    • merge insert buffer
    • flush dirty page
  • innodb_fast_shutdown=1

    • flush dirty page
  • innodb_fast_shutdown=2

    • flush redo log only

Fuzzy CheckPoint

  • Master Thread CheckPoint

  • FLUSH_LRU_LIST CheckPoint

    • 保证约100个空闲页可用

    • Innodb1.1.x版本前

      • 在用户查询线程中查询空闲页,不够CheckPoint,会阻塞用户线程
    • MySQL5.6之后

      • 由Page Cleaner线程查询

        • innodb_lru_scan_depth修改空闲页数量
  • Async/Sync Flush CheckPoint

    • 重做日志空间不足时
    • checkpoint_age = redo_lsn - checkpoint_lsn
    • async_water_mark = 75% * total_redo_log_file_size
    • sync_water_mark = 90% * total_redo_log_file_size
    • checkpoint_age < async_water_mark NO FLUSH
    • async_water_mark < checkpoint_age < sync_water_mark Async Flush
    • checkpoint_age > sync_water_mark Sync Flush
    • Inonodb1.2.x前,Async Flush阻塞查询线程,Sync Flush阻塞所有用户线程,MySQL5.6之后由Page Cleaner处理
  • Dirty Page too much CheckPoint

    • innodb_max_dirty_pages_pct

后台线程

Master Threads

  • loop

    • per seconds

      • 刷新日志缓冲(总是)

        即使事务还未提交,日志也会刷盘,可解释为什么很大的事务提交时间也很短(只需日志刷盘)

      • 合并插入缓冲(可能)

        前一秒IO次数小于5次

      • 刷新最多100个脏页(可能)

        buf_get_modified_radio_pct>innodb_max_dirty_pages_pct

      • 没有用户活动,切换到background loop(可能)

  • background loop

    • 数据库空闲或shutdown时切换到此

IO Threads

  • 处理异步IO

  • write

    • innodb_write_io_threads
  • read

    • innodb_read_io_threads
  • insert buffer

  • log

Purge Threads

  • 回收undo页
  • innodb_purge_threads

Page Cleaner Threads

  • 刷盘脏页

内存

缓冲池

  • innodb_buffer_pool_size

  • 设置多个缓冲池

    • innodb_buffer_pool_instances
  • information_schema.INNODB_BUFFER_POOL_STATS

  • Insert Buffer\Change Buffer

    将索引叶子节点不在缓存池的插入操作缓存起来,暂时不合并到非唯一辅助索引的叶子节点,之后批量合并到叶子节点。

    Why 辅助索引:聚簇索引的插入往往是顺序递增的(如自增主键),不会产生随机IO,而insert buffer是用来将离散的插入操作合并提升性能的。

    Why 非唯一:插入唯一辅助索引时,需要将叶子节点读出判断索引是否唯一,此时叶子节点已被读入缓存池,插入操作便可直接合并了。

    Change Buffer对DML操作均可缓存。

    • 合并对非唯一辅助索引页的插入

    • Insert Buffer B+ Tree

      全局一颗B+树,存放在共享表空间。

      • 非叶子节点

        • space,表id
        • offset,页偏移
      • 叶子节点,change record

        • space,offset

        • metadata

          • record进入Insert Buffer的顺序
        • record data

          插入记录的各字段值。

    • Insert Buffer Bitmaps页

      • 追踪16384个辅助index page
      • index page容量
      • index page是否有record在B+树中
      • index page是否是insert Buffer B+的索引页
    • Merge Insert Buffer

      • 辅助索引页被读到缓冲池
      • 由bitmap追踪到index page容量不足(随机选择index page检查)
      • Master Thread发起

List

  • LRU List

    • midpoint

      为了防止大批量、遍历操作,如select * from table这种操作淘汰掉LRU列表所有热点数据,MySql将页面读入内存时,先插入的midpoint处,淘汰掉列表尾的节点,这样可以防止批量操作淘汰midpoint之前的数据(真正的热点数据),同时,提供innodb_old_blocks_time参数,设定midpoint之后的页面,在加入LRU列表指定时间之后,移动到midpoint之前

      • 5/8
      • innodb_old_blocks_pct
      • innodb_old_blocks_time
    • unzip LRU Page?

  • Free List

  • Flush List

    • 存放LRU中被修改过的页,checkpoint 刷盘

redo log buffer

  • innodb_log_buffer_size
  • Master Threads每秒Flush
  • 事务提交后Flush
  • 剩余空闲小余1/2时Flush

other buffer

  • 缓冲池的帧缓冲

  • 缓冲控制对象

    • LRU、锁、等待等信息

double write

先将脏页memcpy到共享表空间128个连续页,刷盘

再将脏页刷盘

写到一半宕机,根据连续页数据恢复

为什么redoLog不能恢复?

当物理页损坏时,redolog只记录了物理页的更改,无法恢复,但doublewrite的共享表空间buffer记录了页的完整备份。

若下层存储如LUN或FS提供了此功能,数据库中应关闭

文件

数据库文件

  • 日志文件

    • 错误日志(err log)

      show variables like 'log_error';

    • 二进制日志(binlog)

      数据库日志,记录包括Innodb存储引擎在内的所有存储引擎产生的日志记录。
      Innodb重做日志文件只记录Innodb存储引擎事务产生的事务日志。

      • STATEMENT

        记录SQL语句,在SQL语句中存在类似随机函数时,远程复制从端会和主端数据不一致。

      • ROW

        记录表行的更改情况,还是逻辑更改,不同于重做日志的记录页的物理更改,可解决STATEMENT的远程复制主从数据不一致问题。

      • MIXED

        在会产生数据不一致的函数调用时用ROW,其他用STATMENT。

      • 记录Mysql事物的逻辑更改

      • 通过拷贝二进制日志同步数据到slave

      • 事务提交前写盘,无论事务多大

        sync_binlog=[N]
        每缓冲写N次,就刷一次盘;
        当N=1时,将不使用操作系统的缓冲来写二进制日志。

    • 慢查询日志(slow query log)

      show variables like 'long_query_time';
      show variables like 'log_slow_queries';
      show variables like 'log_queries_not_using_indexes';

      mysql.slow_log

    • 查询日志(log)

      记录所有对MySQL数据库请求的信息,无论请求是否得到了正确的执行。

Innodb文件

  • 重做日志

    • 记录Innodb事物对每个页的物理更改

    • 事务进行过程中不断将redo entry写盘

    • 格式

      • redo_log_type
      • space
      • page_no
      • redo_log_body
    • 按最小单位扇区写入,保证写入成功,不需double write

    • innodb_flush_log_at_trx_commit

      0:事务提交时不写入重做日志,仅由Master Thread每秒fsync,未写入文件系统缓存,数据库宕机就会丢失日志。
      1:事务提交时必须fsync重做日志(默认)。
      2:事务提交时,仅将重做日志写入文件系统缓存,若数据库宕机,重做日志不会丢失,操作系统宕机时,丢失Master Thread还未fsync的日志。

    • 物理结构

      LOG_BLOCK_HDR_NO:大概是log block在Redo Log Buffer这个数组中的下标。LOG_BLOCK_HDR_DATA_LEN:记录log block写入了多少字节数据。LOG_BLOCK_FIRST_REC_GROUP:记录log block中第一个事务日志的偏移。LOG_BLOCK_CHECKPOINT_NO:该log block最后一次被写入时的check point第4字节值。
      LOG_BLOCK_TRL_NO:同LOG_BLOCK_HDR_NO。

innodb逻辑存储结构

  • 表空间

    • 共享表空间

        • 数据段

          • 32碎片页

            使用完后按区分配空间

            • 1MB/64pages

            • 数据页 B-tree Node

              • 每页最多16KB/2-200行,最少2行

              • File Header

                38字节
                记录页的头信息

                • checksum

                  4字节

                • page offset

                  4字节
                  查询(space,1)->(10,1)
                  查询表空间id为10的第二个页

                • prev page

                  4字节
                  当前页的前一个页

                • next page

                  4字节
                  当前页的下一个页

                • LSN

                  8字节
                  当前页最后被修改的日志序列位置LSN

                  LSN(重做日志中):事务被写入到重做日志的总量,单位字节。
                  LSN(页中):表示该页最后flush时的LSN大小。
                  数据库启动时,若检测到重做日志中的LSN大于页中的LSN,并且重做日志中大于的部分,事务已经提交,则需将该部分事务应用到页,以恢复。

                  Log sequence number,日志序列号,不断随着事务被记录到重做日志,而更新重做日志的LSN。
                  Log flush up to,已经被记录到重做日志的Log sequence number。
                  Last checkpoint at,对应页中LSN。

                • page type

                  2字节

                • flush LSN

                  8字节
                  仅在系统表空间的一个页中定义,代表文件至少被更新到该LSN,独立表空间中值为0

                • space id

                  4字节

              • Page Header

                56字节
                记录页的状态信息

                • dir slots

                  2字节
                  在Page Directory中的slot数

                • heap top

                  2字节
                  堆中第一条记录的指针,记录在页中以堆得形式存放(X)
                  代表空闲空间的开始位置偏移量

                • N heap

                  2字节
                  堆中的记录数,第15位表示行记录格式

                • free

                  2字节
                  指向可重用空间的首指针

                • garbage

                  2字节
                  已删除记录的字节数,对应记录结构中delete flag 为1的记录总大小

                • last insert

                  2字节
                  最后插入记录的位置

                • direction

                  2字节
                  最后插入的方向
                  PAGE_LEFT
                  PAGE_RIGHT
                  PAGE_SAME_REC
                  PAGE_SAME_PAGE
                  PAGE_NO_DIRECTION

                • N direction

                  2
                  一个方向连续插入记录的数量

                • N recs

                  2字节
                  该页中记录的数量

                • max trx id

                  8字节
                  修改该页的最大事务号

                • level

                  2字节
                  该页在索引树中的层级

                • index id

                  8字节
                  记录当前页属于哪个index

                • btr seg leaf

                  10字节
                  数据页非页节点所在段的
                  segment header,仅在B+树的root页定义

                • btr seg top

                  10字节
                  数据页所在段的segment header

              • Infimun and Supremun Records

                页创建时建立,用于限定页中记录的边界。

              • User Records

                  • Compact

                    • 变长字段长度列表

                      变长字段小于255字节,长度为1字节,大于255字节,对应长度2字节,变长字段最大2字节,对应varchar最长65535字节

                    • NULL标志位

                      1字节

                    • record header

                      5字节

                      - ()
                      - ()
                      - deleted_flag

                      1bit
                      该行是否被删除
                      

                      - min_rec_flag

                      1bit
                      该行是否被定义为最小的记录
                      

                      - n_owned

                      4bit
                      该记录拥有的记录数
                      作为directory slots
                      

                      - heap_no

                      13bit
                      索引堆中该条记录的排序记录
                      

                      - record_type

                      3bit
                      记录类型
                      000普通
                      001B+树节点指针
                      010Infimum
                      011Supremum
                      1xx保留
                      

                      - next_record

                      16
                      页中下一条记录的相对位置
                      
                    • 事务ID
                      - 回滚指针
                      - 列1数据
                      - 列2数据...
                      - 行溢出数据

                      varchar BLOB TEXT

                      当不能保证一个page不能至少存放两条记录时,产生行溢出数据,数据页中存放前缀数据,其余数据存放在BLOB page。

                      page至少存放两条记录,防止B+树退化成链表

                      - 768prefix前缀数据
                      - 偏移量

                  • Redundant

                  • Compressed

                    • 对BLOB采用完全行溢出

                      行数据中只存指针,实际数据都存在Off Page

                    • 行数据采用zlib算法压缩

                  • Dynamic

              • Free Page

                • 空闲空间

                  记录被删除后被放到空闲链表

              • Page Directory

                • Directory Slots

                  B+树的目录槽

                  非数据页,目录槽存放键值范围,及范围第一个记录的页偏移,记录中存放键值及数据页的页号,通过页号找到数据页

                  数据页,记录records的页偏移,通过目录槽追踪(二叉查找)到记录查询结果的页,将页读入内存,然后在内存中,再通过recorder header的next_header来找到最终结果

                  聚簇索引并非按索引的顺序严格存放物理记录,否则会带来巨大的维护开销,数据页和页中的记录,都是通过双向链表维护顺序的。

              • File Trailer

                8字节

                • FILE_PAGE_END_LSN

                  8字节

                  前4字节 checksum

                  后4字节 同File Header中的FILE_PAGE_LSN
                  为了检测页是否完整写入磁盘

        • 索引段

          • B-tree Node level
        • 回滚段(rollback segment)

          Innodb 1.1之后,最大支持128个rollback segment,每个rollback segment包含1024个undo log segment,所以最大并发事务为128 * 1024.

          • undo log segment

            • undo页
          • 1024个 undo log segment

        • 其他段

          • 系统页
          • 事务数据页
          • Insert Buffer Bitmap
          • Insert Buffer Free List
          • UnCompressed BLOB Page
          • Compressed BLOB Page
    • innodb_file_per_table

      • 数据段
      • 索引段
      • insert buffer bitmap页

Index

存储引擎选择不使用索引的原因

当使用辅助索引不能返回所有需要查询的列时,会通过辅助索引查到主键值,在通过主键值使用聚集索引查找完整列,由于经过了辅助索引后,再访问聚集索引,在需要查询的记录量比较大时,会产生大量随机IO,在机械盘的情况下,性能会大大降低,这时优化器可能选择直接遍历聚集索引(顺序IO)。

但在需要查询的记录量比较小时,可能依然先使用辅助索引,或者在SSD盘的情况下,用户可设置先使用辅助索引。
select * from table force index(.)...

MRR优化

在通过辅助索引查询大量记录的时候,MRR优化先将辅助索引查询得来的主键ID排序,再通过主键ID顺序查询记录整行记录,以此将随机IO转换为顺序IO;
另外,MRR可以将范围查询拆分为键值对查询,如 select * from t where idx1 >= 1000 and idx1 << 2000 and idx2 = 10000;
其中(idx1,idx2)为联合索引。
MRR将会过滤掉idx2 = 10000的数据,只取出满足查询条件的主键ID页,否则将先返回满足idx1条件的数据,再在内存中过滤掉idx2条件。。。

Index Condition Pushdown

Mysql5.6之后将Index Filter 过滤条件在innodb存储引擎索引层面过滤,5.6之前是只使用Index first key和Index last key查询记录,再将记录返回到server层过滤。

Innodb根据事务访问的每个页对锁进行管理,采用位图的方式,锁住一页中的一条记录和多条记录开销差不多。

自增长列

  • AUTO-INC Locking

    为了自增长列++互斥的锁,在完成插入自增长列的语句之后释放锁。

  • Mutex

    使用互斥量来互斥内存中自增长计数累加的过程;
    在statment-based方式的replication:
    对于simple inserts,插入的记录数确定,并发情况下,插入记录的自增列主从一致;
    对于bulk inserts,插入的记录数不确定,并发插入时,可能出现主从自增列不一致。

  • 插入类型

    • insert-like

      所有插入类型

    • simple inserts

      能确定插入行数
      如insert

    • bulk inserts

      不能确定插入行数
      如 insert...select...

    • mixed-mode inserts

      插入的记录有些自增长列是确定的,有些是自增长的。

  • innodb_autoinc_lock_mode

    0:AUTO-INC Locking
    1:simple inserts 使用mutex互斥预分配插入条数对应的自增列;其它使用AUTO-INC Locking。
    2:所有插入语句都使用mutex,并发性能提高,但在replication下会出现主从自增列不一致问题。

外键

对子表插入记录时,会先select父表,查看父表是否存在相应的外键值,此时select不再读快照,而是采用select ... lock in share mode的方式,若此时父表相应外键已经被加了X锁,则子表的插入阻塞。

这样是为了防止死锁,若select 父表采用快照读,则会读到已经加了X锁记录的快照,然后完成对子表的插入,之后加X锁的事务再修改外键,造成子表父表外键对应不上。

调优

CPU

OLTP应用属于IO密集型,采购设备应更关注IO能力强的配置。
根据CPU支持的核数,设置数据库线程(purge,IO等)的数量,由于是IO密集型,可适当调整线程为核的两倍。

内存

预估数据库“活跃”数据量。
判断内存是否达到瓶颈:
缓冲池命中率>99%

硬盘/SSD

RAID/阵列

测试工具

XMind - Trial Version

你可能感兴趣的:(InnoDB)