mysql底层原理及优化实战-1

677 阅读6分钟

1:一个update语句的全流程

假设现在有这么一个sql,update t_user set name='李四' where id=8

  • 首先分析一下,从你写的sql到数据库的存储引擎之间到底发生了什么,首先,你写的sql语句总要连接到数据库吧,你可能配置了一堆数据库地址,端口号,用户名和密码,但是总要有人负责跟mysql去牵线搭桥吧,当然,你可以手动用jdbc那一套连接,不过目前一般都用数据库驱动,把这些操作都封装起来。还有,一个线程发起一次跟mysql的连接,执行完语句了,总不能就立刻销毁这次连接吧?都知道频繁创建销毁很耗费性能,所以这里还有个数据库连接池的概念,负责维护一定量的线程池,你的程序需要执行sql语句,就从连接池拿一个连接,执行完再放回去。mysql拿到你的sql会依次经过sql接口,sql解析,查询优化寻找最优路径,用执行器调用存储引擎,找到相应数据,然后修改,并返回结果。 接下来再看看存储引擎里到底发生了什么,在说明之前先说两个概念,buffer pool和os cache,buffer pool其实就是innodb存储引擎的缓存数据,要知道,mysql的数据最终都是要在磁盘上的,但是随机读写磁盘的速度相信大家都明白,所以mysql在这加了一层缓存,不和磁盘直接交互,而是在buffer pool缓存上修改。一会会详细说这个。os cache,在linux里,写磁盘并不会真的和磁盘马上交互,而是会有一层系统缓存,看下图,我8g的机器,有将近4个g的系统缓存。先知道有这个概念,一会细讲。
  • 1:先到buffer pool中查看是不是有这条数据的信息,如果没有,就从磁盘读出来,再加载到buffer pool里。
  • 2:写undo.log 记录一下修改前的状态,回滚事务需要这个记录,不然谁知道怎么回滚
  • 3:修改buffer pool的数据,把id=8的那条数据name改为李四
  • 4:写redo.log 记录一下改的记录
  • 5:把日志刷到磁盘里(准备提交事务)
  • 6:记录bin.log,并写回磁盘也是记录修改的操作
  • 7:记录bin.log的名称和地址写到redo.log中,并执行commit标记。 差不多就这样的,不过不知道你们发现没有,哎,你好像只修改了内存,磁盘文件没改啊,实际上会有一个后台线程会不停的把内存中修改的数据刷回磁盘,这里暂且不说。不过还有一个问题,假如在第5步,我提交事务的时候,mysql挂了,数据会不会丢。这就引出刷盘策略的问题了。可以用innodb_flush_log_at_trx_commit参数,设置为0,就是说我提交事务也不刷到磁盘,这个时候挂了,数据真的会丢。设置1,只要提交事务,一定会立刻刷到磁盘,所以一般都设置1,这时候哪怕mysql挂了,等重新启动了,把redolog的数据读出来,再去执行一遍就是了。设置为2则是刷到os cache里,可能一秒后才会刷到磁盘。你可能有疑惑,那还扯什么,直接刷磁盘啊,还弄其他参数干啥,要知道,但凡刷磁盘,肯定会影响效率,降低吞吐量,总要多给点选择不是。有的场景确实不需要这么严格。除了redolog,binlog一样you刷盘策略,默认为0,刷到oscache,为1的时候强刷磁盘。其实就算第七步失败了,如果两边的log都已经入盘了,重新加载的时候会判断一下,既然都有值,那就再加上commit标志就是了。也算事务成功。
  • 从宏观层面说完了,再说说微观的。 一般来说,buffer pool默认就128M,如果你机器内存不错,有个三十多g,那可以把buffer pool的值设置大点,比如2g,缓存多了,查询速度自然也会快点。在磁盘文件里,你的每一个表可以当做是一个文件,也叫表空间,里面分为多个数据区组,一个数据区组包含256个数据区,每个数据区包含64个连续数据页,每个数据页16K,包含多个数据行,这个数据行也就是所谓的一条数据。基本上数据页是最小的单位,你去执行一个插入语句,会先从磁盘读出一个数据页,加载到buffer pool里,就是缓存页,在byte buffer里默认分配了很多空的缓存页,也是16k,还有free链表(记录谁是空闲数据页),flush链表(记录哪些数据被修改了),lru链表(记录热点缓存页,比如刚从磁盘加载的数据页放到冷链表尾部,如果1秒后再次访问,就把这个数据刷到热数据头部)。刚开始free链表肯定是满的,毕竟都是空的,flush和lru都是空的,从磁盘加载到内存后,会从free链表找一个数据页的地址,然后把数据放进去,再改值,这时候将这个数据页从free移除,lru加上,flush加上,顺便在一个hash表内记录一下,说明这个数据是有缓存的。直到慢慢内存越来越多,会有线程不定时把flush和lru的脏数据刷到磁盘,这个时候再把这个数据页释放,free加上,flush和lru和hash表都去除这条数据。
  • 那磁盘里到底有什么呢?先从一行数据开始吧,假如insert了一行记录,肯定是要先找磁盘上申请一个数据页加载到内存buffer pool里,具体加载哪一个数据页以后再说,加载完一个数据页后到内存中后,会先写一个undolog,其实就是一个delete语句,反向操作,然后去把这个值写到内存,再写上redo日志,那这个redo日志就直接刷到磁盘了吗?要知道一个log也就几字节甚至几十字节,这样也太浪费了,所以会有一个redo log block的概念,分配在redo log buffer里(16M),实际上类似缓存页,也是很多redolog的集合,正常情况下有4种情况会刷到磁盘。提交事务,buffer到了一半(就是8M),每隔一秒,关闭mysql。而且redolog是有状态的的,也不用担心万一数据页被刷到磁盘上修改了数据,但是redolog事务回滚的问题。redolog记录的东西大致就是表空间+数据页+偏移量+修改的字段+修改值。好了,那如果数据行所在的缓存页被某个定时线程刷到磁盘的数据页了,那表里面大致长啥样呢?一个表文件分为多组数据区组,一个数据区组有256个数据区,一个数据区有64个数据页,每个数据页有16k,这个数据页里面放的就是一堆数据行,也就是在mysql表里看的那些东西,还有一些索引目录,前后数据页地址,数据页的最大最小值等等。