mysql数据库(InnoDB引擎)笔记

基于《MySQL技术内幕(InnoDB存储引擎)第2版》一书整理的笔记

注:使用Sublime Text编辑的,博客显示效果并不理想,可粘贴到本地使用Sublime Text打开阅读。

 

存储引擎是基于表的,而不是基于数据库的。

MyISAM引擎不支持事务,不支持行级锁,支持全文索引,主要面向一些OLAP数据库应用。MyISAM的缓冲池只缓存索引文件,不缓存数据文件。

InnoDB后台线程:
    Master Thread:
        日志缓冲刷新到磁盘,刷新脏页到磁盘,合并插入缓冲,UNDO页的回收。
    IO Thread:
        InnoDB大量使用AIO来处理I/O请求。InnoDB1.0之前版本有4个IO Thread,分别是read,write,insert buffer,log IO Thread。InnoDB1.0.x版本开始,read thread和write thread分别增大到了4个。
    Purge Thread:
        事务被提交后,其所使用的undolog可能不再需要,因此需要Purge Thread来回收已经使用并分配了的undo页。
    Page Cleaner Thread:
        InnoDB1.2x版本后引入的,将脏页刷新放到单独的线程中执行,减轻Master Thread的工作,降低用户查询的延迟。

InnoDB中内存:
缓冲池:
    InnoDB的存储引擎是基于磁盘的,由于CPU的磁盘的速度差异很大,使用内存缓冲池来弥补磁盘速度较慢对数据库的影响。在数据库进行读操作时,首先将磁盘读到的页放入缓冲池中,下次读到相同的页时,判断该页是否在缓冲池中,若在,则直接读取缓冲池中数据,否则从磁盘读取。
    页的修改操作通过CheckPoint机制刷新回磁盘。
    缓冲池的数据页类型有:索引页,数据页,undo页,插入缓冲,自适应哈希索引,InnoDB存储的锁信息,数据字典信息等。

    InnoDB缓冲池基于优化的LRU算法进行管理。在LRU列表中加入midpoint,新读取到页不是放在LRU列表的首部,而是放在midpoint位置,这是因为某些sql操作,如索引或数据的扫描操作,会访问表中的许多页,这些页通常来说只在这次查询中需要,而不是热点数据,如果放在LRU首部很可能导致热点数据被从LRU列表删除。还需要引入一个参数来表明数据页到达mid位置后需要多久才能被放到LRU热端。

重做日志缓冲:
    重做日志缓冲刷新到磁盘的情况:
        Master Thread每秒钟将重做日志缓冲刷新到重做日志文件。
        每个事务提交时会将重做日志缓冲刷新到重做日志文件。
        当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件。
额外的内存池

为了避免数据丢失,当前事务数据库系统普遍采用了 Write Ahead Log策略,先写重做日志,再提交事务。

CheckPoint主要解决以下几个问题:
    1缩短数据库的恢复时间
    2缓冲池不够用时,将脏页刷回磁盘
    3重做日志不可用时,刷新脏页
        重做日志不可用的情况:因为当前事务数据库系统对重做日志的设计都是循环使用的,并不能无限增大。重做日志可以被重用的部分指这些日志已经不再需要,这部分数据就可以被覆盖使用,如果此时重做日志还需要使用的话,就强制CheckPoint,将缓冲池中的页至少刷新到当前重做日志的位置。

两种CheckPoint:Sharp CheckPoint 、 Fuzzy CheckPoint
    Sharp CheckPoint:发生数据库关闭时,将所有脏页刷新回磁盘
    Fuzzy CheckPoint:只刷新一部分脏页,而不是所有。
        4种可能导致Fuzzy CheckPoint的情况:
            1Master Thread CheckPoint
            2FLUSH_LRU_LIST CheckPoint
            3Aysnc/Sync Flush CheckPoint
            4Dirty Page to much CheckPoint

Master Thread 
    Master Thread具有最高的线程优先级别。内部由多个循环组成:主循环(loop),后台循环(background loop),刷新循环(flush loop),暂停循环(suspend loop)
    主循环(loop):主要进行两大部分的操作:每秒钟的操作和每10秒种的操作
        每秒钟的操作包括:
            1日志缓冲刷新到磁盘,即使该事务还未提交(总是)  (解释了为什么再大的事务的提交时间也是很短的)
            2合并插入缓冲(可能 ,当前一秒内IO次数小于5次则执行)
            3刷新至多100个innodb缓冲池的脏页到磁盘(可能,判断缓冲池中脏页比例是否超过设置的阈值)
            4如果当前没有用户活动,则切换到background loop(可能)
        每10秒的操作包括:
            1刷新100个脏页到磁盘(可能,过去10秒钟内IO次数小于200则执行)
            2合并至多5个插入缓冲(总是)
            3将日志缓冲刷新到磁盘(总是)
            4删除无用的Undo操作(总是)
            5刷新100个或10个脏页到磁盘(总是)
    后台循环(background loop):如果当前没有用户活动或关闭数据库,则会切换到background loop
        执行的操作:
            1删除无用的Undo页(总是)
            2合并20个插入缓冲(总是)
            3跳回到主循环(总是)
            4不断刷新100个页直到符合条件(可能,跳转到flush loop中完成)
    若flush loop中无事可做,InnoDB引擎会切换到suspend_loop,将Master Thread挂起。

InnoDB引擎的关键特性
    1插入缓冲(Insert Buffer)
        含义:对于非聚集索引的插入或更新操作, 并不是每次直接插入到索引页(由于非聚集索引的插入的离散型),而是判断插入的非聚集索引页是否在缓冲区,若是则直接插入,若不在则先放入InsertBuffer,再以一定频率将Insert Buffer和辅助索引叶子阶段进行merge操作,因为此时通常能够将多个插入合并到一个操作中,提高插入性能。
        要满足的两个条件:
            1索引是辅助索引
            2索引不是唯一的
        合并InsertBuffer时机:
            1辅助索引页被读取到缓冲池
            2Insert Buffer Bitmap检测到该辅助索引页已无可用空间
            3Master Thread
    2两次写(Double Write)
        含义:引入目的为提高数据库的可靠性,在应用重做日志前,用户需要一个页的副本,当写入失效发生时,先通过该页的副本还原该页,再按重做日志进行恢复,主要避免某个页写入一半发生了错误而导致页损坏。
        组成:分为两部分。一部分是内存中的doublewrite buffer,大小2MB,一部分是物理磁盘上的共享表空间中连续的128页,即两个区,也是2MB。
        过程:对缓冲区的脏页进行刷新时,并不直接写磁盘,而是先通过memcpy函数将脏页复制到doublewrite buffer,然后doublewrite buffer分两次写,每次1M写入到共享表空间的物理磁盘,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题。
    3自适应哈希索引(Adaptive Hash Index)
        含义:InnoDB会监控对表上各索引页的查询,如果构建哈希索引能够带来速度的提升,则会构建哈希索引(AHI)。AHI是根据缓冲池中的B+数索引页来构建的,不必对整张表构建AHI,因此构建速度很快。InnoDB会根据访问的模式和频率自动为某些热点数据构建哈希索引。
        要求:
            1这个页的连续访问模式必须相同,如where a=XXX或where a=xxx and b=xxx 但是两种模式不能交替执行。
            2以该模式访问了100次
            3页通过该模式访问了N次,其中N=页中记录数/16;
        适用范围:只适合等值查询,不适合范围查询等
    4异步IO(Async IO)
        优势:
            1用户发起IO请求后可以立即执行其他操作无需等待。
            2可以进行IO merge操作,将多个IO请求合并为一个。
    5刷新邻接页(Flush Neighbor Page)
        含义:当刷新脏页时,InnoDB存储引擎会检测该页所在区的所有页,如果是脏页,则一起刷新。
        好处:便于AIO进行IO请求合并(适合于机械硬盘)


第四章:表
索引组织表:InnoDB存储引擎中,表都是根据主键顺序组织存放的,这种组织方式为索引组织表。
    InnoDB中每张表都有主键,如果用户没有给定主键,会采用下列方案创建主键:
    1首先判断表中是否由非空的唯一索引(Unique NOT NULL),如果有将该字段设为主键。(如有多个,则选择建表时定义索引的第一个)
    2如果不符合上述条件,则会自动创建一个6字节大小的指针。

InnoDB逻辑存储结构:所有数据被逻辑的存放在一个空间里,称为表空间(tablespace),表空间又由段(segment)区(extent)页(page)组成。页中还有一些文档有时称为块(block)
    表空间:ibdata1,逻辑存储结构的最高层,所有数据都放在表空间。
    段:表空间由各个段组成,包括数据段(Leaf segement),索引段(Not-leaf segement),回滚段(Rollback segement)等
    区:由连续的页组成的空间,每个区大小固定为1MB,默认情况下页的大小为16kB,所以默认一个区有64个连续的页。
    页:磁盘管理的最小单位,默认为16KB,页大小一旦确定就无法修改。常见页类型有:
        数据页(B-tree Node)
        Undo页(Undo log page)
        系统页(System Page)
        事务数据页(Transaction system page)
        插入缓冲位图页(Insert Buffer Bitmap)
        插入缓冲空闲列表页(Insert Buffer Free List)
        未压缩的二进制大对象页(Uncompressed BOLB Page)
        压缩的二进制大对象页(Compressed BOLB Page)
    行:InnoDB引擎是面向列的,也就是数据是按行存储的。每个也做多存储16KB/2-200行的记录(即7992行)。
        行记录格式分为:Compact和Redundant(为兼容之前版本)两种(属于Antelope文件格式)
            Compact格式:为了高效的存储数据(即在一个页中放更多的数据)而引入,存储格式:变长字段长度列表(按列逆序),NULL标志位,记录头信息,列1数据,列2数据。。。每行数据有两个隐藏列,事务ID列和回滚指针列,另外如果表没有指定主键,则还有一个6字节的列rowid。Compact格式下NULL值不占任何空间。
            Redundant格式:存储格式:变长字段偏移列表(按列逆序),记录头信息,列1数据,列2数据。。。
        行数据溢出:InnoDB会将某些大的数据真正的数据以外(BLOB,LOB,VARCHAR等并不一定)评判标准:一个页至少有两条行记录。
        新的文件格式Barracuda:Compressed和Dynamic两种行记录格式。
            新的两种行记录格式对于存放在BLOB中的数据采用了完全的行溢出方式。另外Compressed行记录格式会对存储其中的记录采用zlib的算法进行行压缩。

约束和索引的区别:约束是一个逻辑概念,用来保证数据的完整性。索引是一个数据结构,既有逻辑概念,又在数据库中代表物理存储的方式。

物化视图:Oracle支持物化视图,可以预先计算并保存多表的链接(Join)或聚集(Group By)等耗时的sql操作,这样在进行复杂的查询时可以避免这些耗时的操作。

Mysql支持水平分区,不支持垂直分区。此外Mysql的分区时局部分区索引,一个分区既包含了数据又包含了索引,全局分区是指,数据存放在各个分区,而数据的索引集中存放在一起,目前Mysql不支持全局分区。
    常见的分区方式有:RANGE分区、LIST分区、Hash分区、KEY分区,Columns分区。

第五章、索引与算法
InnoDB支持以下几种常见的索引:    
    1B+树索引
        B+树索引只能找到被查找数据行的页,再将该页读入内存,在页中查找行。并不能直接找到对应行。
        由于每页PageDirectory中的糟是按照主键存放的,对于页中的具体记录是采用二分法查找的。
    2全文索引
    3哈希索引


聚簇索引(clustered index)和辅助索引(secondary index)的区别:聚簇索引叶子节点存放的是一整行的记录,而辅助索引存放的是指向记录的指针。因此聚簇索引能够直接获取行的信息,辅助索引则还需要一次指针定位。这也意味着更新操作对聚簇索引的影响更大。
聚簇索引的特点:
    聚簇索引对于主键的范围查询和排序查询非常快,适合like 'xxx%'(索引前缀查找)。
    聚簇索引的存储并不是物理上连续的,而是逻辑上连续的。主要体现在以下两点:
        1数据页是通过双向链表连接的,页按主键顺序连续。
        2页中记录也是通过双向链表维护的,物理上可以不按主键存储。
辅助索引:不包含行信息。除了包含键值外,每个节点还包含一个书签(bookmark),用来告诉InnoDB引擎哪里可以找到与索引相对应的行数据(即相应数据行的聚簇索引键,再通过聚簇索引找到对应的行记录)

Mysql5.5前创建或删除索引的方式:
    1创建一张新临时表,表结构为通过命令ALTER TABLE新定义的结构
    2将原来表数据导入到新临时表
    3删除原来表
    4把临时表命名为原表
InnoDB 1.0x开始支持一种Fast Index Creation(FIC)的索引创建方式。在创建辅助索引时,对该表加一个S锁。在创建过程中不需要重建表。删除辅助索引则只需要Innodb更新内部视图,并将辅助索引的空间标记为可用,然后删除mysql数据库内部视图对该表的索引定义即可。

添加B+树索引的情况:
    1.经常访问表中很少一部分行记录时
    2.某个字段取值范围很广,几乎没有重复时
        show index查询中的Cardinality值就是索引中不重复记录数量的预估值
    3.满足上述条件并且经常作为查询条件的字段

Cardinality值:
    对Cardinality值的获取是通过采样方式完成(减少开销)
    对Cardinality值的更新是发生在INSERT和UPDATE操作中,更新策略:
        1表中1/16数据已经发生了变化
        2stat_modified_counter>2000000000(发生变化的次数)
    对Cardinality值的更新是通过对8个叶子节点进行采样,每次统计也会更新Cardinality值

联合索引:本质上也是B+树,不同的是联合索引的键值数量不是1,而是大于等于2(有可能避免排序,如查询一个人最近购买的N件商品)

覆盖索引(covering index):即可以从辅助索引中直接得到查询的记录,而不需要再访问一次聚集索引,但辅助索引不包含整行记录的所有信息,可以减少大量的IO操作。

优化器不选择使用索引的情况:

Multi-Range Read(MRR)优化:mysql5.6开始支持MRR优化。目的是减少磁盘的随机访问,转化为较为顺序的数据访问。可以用来优化,range,ref,eq_ref类型的查询。
    MRR优化的好处:
        1是数据访问变得比较顺序,查询辅助索引时,首先根据查询到的结果根据主键进行排序,然后按照主键排序的顺序进行书签查找。
        2减少缓冲池中页被替换的次数。
        3批量处理对键值的查询操作。
    对于InnoDB和MyISAM存储引擎的范围查询和JOIN操作,MRR的工作方式如下:
        1将查询到的辅助索引键值放到一个缓存中,此时缓冲中数据是根据辅助索引键值排序的。
        2将缓存中的键值根据rowid进行排序。
        3根据rowid的顺序来访问实际数据文件。

Index Condition Pushdown(ICP)优化:mysql5.6开始支持ICP优化。
    在不支持ICP前,进行索引查询时,先根据索引查找记录再根据WHERE条件来过滤记录。引入ICP后可以在取出索引记录的同时进行WHERE条件过滤(将WHERE的部分过滤操作放在存储引擎层),大大减少上层sql对记录的索取(fetch)。
    ICP支持range,ref,eq_ref,ref_or_null类型的操作。

InnoDB中的哈希算法:冲突机制采用链表法,哈希函数采用除法散列方式

第六章、锁
lock和latch:
    latch是一种轻量级锁,其要求锁定的时间必须非常短。又可以分为mutex(互斥锁)和rwlock(读写锁),其目的是用来保证并发进程操作临界资源的正确性,通常没有死锁检测的机制。
    lock的对象是事务,用来锁定数据库中的对象,如表、页、行。一般lock的对象仅在事务commit或rollback后释放,lock有死锁机制。

锁(lock)的类型:
    InnoDB提供了两种标准的行级锁:
        共享锁(S Lock):允许事务读一行数据
        排他锁(X Lock):允许事务删除或更改一行数据
    InnoDB支持意向锁,意向锁是将锁的对象分为多个层次,如果把上锁的对象看成一个棵树,那么对最下层的对象上锁,也就是对最细粒度的对象上锁,那么首先要对粗粒度的对象上锁。例如,如果要对页上的记录r加X锁,那么分别需要对数据库、表、页上加意向锁IX锁,再在记录r上加X锁。
    InnoDB的意向锁为表级别的锁,其目的是为了在一个事务中揭示下一行被请求锁的类型,其支持两种意向锁:
        意向共享锁(IS Lock):事务想要获取一张表某几行的共享锁
        意向排他锁(IX Lock):事务想要获取一张表某几行的排他锁
    InnoDB存储引擎支持的锁 行级别的锁,因此意向锁其实不会阻塞除全表扫描以外的其他操作。

一致性非锁定读(REPEATABLE READ级别):InnoDB通过行多版本控制的方式来读取当前执行时间数据库中的行数据。如果当前读取的行正在执行DELETE或UPDATE操作,这时候读取操作不会因此去等待行上锁的释放,而是会读取一个行的快照数据(数据未修改时的版本)。其实现是通过undo段来完成。
    具体实现:多版本并发控制(MVCC) 参考:  https://www.cnblogs.com/lmj612/p/10598971.html

一致性锁定读:InnoDB对SELECT语句支持两种一致性锁定读操作:
    SELECT ... FOR UPDATE (加X锁)
    SELECT ... LOCK IN SHARE MODE (加S锁)
    

自增长与锁:
    解释:InnoDB的内存中每个含有自增长值的表都含有一个自增长计数器(auto-increment counter),每次插入,这个计数器值就+1,这种实现方式叫做AUTO-INC Locking,是一种特殊的表锁机制,此锁并不是事务完成后释放,而是自增长值插入的SQL语句结束时释放。
    性能问题:
        1对自增长值的列并发插入性能较差,事务必须等待前一个插入的完成。
        2对于INSERT...SELECT的大数据量的插入会影响插入的性能,因为另一个事务的插入会被阻塞。
    Mysql版本5.1.22后,InnoDB提供了一种轻量级互斥量的自增长实现机制。

外键和锁:
    解释:InnoDB中如果对于一个外键列没有显示添加索引,系统会自动给此列添加一个索引,这样看可以避免表锁(为啥?)
    注意:对于外键值的插入或更新,首席按要查询父表中的记录,即SELECT父表。但是对父表的SELECT操作并不是使用一致性非锁定读的方式,因为这样会导致数据不一致,而是采用SELECT ... LOCK IN SHARE MODE的方式,即主动对父表加一个S锁。如果此时父表已经加了X锁,则子表的操作会被阻塞。


锁的算法:
    行锁的算法:InnoDB引擎上有三种行锁算法:
        Record Lock:单个行记录上的锁
        Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
        Next-Key Lock:Gap Lock + Recent Lock,锁定一个范围,并锁定记录本身。
    InnoDB所有的行锁算法都是基于索引实现的,锁定的也都是索引或索引区间;
    InnoDB对行的查询都采用Next-Key Lock,如果一个索引有10,11,13,20这四个值,那么该索引可能被Next-Key Lock的区间为(-无穷,10](10,11],(11,13],(13,20],(20,+无穷),其设计的目的是为了解决Phantom Problem。如果查询的索引含有唯一属性,则将其降级为Recent Lock(查询的列有唯一索引才会降级,若是辅助索引则不会,辅助索引除了对加Next-Key Lock外还会对辅助索引的下一个键值加上Gap Lock)
    两种方式显式关闭Gap Lock:
        1将事务隔离级别设置为READ COMMITTED
        2将参数innodb_locks_unsafe_for_binlog设置为1
        可能导致的问题:破坏事务的隔离性,对于replication,可能导致主从数据不一致。

解决Plantom Problem(幻象问题)
    Plantom Problem含义:指同一事务下连续执行两次相同的SQL操作可能导致不同的结果,第二次SQL语句可能返回之前不存在的行。

脏读:一个事务可以读到另一个事务未提交的信息,违反了事务的隔离性。例如:事务A对插入了一条数据,还未提交,事务B读取时读到了插入的数据,之后事务A进行了回滚,这样B就读到了不存在的数据。

不可重复读:一个事务多次读取一个数据集合,由于其间其他事务对数据进行了修改,导致两次读取的结果不同。违反了事务的一致性要求。
    脏读和不可重复读的区别:脏读读到的是未提交的数据,不可重复读读到的是已提交的数据。

丢失更新:两个事务同时对一个数据进行更新操作,导致其中一个事务的更新操作被覆盖。(即使是最低级别的READ UNCOMMITED也能避免这种情况)

死锁的处理:
    超时
    wait-for graph(等待图):是一种更主动的死锁检测方式。InnoDB采用这种方式。图中主要存在两种信息:
        锁的信息链表
        事务等待链表

锁升级:将当前锁的粒度降低。例如将1000个行锁升级为一个页锁,或者将页锁升级为表锁。
    InnoDB不存在锁的升级,因为其不是根据诶个记录来生成行锁的,其根据每个事务访问的每个页对锁进行的管理,采用的是位图的方式,锁住多少记录开销影响不大。

第七章、事务
事物的ACID特性:
    A 原子性(atomicity):事务被看作数据库的逻辑工作单元。事务中的操作要么全都成功,要么全都失败。
    C 一致性(consistency):事务将数据库从一种一致性状态变为另一种一致性状态
    I 隔离性(isolation):一个事务的执行不能被其他事务干扰。(任何事物的更新操作直到其提交成功,对其他事务都是不可见的)
    D 永久性(durability):一个事务它对数据库应该是永久的,即使出现故障其对数据库的更新也是永久的。

事务的分类:
    扁平事务
    带保存点的扁平事务
    链事务
    嵌套事务
    分布式事务

事务的实现
    事务的隔离性由锁机制保证,原子性,一致性,永久性由数据库的redo log和undo log来完成。redo log保证原子性和永久性,undo log来保证一致性。
    redo和undo可以视为一种数据库恢复操作,redo恢复提交事务的页修改操作,而undo回滚行记录到某个特定的版本,两者的记录内容不同,redo通常是物理日志,记录的是页的物理修改操作,undo是逻辑日志,根据每行记录进行记录。

    redo:分为两部分:重做日志缓冲,重做日志文件。
        InnoDB通过Force Log at Commit机制保证事务的持久性。即事务提交前,必须先将该事务的日志写入重做日志文件中进行持久化。
        log block:InnoDB存储引擎中,重做日志都是以512字节存储的,这意味着重做日志缓冲和重做日志文件都是以块(block)为单位的。称之为重做日志块(redo log block),由于重做日志块大小和磁盘扇区大小一样都是512字节,因此其写入可以保证原子性,无需doublewrite。

        重做日志和二进制日志比较:
            共同点:两者都记录了对数据库操作的日志。
            区别:
                重做日志在InnoDB存储引擎层产生,二进制日志是mysql上层产生,任何存储引擎产生的数据库操作都会记录在二进制日志中。
                二进制日志是一种逻辑日志,记录的是对应的sql语句,重做日志是物理日志,记录的是对应页的修改。
                二进制日志只在事务提交时写入日志,重组日志在事务进行时不断提交。

    undo:主要用于回滚操作和MVCC。与redo存放在重做日志文件中不同,undo存放在表空间的undo segment。    
        undo是逻辑日志,只是将事务的修改逻辑地取消了,但数据结构和页本身可能大不相同。例如用户执行Insert 10W条记录的语句时,可能系统会为其申请一个大的段,表空间会增大。但是执行rollback操作时,申请的存储空间并不会回收。实际上对于Insert操作,数据库回滚是是执行相应的delete操作,对于update操作,会执行一个相反的update操作。
        undo log会产生redo log,因为undo log也需要持久性的保护。

purge操作:
    举例:
        delete和update操作可能并不会立即执行,例如sql语句:delete from t where a=1。表t上有列a的聚集索引,有列b的辅助索引,上述delete操纵仅仅是将主键列的delete flag=1,记录并没有删除还是存在于B+树中,其次对于辅助索引上的a=1,b=1的记录同样没有操作,甚至没有产生undo log。其真正的删除操作被延迟到purge操作中完成。
    这样的设计是因为:innoDB支持MVCC,所以事务不能在记录提交时立即处理。

附:
mysql为什么使用B+树而不使用B树
B树每个节点都代表一个记录,都存有数据域,这使得B树的每个非叶子节点比B+要大的多,这也使得每次索引的I/O次数增加。
另外相比于B树,B+树有将所有叶子节点串联起来的指针,可以顺序访问,很方便全表遍历等操作,另外对于区间访问也很方便。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章