1. 1. Mysql 面试题
    1. 1.1. 1. 能说下myisam 和 innodb的区别吗?
    2. 1.2. 2. 说下mysql的索引有哪些吧,聚簇和非聚簇索引又是什么?
    3. 1.3. 3. 那你知道什么是覆盖索引和回表吗?
    4. 1.4. 4. 锁的类型有哪些呢
      1. 1.4.0.1. InnoDB锁机制之Gap Lock、Next-Key Lock、Record Lock解析
  2. 1.5. 5. 你能说下事务的基本特性和隔离级别吗?
  3. 1.6. 6. 那ACID靠什么保证的呢?
  4. 1.7. 7. 什么是MVCC?
  5. 1.8. 8. 为什么说间隙锁是可重复读隔离级别下防止幻读的主要云因?
  6. 1.9. 9. 你们数据量级多大?分库分表怎么做的?
  7. 1.10. 10. 那分表后的ID怎么保证唯一性的呢?
  8. 1.11. 11. 分表后非sharding_key的查询怎么处理呢?
  9. 1.12. 12. 说说mysql主从同步怎么做的吧?
  10. 1.13. 13. 那主从的延迟怎么解决呢?
  11. 1.14. 14. InnoDB 引擎的四大特性是什么?
    1. 1.14.0.1. 插入缓冲(Insert buffer)
    2. 1.14.0.2. 二次写 (Double write)
    3. 1.14.0.3. 自适应哈希索引 (Adaptive Hash Index)
    4. 1.14.0.4. 缓存池
  • 1.15. 15. Mysql如何进行后期优化?
  • 1.16. 16. Mysql一条SQL语句的执行过程
    1. 1.16.0.1. 查询语句
    2. 1.16.0.2. 更新语句
  • 1.17. 17. Mysql Join算法的原理
    1. 1.17.0.1. 一、Simple Nested-Loop Join(简单的嵌套循环连接)
    2. 1.17.0.2. 二、Index Nested-Loop Join(索引嵌套循环连接)
    3. 1.17.0.3. 三 Block Nested-Loop Join(缓存块嵌套循环连接)
    4. 1.17.0.4. 总结
  • 1.17.1. 18. 堆组织表,索引组织表和索引聚簇表
    1. 1.17.1.1. 堆表 :
    2. 1.17.1.2. 索引组织表:
    3. 1.17.1.3. 索引聚簇表:
  • 1.17.2. 19. LRU List、Free List和Flush List
  • 1.17.3. 20. Mysql的 CheakPoint技术
    1. 1.17.3.1. Sharp Checkpoint
    2. 1.17.3.2. Fuzzy Checkpoint
  • 1.18. 21 Mysql的Redo Undo BinLog
    1. 1.18.0.1. 重做日志Redo log
    2. 1.18.0.2. 回滚日志 Undo log
    3. 1.18.0.3. 二进制日志(binlog):
  • MySQL面试题整理

    [TOC]

    Mysql 面试题

    1. 能说下myisam 和 innodb的区别吗?

    myisam引擎是5.1版本之前的默认引擎,支持全文检索、压缩、空间函数等,但是不支持事务和行级锁,所以一般用于有大量查询少量插入的场景来使用,而且myisam不支持外键,并且索引和数据是分开存储的。

    innodb是基于聚簇索引建立的,和myisam相反它支持事务、外键,并且通过MVCC来支持高并发,索引和数据存储在一起。

    2. 说下mysql的索引有哪些吧,聚簇和非聚簇索引又是什么?

    索引按照数据结构来说主要包含B+树和Hash索引。

    假设我们有张表,结构如下:

    1
    2
    3
    4
    5
    6
    create table user(
    id int(11) not null,
    age int(11) not null,
    primary key(id),
    key(age)
    );

    B+树是左小右大的顺序存储结构,节点只包含id索引列,而叶子节点包含索引列和数据,这种数据和索引在一起存储的索引方式叫做聚簇索引,一张表只能有一个聚簇索引。假设没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有的话则会隐式定义一个主键作为聚簇索引。

    img

    这是主键聚簇索引存储的结构,那么非聚簇索引的结构是什么样子呢?非聚簇索引(二级索引)保存的是主键id值,这一点和myisam保存的是数据地址是不同的。

    img

    最终,我们一张图看看InnoDB和Myisam聚簇和非聚簇索引的区别

    img

    3. 那你知道什么是覆盖索引和回表吗?

    覆盖索引指的是在一次查询中,如果一个索引包含或者说覆盖所有需要查询的字段的值,我们就称之为覆盖索引,而不再需要回表查询。

    而要确定一个查询是否是覆盖索引,我们只需要explain sql语句看Extra的结果是否是“Using index”即可。

    以上面的user表来举例,我们再增加一个name字段,然后做一些查询试试。

    1
    2
    explain select * from user where age=1; //查询的name无法从索引数据获取
    explain select id,age from user where age=1; //可以直接从索引获取

    4. 锁的类型有哪些呢

    mysql锁分为共享锁排他锁,也叫做读锁和写锁。

    读锁是共享的,可以通过lock in share mode实现,这时候只能读不能写。

    写锁是排他的,它会阻塞其他的写锁和读锁。从颗粒度来区分,可以分为表锁行锁两种。

    表锁会锁定整张表并且阻塞其他用户对该表的所有读写操作,比如alter修改表结构的时候会锁表。

    行锁又可以分为乐观锁悲观锁,悲观锁可以通过for update实现,乐观锁则通过版本号实现。

    InnoDB锁机制之Gap Lock、Next-Key Lock、Record Lock解析

    MySQL InnoDB支持三种行锁定方式:

    行锁(Record Lock):锁直接加在索引记录上面,锁住的是key。

    间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变。间隙锁是针对事务隔离级别为可重复读或以上级别而已的。

    Next-Key Lock :行锁和间隙锁组合起来就叫Next-Key Lock。

    默认情况下,InnoDB工作在可重复读隔离级别下,并且会以Next-Key Lock的方式对数据行进行加锁,这样可以有效防止幻读的发生。Next-Key Lock是行锁和间隙锁的组合,这个锁机制其实就是前面两个锁相结合的机制,既锁住记录本身还锁住索引之间的间隙。加锁原则:

    原则1:加锁的基本单位是next-key lock,前开后闭

    原则2:查找过程中访问到的对象才会加锁

    优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化成行锁

    优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁

    唯一索引上的范围查询会访问到不满足条件的第一个值为止

    5. 你能说下事务的基本特性和隔离级别吗?

    事务基本特性ACID分别是:

    原子性指的是一个事务中的操作要么全部成功,要么全部失败。

    一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设中间sql执行过程中系统崩溃A也不会损失100块,因为事务没有提交,修改也就不会保存到数据库。

    隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。

    持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。

    而隔离性有4个隔离级别,分别是:

    read uncommit 读未提交,可能会读到其他事务未提交的数据,也叫做脏读。

    用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age=20,这就是脏读。

    img

    read commit 读已提交,两次读取结果不一致,叫做不可重复读。

    不可重复读解决了脏读的问题,他只会读取已经提交的事务。

    用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。

    img

    repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。

    serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。

    6. 那ACID靠什么保证的呢?

    A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql

    C一致性一般由代码层面来保证

    I隔离性由MVCC来保证

    D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复

    7. 什么是MVCC?

    MVCC多版本并发控制机制

    这个隔离性就是靠MVCC(Multi-Version Concurrency Control)机制来保证的,对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥,而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。

    Mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。

    undo日志版本链与read view机制详解

    undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链(见下图,需参考视频里的例子理解)

    img

    可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在事务结束之前都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成),这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。

    版本链比对规则:

    \1. 如果 row 的 trx_id 落在绿色部分( trx_id<min_id ),表示这个版本是已提交的事务生成的,这个数据是可见的;

    \2. 如果 row 的 trx_id 落在红色部分( trx_id>max_id ),表示这个版本是由将来启动的事务生成的,是不可见的(若 row 的 trx_id 就是当前自己的事务是可见的);

    \3. 如果 row 的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括两种情况

    a. 若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id 就是当前自己的事务是可见的);

    b. 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

    对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的(deleted_flag)标记位写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对应的记录如果delete_flag标记位为true,意味着记录已被删除,则不返回数据。

    注意:begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,事务才真正启动,才会向mysql申请事务id,mysql内部是严格按照事务的启动顺序来分配事务id的。

    总结:

    MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。

    8. 为什么说间隙锁是可重复读隔离级别下防止幻读的主要云因?

    解决幻读的方式很简单,就是需要当事务进行当前读的时候,保证其他事务不可以在满足当前读条件的范围内进行数据操作。

    例如:

    id(主键) c(普通索引) d(无索引)
    5 5 5
    10 10 10
    15 15 15
    20 20 20
    25 25 25

    以上数据为了解决幻读问题,更新的时候不只是对上述的五条数据增加行锁,还对于中间的取值范围增加了6个间隙锁,(-∞,5](5,10](10,15](15,20](20,25](25,+supernum] (其中supernum是数据库维护的最大的值。为了保证间隙锁都是左开右闭原则。)

    9. 你们数据量级多大?分库分表怎么做的?

    首先分库分表分为垂直和水平两个方式,一般来说我们拆分的顺序是先垂直后水平。

    垂直分库

    基于现在微服务拆分来说,都是已经做到了垂直分库了

    img

    垂直分表

    如果表字段比较多,将不常用的、数据较大的等等做拆分

    img

    水平分表

    首先根据业务场景来决定使用什么字段作为分表字段(sharding_key),比如我们现在日订单1000万,我们大部分的场景来源于C端,我们可以用user_id作为sharding_key,数据查询支持到最近3个月的订单,超过3个月的做归档处理,那么3个月的数据量就是9亿,可以分1024张表,那么每张表的数据大概就在100万左右。

    比如用户id为100,那我们都经过hash(100),然后对1024取模,就可以落到对应的表上了。

    10. 那分表后的ID怎么保证唯一性的呢?

    因为我们主键默认都是自增的,那么分表之后的主键在不同表就肯定会有冲突了。有几个办法考虑:

    1. 设定步长,比如1-1024张表我们分别设定1-1024的基础步长,这样主键落到不同的表就不会冲突了。
    2. 分布式ID,自己实现一套分布式ID生成算法或者使用开源的比如雪花算法这种
    3. 分表后不使用主键作为查询依据,而是每张表单独新增一个字段作为唯一主键使用,比如订单表订单号是唯一的,不管最终落在哪张表都基于订单号作为查询依据,更新也一样。

    11. 分表后非sharding_key的查询怎么处理呢?

    1. 可以做一个mapping表,比如这时候商家要查询订单列表怎么办呢?不带user_id查询的话你总不能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查询到用户列表,再通过user_id去查询。
    2. 打宽表,一般而言,商户端对数据实时性要求并不是很高,比如查询订单列表,可以把订单表同步到离线(实时)数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
    3. 数据量不是很大的话,比如后台的一些查询之类的,也可以通过多线程扫表,然后再聚合结果的方式来做。或者异步的形式也是可以的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    List<Callable<List<User>>> taskList = Lists.newArrayList();
    for (int shardingIndex = 0; shardingIndex < 1024; shardingIndex++) {
    taskList.add(() -> (userMapper.getProcessingAccountList(shardingIndex)));
    }
    List<ThirdAccountInfo> list = null;
    try {
    list = taskExecutor.executeTask(taskList);
    } catch (Exception e) {
    //do something
    }

    public class TaskExecutor {
    public <T> List<T> executeTask(Collection<? extends Callable<T>> tasks) throws Exception {
    List<T> result = Lists.newArrayList();
    List<Future<T>> futures = ExecutorUtil.invokeAll(tasks);
    for (Future<T> future : futures) {
    result.add(future.get());
    }
    return result;
    }
    }

    12. 说说mysql主从同步怎么做的吧?

    首先先了解mysql主从同步的原理

    1. master提交完事务后,写入binlog
    2. slave连接到master,获取binlog
    3. master创建dump线程,推送binglog到slave
    4. slave启动一个IO线程读取同步过来的master的binlog,记录到relay log中继日志中
    5. slave再开启一个sql线程读取relay log事件并在slave执行,完成同步
    6. slave记录自己的binglog

    img

    由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。

    全同步复制

    主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。

    半同步复制

    和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。

    13. 那主从的延迟怎么解决呢?

    这个问题貌似真的是个无解的问题,只能是说自己来判断了,需要走主库的强制走主库查询。

    在学习过程中,我喜欢找一些电子书,视频结合起来学习,有不明白的都可以到圈子里面讨论交流,主要对Java视频,Java学习路线,Java.面试题,电子书分享,程序员的聚集地。

    14. InnoDB 引擎的四大特性是什么?

    插入缓冲(Insert buffer)

    Insert Buffer 用于非聚集索引的插入和更新操作。先判断插入的非聚集索引是否在缓存池中,如果在则直接插入,否则插入到 Insert Buffer 对象里。再以一定的频率进行 Insert Buffer 和辅助索引叶子节点的 merge 操作,将多次插入合并到一个操作中,提高对非聚集索引的插入性能。

    二次写 (Double write)

    Double Write由两部分组成,一部分是内存中的double write buffer,大小为2MB,另一部分是物理磁盘上共享表空间连续的128个页,大小也为 2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是通过 memcpy 函数将脏页先复制到内存中的该区域,之后通过doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免操作系统缓冲写带来的问题。

    自适应哈希索引 (Adaptive Hash Index)

    InnoDB会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。索引通过缓存池的 B+ 树页构造而来,因此建立速度很快,InnoDB存储引擎会监控对表上各个索引页的查询,如果观察到建立哈希索引可以带来速度上的提升,则建立哈希索引,所以叫做自适应哈希索引。

    缓存池

    为了提高数据库的性能,引入缓存池的概念,通过参数 innodb_buffer_pool_size 可以设置缓存池的大小,参数 innodb_buffer_pool_instances 可以设置缓存池的实例个数。缓存池主要用于存储以下内容:

    缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲 (insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息 (lock info)和数据字典信息 (data dictionary)。

    15. Mysql如何进行后期优化?

    1. 选择合适的存储引擎
    2. 尽量保证从内存中读取数据,将数据保存在内存中
    3. 减少磁盘写入操作
    4. 充分使用索引,在Join表的时候使用相当类型的例,并将其索引
    5. 分析查询日志和慢查询日志

    16. Mysql一条SQL语句的执行过程

    查询语句

    1. 先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。
    2. 通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id=‘1’。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。
    3. 优化器进行确定执行方案,优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。
    4. 进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。

    SQL 等执行过程分为两类,一类对于查询等过程如下:权限校验—》查询缓存—》分析器—》优化器—》权限校验—》执行器—》引擎

    更新语句

    1. 先查询到需要修改的这一条数据,如果有缓存,也是会用到缓存。
    2. 然后拿到查询的语句,进行值的修改,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
    3. 执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。
      更新完成。

    对于更新等语句执行流程如下:分析器—-》权限校验—-》执行器—》引擎—redo log prepare—》binlog—》redo log commit

    17. Mysql Join算法的原理

    一、Simple Nested-Loop Join(简单的嵌套循环连接)

    简单来说嵌套循环连接算法就是一个双层for 循环 ,通过循环外层表的行数据,逐个与内层表的所有行数据进行比较来获取结果,当执行

    1
    2
    3
    select * from user tb1 
    left join level tb2
    on tb1.id=tb2.user_id

    时,我们会按类似下面代码的思路进行数据匹配:

    img

    整个匹配过程会如下图:

    img

    特点:

    Nested-Loop Join 简单粗暴容易理解,就是通过双层循环比较数据来获得结果,但是这种算法显然太过于粗鲁,如果每个表有1万条数据,那么对数据比较的次数=1万 * 1万 =1亿次,很显然这种查询效率会非常慢。

    当然mysql 肯定不会这么粗暴的去进行表的连接,所以就出现了后面的两种对Nested-Loop Join 优化算法,在执行join 查询时mysql 会根据情况选择 后面的两种优join优化算法的一种进行join查询。

    二、Index Nested-Loop Join(索引嵌套循环连接)

    Index Nested-Loop Join其优化的思路:
    主要是为了减少内层表数据的匹配次数, 简单来说Index Nested-Loop Join 就是通过外层表匹配条件 直接与内层表索引进行匹配,避免和内层表的每条记录去进行比较, 这样极大的减少了对内层表的匹配次数,从原来的匹配次数=外层表行数 * 内层表行数,变成了 外层表的行数 * 内层表索引的高度,极大的提升了 join的性能。

    案例:

    如SQL:

    1
    2
    3
    select * from user tb1 
    left join level tb2
    on tb1.id=tb2.user_id

    当level 表的 user_id 为索引的时候执行过程会如下图:

    img

    注意:使用Index Nested-Loop Join 算法的前提是匹配的字段必须建立了索引。

    三 Block Nested-Loop Join(缓存块嵌套循环连接)

    Block Nested-Loop Join 其优化思路是减少内层表的扫表次数,通过简单的嵌套循环查询的图,我们可以看到,左表的每一条记录都会对右表进行一次扫表,扫表的过程其实也就是从内存读取数据的过程,那么这个过程其实是比较消耗性能的。

    img

    所以缓存块嵌套循环连接算法意在通过一次性缓存外层表的多条数据,以此来减少内层表的扫表次数,从而达到提升性能的目的。如果无法使用Index Nested-Loop Join的时候,数据库是默认使用的是Block Nested-Loop Join算法的

    当level 表的 user_id 不为索引的时候,默认会使用Block Nested-Loop Join算法,匹配的过程类似下图。

    img

    注意:

    1、使用Block Nested-Loop Join 算法需要开启优化器管理配置的optimizer_switch的设置block_nested_loop为on 默认为开启,如果关闭则使用Simple Nested-Loop Join 算法;

    通过指令:Show variables like ‘optimizer_switc%’; 查看配置

    img

    2、设置join buffer 的大小

    通过join_buffer_size参数可设置join buffer的大小

    指令:Show variables like ‘join_buffer_size%’;

    img

    总结

    不论是Index Nested-Loop Join 还是 Block Nested-Loop Join 都是在Simple Nested-Loop Join的算法的基础上进行优化,这里 Index Nested-Loop Join 和Nested-Loop Join 算法是分别对Join过程中循环匹配次数和IO 次数两个角度进行优化。

    Index Nested-Loop Join 是通过索引的机制减少内层表的循环匹配次数达到优化效果,而Block Nested-Loop Join 是通过一次缓存多条数据批量匹配的方式来减少内层表的扫表IO次数,通过 理解join 的算法原理我们可以得出以下表连接查询的优化思路。

    1、永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量)

    2、为匹配的条件增加索引(减少内层表的循环匹配次数)

    3、增大join buffer size的大小(一次缓存的数据越多,那么内层包的扫表次数就越少)

    4、减少不必要的字段查询(字段越少,join buffer 所缓存的数据就越多)

    18. 堆组织表,索引组织表和索引聚簇表

    堆表 :

    • 就是无序数据的集合,索引就是将数据变得有序,在索引中键值有序,数据还是无序的
    • 数据存放在数据里面,索引存放在索引里
    • 表中,主键索引和普通索引一样的,叶子节点存放的是指向表中数据的指针(可以是一个页编号加偏移量),指向物理地址,没有回表的说法
    • 表中,主键和普通索引基本上没区别,和非空的唯一索引没区别
    • mysql 的 myisam 引擎,oracle pg 都支持的是

    索引组织表

    • innodb 引擎支持的就是索引组织表
    • 对于主键的索引,页子节点存放了一整行所有数据,其他索引称为辅助索引(二级索引),它的页子节点只是存放了键值和主键值
    • 主键包含了一张表的所有数据,因为主键索引的页子节点中保存了每一行的完整记录,包括所有列。如果没有主键,MySQL会自动帮你加一个主键,但是对用户不可见
    • innodb中数据存放在聚集索引中,换言之,按照主键的方式来组织数据的
    • 其他索引(唯一索引,普通索引)的页子节点存放该索引列的键值和主键值
    • 不管是什么索引非页子节点存放的存放的就是键值和指针,不存数据,这个指针在innodb中是6个bit,键值就看数据大小了

    索引聚簇表

    聚簇是指:如果一组表有一些共同的列,则将这样一组表存储在相同的数据库块中;聚簇还表示把相关的数据存储在同一个块上。

    利用聚簇,一个块可能包含多个表 的数据。概念上就是如果两个或多个表经常做链接操作,那么可以把需要的数据预先存储在一起。

    聚簇还可以用于单个表,可以按某个列将数据分组存储。

    19. LRU List、Free List和Flush List

    数据库中的缓冲池是通过LRU(Latest Recent Used,最近最少使用)算法来进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。

    InnoDB存储引擎中,缓冲池中页的大小默认为16KB,同样使用LRU算法对缓冲池进行管理。稍有不同的是InnoDB存储引擎对传统的LRU算法做了一些优化。在InnoDB的存储引擎中,LRU列表中还加入了midpoint位置。新读取到的页,虽然是最新访问的页,但并不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置。这个算法在InnoDB存储引擎下称为midpoint insertion strategy。在默认配置下,该位置在LRU列表长度的5/8处。midpoint位置可由参数innodb_old_blocks_pct控制。

    为什么不采用朴素的LRU算法,直接将读取的页放入到LRU列表的首部呢?
    这是因为若直接将读取到的页放入到LRU的首部,那么某些SQL操作可能会使缓冲池中的页被刷新出,从而影响缓冲池的效率。常见的这类操作为索引或数据的扫描操作。这类操作需要访问表中的许多页,甚至是全部的页,而这些页通常来说又仅在这次查询操作中需要,并不是活跃的热点数据。如果页被放入LRU列表的首部,那么非常可能将所需要的热点数据页从LRU列表中移除,而在下一次需要读取该页时,InnoDB存储引擎需要再次访问磁盘。

    LRU列表用来管理已经读取的页,但当数据库刚启动时,LRU列表是空的,即没有任何的页。这时页都存放在Free列表中。当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页。当页从LRU列表的old部分加入到new部分时,称此时发生的操作为page made young,而因为innodb_old_blocks_time的设置而导致页没有从old部分移动到new部分的操作称为page not made young。可以通过命令SHOW ENGINE INNODB STATUS来观察LRU列表及Free列表的使用情况和运行状态。

    20. Mysql的 CheakPoint技术

    缓冲池的设计目的为了协调CPU速度与磁盘速度的鸿沟。因此Checkpoint(检查点)技术的目的是解决以下几个问题:

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

    对于InnoDB存储引擎而言,其是通过LSN(Log Sequence Number)来标记版本的。而LSN是8字节的数字,其单位是字节。每个页有LSN,重做日志中也有LSN,Checkpoint也有LSN。

    有两种Checkpoint,分别为:

    • Sharp Checkpoint

    • Fuzzy Checkpoint

    Sharp Checkpoint

    Sharp Checkpoint发生在数据库关闭时将所有的脏页都刷新回磁盘,这是默认的工作方式,即参数innodb_fast_shutdown=1。

    但是若数据库在运行时也使用Sharp Checkpoint,那么数据库的可用性就会受到很大的影响。故在InnoDB存储引擎内部使用Fuzzy Checkpoint进行页的刷新,即只刷新一部分脏页,而不是刷新所有的脏页回磁盘。

    Fuzzy Checkpoint

    在InnoDB存储引擎中可能发生如下几种情况的Fuzzy Checkpoint:

    • Master Thread Checkpoint
      Master Thread中发生的Checkpoint,差不多以每秒或每十秒的速度从缓冲池的脏页列表中刷新一定比例的页回磁盘。这个过程是异步的,即此时InnoDB存储引擎可以进行其他的操作,用户查询线程不会阻塞。

    • FLUSH_LRU_LIST Checkpoint
      FLUSH_LRU_LIST Checkpoint是因为InnoDB存储引擎需要保证LRU列表中需要有差不多100个空闲页可供使用。

    • Async/Sync Flush Checkpoint
      Async/Sync Flush Checkpoint指的是重做日志文件不可用的情况,这时需要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选取的。若将已经写入到重做日志的LSN记为redo_lsn,将已经刷新回磁盘最新页的LSN记为checkpoint_lsn。

    • Dirty Page too much Checkpoint
      最后一种Checkpoint的情况是Dirty Page too much,即脏页的数量太多,导致InnoDB存储引擎强制进行Checkpoint。其目的总的来说还是为了保证缓冲池中有足够可用的页。其可由参数innodb_max_dirty_pages_pct控制

    21 Mysql的Redo Undo BinLog

    MySQL中有六种日志文件,
    分别是:重做日志(redo log)、回滚日志(undo log)、二进制日志(binlog)、错误日志(errorlog)、慢查询日志(slow query log)、一般查询日志(general log),中继日志(relay log)。

    重做日志Redo log

    作用:

      1. 确保事务的持久性。
      2. 防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

    内容:
    物理格式的日志,记录的是物理数据页面的修改的信息,其redo log是顺序写入redo log file的物理文件中去的。

    什么时候产生:
    事务开始之后就产生redo log,redo log的落盘并不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入redo log文件中。

    什么时候释放:
    当对应事务的脏页写入到磁盘之后,redo log的使命也就完成了,重做日志占用的空间就可以重用(被覆盖)。

    回滚日志 Undo log

    作用:
      保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读

    内容:
      逻辑格式的日志,在执行undo的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log的。

    什么时候产生:
      事务开始之前,将当前是的版本生成undo log,undo 也会产生 redo 来保证undo log的可靠性

    什么时候释放:
      当事务提交之后,undo log并不能立马被删除,
      而是放入待清理的链表,由purge线程判断是否由其他事务在使用undo段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间。

    二进制日志(binlog):

    作用:
      1,用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。
      2,用于数据库的基于时间点的还原。
    内容:
      逻辑格式的日志,可以简单认为就是执行过的事务中的sql语句。
      但又不完全是sql语句这么简单,而是包括了执行的sql语句(增删改)反向的信息,
      也就意味着delete对应着delete本身和其反向的insert;update对应着update执行前后的版本的信息;insert对应着delete和insert本身的信息。
      在使用mysqlbinlog解析binlog之后一些都会真相大白。
      因此可以基于binlog做到类似于oracle的闪回功能,其实都是依赖于binlog中的日志记录。

    什么时候产生:
      事务提交的时候,一次性将事务中的sql语句(一个事物可能对应多个sql语句)按照一定的格式记录到binlog中。
      这里与redo log很明显的差异就是redo log并不一定是在事务提交的时候刷新到磁盘,redo log是在事务开始之后就开始逐步写入磁盘。
      因此对于事务的提交,即便是较大的事务,提交(commit)都是很快的,但是在开启了bin_log的情况下,对于较大事务的提交,可能会变得比较慢一些。
      这是因为binlog是在事务提交的时候一次性写入的造成的,这些可以通过测试验证。

    什么时候释放:
      binlog的默认是保持时间由参数expire_logs_days配置,也就是说对于非活动的日志文件,在生成时间超过expire_logs_days配置的天数之后,会被自动删除。