1. MySQL构成: 上层的MySQL Server和下层的存储引擎构成。当一条SQL语句过来的时候,是首先由MySQL Server的连接器负责建立客户端和服务器的连接,然后经过权限判断此查询是否有权限;然后经过词法分析语法分析判断语句是否有语法错误;然后经过优化器来优化查询语句使用适当的索引;最后调用存储引擎的接口执行相应的操作。
2. 存储引擎:MyISAM和InnoDB,InnoDB主要处理事务比较多的数据存储。
3. InnoDB存储引擎的实现:
(1)底层数据存储方式:采用页的方式存储数据记录,索引和数据分开存储。
(2)为了使查询速度快,需要建立索引去查询,采用何种方式建索引?采用B+树的方式存储,可以减少树的高度,减少磁盘IO次数。
(3)聚簇索引和辅助索引?聚簇索引就是每个表通过主键来建立的,非叶节点上都是主键索引,叶节点上是记录。每个表只有一个聚簇索引。辅助索引是在其他字段上建立的索引,可以有多个,非叶节点上存储索引,而叶节点存储主键值。辅助索引可以通过主键值来找到记录。
(4)事务特性?ACID,原子性、一致性、隔离性、持久性。
(5)原子性如何保证?通过undo log,mvcc
数据在处理之前,如果数据在处理中变化了,出现了异常,需要我们能把数据恢复到处理之前的样子,所以在事务开始前,记录下数据记录的原始值。然后开始事务,代表事务开始处理了,如果是查询的操作,按理来说如果查询的那条记录正在被更新,那这条记录是被加写锁的,无论再读还是写都会阻塞,这显然会降低效率,所以InnoDB对记录增加了版本的概念,当记录在更新的时候,我们可以读这条记录之前的版本。这就是MVCC引入的原因。数据记录在不断被更新的过程中,都会将改动之前的记录添加到undo log。就是这个记录的之前的版本,这个版本有俩作用,一个上我门上面提到的可以提高并发的读写的效率;另一个则是可以用来回滚数据。
(6)隔离性如何保证?通过加锁,innodb的锁有表锁、行锁、间隙锁。
表锁是对表中每个记录的主键索引加锁;如果是主键索引或是唯一索引,在主键索引上加行锁。如果是非唯一索引,需要在主键上加索引,以及查询的范围上加间隙锁。
(7)事务的隔离级别?RU, RC,RR,Serializable
RU是写的时候加写锁,如果有两个事务,一个事务修改未提交,另一个事务则会被阻塞。
RC和RR有了MVCC,可以支持在一个事务写的时候,另一个事务的读不受影响,当时这个读是指快照读(select...),而当前读(select for update)还是会被阻塞。其他的更新操作也是当前读,也会被阻塞。
Serializable是串行化的方式,读写都会阻塞。
RR通过加间隙锁解决了幻读的问题。
(8)持久性如何保证?通过redo log,WAL。保证在写入数据前先把写入数据的日志持久化。日志先行的工作方式,减少磁盘IO,文件是顺序IO,而数据是随机IO。
(9)binlog和redo log的区别?binlog是在上层事务提交以后产生的,逻辑上的日志,每种存储引擎都有binlog。redo log是记录数据页的变更,物理上的日志。
一个SQL更新操作的完整过程:
1. 事务开始后,数据进行更新操作,加载缓存数据,如果有的话就从缓存中获取,没有的话需要从磁盘上拉取,放到buffer pool中;
2. 数据的旧值写入到undo log,方便回滚。
3. 更新数据页里的数据记录,此时数据页面变为脏页,数据页在buffer pool里,buffer pool 中的页可以区分为freelist,lrulist, flushlist,当从磁盘中加载一个数据的时候,需要从freelist中申请一个页放在lrulist中,当页被更新后,也会被加到flushlist。lrulist的长度有限,新的访问的页会放在前面,后面的页会慢慢被淘汰,如果淘汰的页面正好是脏页的话需要把脏页的内容同步到磁盘上。
4. 然后把数据的更新记录在redo log buffer上,redo log bufer也不能每次都直接flush到磁盘,那样效率太低,在内存中有个redo log buffer,会存储redo log。可通过三种方式设置redo log buffer的内容同步到磁盘的redo log的方式,一种是每隔一秒一次;第二种是每次事务在commit时候,会把redo log buffer的内容更新到系统的缓存,系统缓存会定时把内容sync到磁盘;第三种是每次提交时候都把他同步到磁盘。默认是第二种。buffer pool中的脏页会在一定是时机同步到磁盘:
5. 内存数据页的更新内容记录在 redolog buffer中,此时,buffer中的这条语句状态为prepare。然后告知执行器执行完成了,随时可以提交事务。
6. server层提交事务时,会先将这个操作的日志写入binlog buffer中,
7. 再调用引擎的事务提交接口,引擎会将刚写入的redolog记录状态修改为commit。更新完成。
8. 数据刷新到磁盘。定期或是bufferpool满了。或是服务down机。
总结:更新数据的基本过程是先加载要更新的数据到磁盘中,然后写undo log,更新内存数据,写redo log,redo log落盘。数据会定期被更新到磁盘。
那未提交的数据也可能被同步到磁盘?参考https://blog.csdn.net/Singularinty/article/details/80747290,看不同的策略,steal + no force组合允许,所以需要redo log和undolog来恢复数据。如果是nosteal + force策略则不会出现未commit的数据出现在磁盘上,就不需要redo log和undo log 来做数据恢复。
个人理解:用redo log把需要更新的数据以记事本的方式记录,这个记录是顺序的,我们可以很快记录我要更新第N页第N个记录为XXX。而如果更新数据的话我们需要先找到这个第N页的第N个记录的地址,会比较慢,所以InnoDB采用这种方式可以把需要更新的数据先记录下来。
redo log开始随着事务中的数据的操作,写在redo log buff中,每次操作都会记录一个,然后在事务提交时候,先把他设置为prepare返回给执行器,执行器记录完binlog以后,执行最终的commit,redolog把 buffer中的内容(也是做后的结果)更新到os cache。然后os cache以他的频率通常是1s,fsync到磁盘。那么可以说,只要事务提交的话,就一定会进入binlog,redo log。而数据的更新则是一方面定时更新到磁盘,另一方面在不得已需要处理脏页的时候更新到磁盘。
force会在刷新完redo log以后也把数据的更新刷新到磁盘,就不有事务提交了但是数据还没有持久化的问题。
no-steal会保证没有commit的数据不会刷新到磁盘,就不会有数据已经在磁盘上了,但是还没有提交事务的情况。
就是数据更新到磁盘的线程和redo log刷新到磁盘的线程是两个独立的线程,两个线程执行先后不一样会造成数据和redo log的不一致。数据到了但是redo log没到或者redo log到了但是数据没到。这两种问题,如果是数据到了redo log没到,就需要通过undo log回滚。而redo log到了,数据没到,就需要用redo log重做。
redo log不能一直追加,要不然恢复的时候数据量太大效率很低,redolog有大小限制,可以重复利用。循环写入,假设编号依此是,1、2、3、4,从1到4顺序写入,在4写入以后在从1开始写,但是1的内容需要是里面数据的修改已经同步到磁盘的了。所以redo log file上有两个标志位,一个是checkpoint,一个是write_pos,checkpoint以前的代表数据已经同步到磁盘,checkpoint后面是还没有同步到磁盘。
参考文档:
https://www.jianshu.com/p/dbbd8d601f8c
https://blog.51cto.com/u_15127629/2736150
https://blog.csdn.net/Singularinty/article/details/80747290
https://www.zhihu.com/question/267595935
https://xie.infoq.cn/article/3cae3ed498b41d3da8a080c23