一直在从事postgres内核修改的工作,对postgres的代码还算比较熟悉,在工作讨论中,有意无意的大家都会提起,MySQL在这方面是怎么做的,最近读了以下网站:
关于MySQL源码解读 的文章,感觉作者对MySQL也是相当熟悉,文章写的也很好,MySQL和PostgresQL在很多功能设计上有许多相似之处,又有些许不同,简单的总结以下,以下的谈论都是针对MySQL的InnoDB引擎的。
1. 数据文件存储
1.1 Postgres 是一张表对应一个文件(或多个文件),一个索引也是对应一个文件(或多个文件),Postgres在创建表时,会默认创建一个物理文件,文件大小默认为1G,随着表中数据的增长,会接着产生第二个,第三个默认大小为1G的数据文件,索引也是类似。
1.2 MySQL InnoDB其实是所有的数据库中所有表都存在一个文件中(ibdata1 by default),后来版本增加了参数 innodb_file_per_table,默认为disable,也可以每张表一个物理文件如mytable.ibd,这里和Postgres的区别是:MySQL会把表和表上的所有索引都存入该文件,而Postgres不会,表对应一个(多个)物理文件,表上的每个索引对应一个(多个)物理文件。
1.3 那么MySQL为什么要增加这个特性呢,以前为什么又把所有的表存入一个文件呢?
从上述讨论中,未找到明确答案。
2. 日志
/2012/04/06/innodb-log1.html
/2012/04/06/innodb-log2.html
/2012/04/06/innodb-log3.html
以上三篇文章把MySQL的日志记录方法说的非常清楚,循序渐进。
2.1 MySQL既存在Redo日志又存在Undo日志,日志记录的格式既有物理日志又有逻辑日志。而Postgres只存在Redo日志,并且日志记录的格式只有物理日志。
2.2 对于日志的恢复策略是:不管事务是否提交,全部重做而非只做提交的事务。这一点Postgres和InnoDB相同。
2.3 为了解决Page部分写到磁盘的问题(如page的头面刷盘,而尾部为刷盘的情况下掉电),MySQL提供了Double Write的机制:
Double Write的思路很简单:
A. 在覆盖磁盘上的数据前,先将Page的内容写入到磁盘上的其他地方(InnoDB存储引擎中的doublewrite
buffer,这里的buffer不是内存空间,是持久存储上的空间).
B. 然后再将Page的内容覆盖到磁盘上原来的数据。
Postgres也有类似的机制,通过full_page_write来控制,不同之处在于Postgres先将页面的内容写入Redo日志,而MySQL是写入double write buffer.
2.4 MySQL的数据页有checksum,而Postgres的数据页并没有checksum,只有索引页有。对于日志在内存中缓存结构,两者都采用了环形缓冲的形式。
3. Checkpoint
/2012/04/23/mysql-innodb-checkpoint.html
在这一点上Postgres和MySQL差别很大。
3.1 Postgres的Checkpoint会刷盘所有的脏页面,而MySQL的checkpoint并不会刷盘所有的脏页面,只是部分脏页面,很多情况下checkpoint时,并不写脏页到存储。只是将所有脏页的 最小的LSN记做checkpoint.在部分情况下,checkpoint会刷盘部分脏页面并向前推进checkpoint点。从这一点上来说,MySQL的checkpoint和PureScale的castout非常类似:刷盘部分页面,并向前推进checkpoint点。另外MySQL对于每次checkpoint刷新多少页面,也有一套计算方法。
4. Buffer pool
/2012/05/25/mysql_innodb_buffer_pool_size.html
/2012/05/29/mysql-innodb-buffer-pool.html
/2012/05/30/mysql-innodb-buffer-pool-allocate.html
4.1 MySQL最新版本中,支持多个buffer pool Instance;而Postgres只有一个Buffer pool,IBM的Z/OS也有多个buffer pool,每个buffer pool中存放的页面类型不同。而MySQL的多个buffer pool是如何管理的,和单个相比有什么优势,目前没看清晰。
4.2 MySQL有预读功能,Postgres没有;
4.3 MySQL数据库InnoDB存储引擎的 Buffer Pool通过LRU算法管理页面的替换策略。LRU List按照功能被划分为两部分:LRU_young与LRU_old,默认情况下,LRU_old为链表长度的3/8。页面读取时(get/read ahead),首先链入LRU_old的头部。页面第一次访问时(read/write),从LRU_old链表移动到LRU_young的头部(整个LRU链表头)。而Postres采用的是Clock Sweep算法。MySQL虽然采用的LRU,但也不是每次新访问的页面都会移到list的头部,如果list很长,一个页面在list的第10位和头部的作用可以看做是相同的,即都不可能被淘汰。
4.4 全表扫描对buffer命中率的影响
全表扫描,在表比较大时,如果不采用特别的策略,大表的页面会使得buffer pool中的所有页面被换出,降低buffer pool的命中率。对于这个问题MySQL和Postgres都有处理:
MySQL全表扫描的所有页面,也遵循先读入LRU_old,后移动到LRU_young的原则,会导致Buffer Pool中的其他页面被替换出内存。为防止全表扫描的负面影响,InnoDB存储引擎提供了系统参数,innodb_old_blocks_time:只有当页面的后续访问与第一次访问时间间隔大于此值,才会移动到LRU链表头。innodb_old_blocks_time在5.1.41版本中引入。默认为0,也就是说全表扫描的页面会进入LRU_young(链表头),一个大表的全表扫描会导致大量page被替换出内存。
Postgres使用了buffer ring。
二者的思想都是,分配一部分buffer给类似全表扫描的场景使用,不让其污染整个buffer pool。
4.5 在正常情况下,buffer中的脏页MySQL是通过checkpoint刷出的,而Postgres是通过称之为bgwriter的进程刷出的,工作进程大部分情况下并不会直接刷盘,但是在bgwriter忙不过来的时候,工作进程也会自己刷盘,对于MySQL也是类似,在checkpoint忙不过时,工作线程也会自己刷盘。
4.6 MySQL具有AIO(异步)的功能,而Postgres没有。MySQL的异步IO有两种实现方式,一是使用linux自带的AIO功能,二是自己通过线程模拟的。
/2012/05/22/mysql-innodb-aio.html
在Linux系统上,MySQL数据库InnoDB存储引擎除了可以使用Linux自带的libaio之外,其内部还实现了一种称之为Simulated aio功能,用以模拟系统AIO实现(其实,Simulated aio要早于linux native aio使用在innodb中,可参考网文[16])。前面的章节,已经分析了InnoDB存储引擎对于Linux原生AIO的使用,此处,再简单分析一下 Innodb simulated aio的实现方式。
以下一段话摘自Transactions on InnoDB网站[16],简单说明了simulated aio在innodb中的处理方式。
… The query thread simply queues the request in an array and then returns to the normal working. One of the IO helper thread, which is a background thread, then takes the request from the queue and issues a synchronous IO call (pread/pwrite) meaning it blocks on the IO call. Once it returns from the pread/pwrite call, this helper thread then calls the IO completion routine on the block in question …
Linux simulated aio,实现简单,基本采用的仍旧是同步IO的方式。相对于Linux native aio,simulated aio最大的问题在于:每个I/O请求,最终都会调用一次pread/pwrite进行处理(除非可以进行相邻page的合并),而Linux native aio,对于一个array,进行一次异步I/O处理即可。
5. Insert buffer
/2012/05/21/mysql-innodb-insert-buffer.html
http://blogs.innodb.com/wp/2010/09/mysql-5-5-innodb-change-buffering/
MySQL的Insert buffer的思想就是,把一些对页面的修改先缓存在内存中,然后集中将修改与硬盘的原始数据Merge在一起。个人感觉这一点 和淘宝的OceanBase的思想比较相似,OceanBase把数据分为增量数据和基准数据,也就说应用对基准数据的修改,数据库层面并不会马上就修改基准数据,而是将修改数据汇总在一起成为增量数据,然后隔一段时间增量数据和基准数据进行merge,形成新的基准数据。这样可以把增量数据和基本数据分开对待,比如将增量数据放在SSD上。
6. Lock
/2012/05/19/locks_in_innodb.html
6.1 这一块看到的资料比较少,感觉MySQL的锁机制貌似比Postgres复杂一点,但是它们都支持行级锁,但是现在不是特别清楚MySQL的行锁是如何存储的,是直接存在内存中,还是其它方案,下面这篇文章对比了DB2和Oracle的锁机制。
http://www.ibm.com/developerworks/cn/data/library/techarticles/dm-0512niuxzh/?S_TACT=105AGX52&S_CMP=12-w-cto
6.2 DB2的行级锁直接记录在内存中,因此如果事务比较大,修改行比较多,锁占用的内存比较大,此时就需要锁升级;而Oracle是通过在元组头上作标记来标识行级锁,因此行级锁并不占内存,Postgres行级锁的方案和Oracle类似。关于表级锁的模式,从上述文章感觉DB2和Postgres差不多,表上都有8种模式。
6.3 值得注意的是,DB2的行级锁模式比较多,有7中之多,并且当插入一行数据时,该行上并不是直接加X(排他锁),而是加W(弱排他锁)。
6.4 Oracle存在一种单独的TX锁,许多对Oracle不太了解的技术人员可能会以为每一个TX锁代表一条被封锁的数据行,其实不然。TX的本义是Transaction(事务),当一个事务第一次执行数据更改(Insert、Update、Delete)或使用SELECT… FOR UPDATE语句进行查询时,它即获得一个TX(事务)锁,直至该事务结束(执行COMMIT或ROLLBACK操作)时,该锁才被释放。
在这一点上,Postgres和Oracle类似,也存储单独的事务锁,锁的标识为事务ID.
7. Deadlock
Postgres并没有一个单独的死锁检测进程,而是由各工作进程在申请锁超时时,自己检测。
/2011/12/05/innodb-dead-lock.html
根据以上描述,MySQL也没有一个单独的死锁检测线程,而是依靠各工作线程,MySQL和Postgres都采用构造WFG的形式进行死锁检测,MySQL会计算事务重量,在回滚环中的事务时,会选择轻量的事务进行回滚,而Postgres检测到环之后,直接回滚检测者自己。
原文地址: http://blog.sina.com.cn/s/blog_742eb90201010yul.html