PostgreSQL简介

PostgreSQL简介

上个周末,无聊的时候关注了一下PostgreSQL。第一次尝试去安装PostgreSQL,还是好几年前的事了,那是8.0版本刚出来,终于开始原生的支持windows了,所以在自己电脑上折腾了一个。不过那时候也仅限于安装了一次而已,甚至psql的命令行都不知道怎么用。

同样作为开源关系型数据库,MySQL在这几年获得了更多的关注。大量的互联网公司都基于MySQL来构架系统,也导致MySQL DBA开始火热,一大堆年轻有为的同学投入到其中,渐成燎原之势。MySQL数据库火热了,MySQL AB公司却被sun收购,现在又随着sun要投入Oracle的怀抱,而且欧盟已经无条件批准这个收购,只剩下中国和俄罗斯,大局已定。作为商业数据库的绝对老大,Oracle的这次收购,让MySQL的支持者感到了威胁,其创始人甚至发起了一场保护MySQL(有墙),阻击Oracle收购的运动。

这也是PostgreSQL的机会,最近PostgreSQL的开发节奏很快,8.5已经连续出到了alpha3版,在这个版本中,最吸引我的是hot standby,类似于Oracle11g的active data guard,hot standby也可以在恢复的同时提供读服务,而以往版本,PostgreSQL的物理备库warm standby,则只能处于恢复状态,一旦open,则需要重做,比较痛苦。PostgreSQL的很多特性,都和Oracle相当的类似,甚至有一家商业化的公司EnterpriseDB,在致力于将PostgreSQL打包,使得应用程序从Oracle迁移到PostgreSQL更方便,据说80%的Oracle应用代码甚至不需要做修改就能在PostgreSQL运行。因此,我在twitter上说,如果PostgreSQL在人机交互的工具和配置部分,能够更加友好一点,完全是一个影子版本的Oracle。

PostgreSQL也支持mvcc多版本一致性控制。不过其实现的机制,和innodb的方式比较像,而和Oracle的不一样。Oracle是将变化的前映像记录到单独的undo段中,而PostgreSQL则只是将前映像(Tuples)上做个标记,如果是delete,则相当于是逻辑删除,实际的数据还是在原来的段中,如果是insert,相当于先delete,再insert,而且会在原来的记录上加一条指向新记录的指针,形成一个链表,查询的时候需要沿着这个链表找到一致的数据。这样会造成一个问题,一段时间以后,dml操作使得数据段和索引段中都有大量的前映像信息存在,会严重影响数据查询的效率。PostgreSQL的mvcc的这种实现方式,带来的一个好处是回滚非常快,只需要修改前映像上的几个标志位即可,而不像oracle需要从undo段将前映像再复制回来。但是,这种方便回滚,却会损失查询性能的设计思路,真的比较诡异。PostgreSQL中有一个专门用来清理这些旧版本数据的程序,叫做vacuum。在以前的版本中,需要定期执行vacuum来优化数据存储结构。这对于DBA来说,无疑是一件痛苦的事情。直到8.1版本,引入了autovacuum,系统可以自动来进行这些清理工作,终于人性化了一点点。

在8.3版本,引入了一个新的特性HOT(Heap Only Tuples),主要的目的是努力避免update造成的性能低下的问题。其实这个HOT,说白了很简单,对于update,要实现mvcc,其机制还是一样的,区别在于select,在沿着链表找一致性数据的过程中,如果发现这个检查过的版本已经没有任何事物在引用了,就会顺便把清理工作做掉,而不是像以前要等vacuum来做。因此这会加大一点select的压力,但前人栽树,后人乘凉,接下来需要访问这些数据的其他select就会快很多了,这和Oracle的延迟块清除其实有些类似的,当然两者的设计目的并不一样。

遭遇MySQL Replication Fatal Error 1236

一套Master-Master Replication的MySQL集群,版本5.1.37。其中一个节点A出现OS异常重启,数据库启动后表现正常。但是没过多久另外一个节点B报错:

091127 21:50:21 [ERROR] Error reading packet from server: Client requested master to start replication
from impossible position ( server_errno=1236)
091127 21:50:21 [ERROR] Got fatal error 1236: 'Client requested master to start replication
from impossible position' from master when reading data from binary log
091127 21:50:21 [Note] Slave I/O thread exiting, read up to log 'mysql-bin.000535', position 193022771

Slave_IO_Running线程终止。仔细看上面的报错信息,说slave进程试图从mysql-bin.000535日志的position 193022771开始启动恢复,但是该日志中是没有这个position的。

跑到A上通过mysqlbinlog查看该日志,发现最后一个有效position是193009460。而要求的193022771已经大于最后有效的position了。这个原因就搞不明白了,难道是因为A库异常关闭后导致A节点的binlog没有来得及刷到磁盘,而B节点slave已经恢复到前面去了?

$mysqlbinlog mysql-bin.000535 > 1.txt

$tail -n 7 1.txt
# at 193009460
#091127 20:50:21 server id 1  end_log_pos 193009487     Xid = 194299849
COMMIT/*!*/;
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;

尝试将B节点change master到最后一个有效的position处,问题暂时得到解决:

change master to master_log_file='mysql-bin.000535', master_log_pos=193009460

网上搜索了一把,发现logzgh之前也碰到过同样的问题,版本是5.0.51。

Gearman for MySQL

Gearman是一个开源的分布式调度框架,支持多种语言。在分布式环境中,如何管理大量的服务器,将某些任务分发到大量的机器上调度执行,是一个比较大的挑战,Gearman为该类任务提供了一个不错的思路。在未来的MySQL集群环境中,Gearman这类工具应当大有用武之地,所以它也提供了MySQL UDF的支持。
gearman_stack

一个Gearman请求的处理过程涉及三个角色:Client -> Job -> Worker。
  Client:请求的发起者,可以是 C,PHP,Perl,MySQL UDF 等等。
  Job:请求的调度者,用来负责协调把 Client 发出的请求转发给合适的 Worker。
  Worker:请求的处理者,可以是 C,PHP,Perl 等等。
因为 Client,Worker 并不限制用一样的语言,所以有利于多语言多系统之间的集成。

InnoDB线程并发检查机制

InnoDB在接受MySQL线程调用能时,有一个并发线程的检查机制,通过innodb_thread_concurrency参数进行控制。如果参数设置大于0,则表示检查机制开启,允许进入的线程数就是参数的值。等于0则禁用并发检查。

在新的MySQL线程调用Innodb接口前,Innodb会检查已经接受的请求线程数,如已经超过innodb_thread_concurrency设置的限制,则该请求线程会等待innodb_thread_sleep_delay微秒后尝试重新请求,如果第二次请求还是无法获得,则该线程会进入线程队列休眠。重试两次的机制是为了减少CPU的上下文切换的次数,以降低CPU消耗,这和Oracle中latch的spin机制是同样的道理。如果请求被Innodb接受,则会获得一个次数为innodb_concurrency_tickets(默认500次)的通行证,在次数用完之前,该线程重新请求时无须再进行前面所说innodb_thread_concurrency的检查。

上述检查逻辑在源码storage/innobase/srv/srv0srv.c(Innodb很多参数都可以在该文件中找到定义)的srv_conc_enter_innodb函数中,有兴趣的可以仔细阅读一下,代码比较浅显,不难理解。另外,如果是一个已经持有lock的线程,则通过调用srv_conc_force_enter_innodb函数可以无视该检查,这是为了避免线程长时间持有锁影响性能,且可能增加死锁的机率。除此之外,slave线程也是有无视检查直接通行的权限。

简单思考一下上述机制,可以得出一个初步的推论:在数据库并发请求较小的情况下,从性能上来说禁用检查机制应该是更好的,毕竟执行检查机制本身也需要加锁(Mutex)。当并发线程很高的情况下,则开启检查机制对性能更有利。至于具体innodb_thread_concurrency设置为多少,可能就需要在不同的条件下实际的做一下测试了,不同的硬件环境,不同的MySQL版本和Innodb版本,应该都会有一些区别。

源代码中对于innodb_thread_concurrency参数的注释如下:

/* The following controls how many threads we let inside InnoDB concurrently:
threads waiting for locks are not counted into the number because otherwise
we could get a deadlock. MySQL creates a thread for each user session, and
semaphore contention and convoy problems can occur withput this restriction.
Value 10 should be good if there are less than 4 processors + 4 disks in the
computer. Bigger computers need bigger values. Value 0 will disable the
concurrency check. */

ulong   srv_thread_concurrency  = 0;

因为检查机制需要Mutex保护(Mutex-based Model),所以开启检查本身也有性能消耗,并且扩展性也会受到限制,在MySQL5.4版本中引入了一种新的机制(Timer-based Model),这里就不讨论了,有兴趣的可以参考这里(需要翻墙),实际上XtraDB存储引擎里已经包含Timer-based Model,通过参数innodb_thread_concurrency_timer_based可以开启,默认为OFF。在MySQL5.4的srv0srv.c的源代码中的注释中,可以看到Google和Percona的版权声明,看来MySQL5.4中吸引了很多第三方的改进代码,值得期待。