更高的并发:改进PostgreSQL锁机制
如果你想构建一个大规模的网站,单凭横向扩展Web服务器是远远不够的。如何巧妙地管理数据库也是非常必要的。锁(Locking)便是实现网站高扩展性的一个关键。
在PostgreSQL中,借助于并发性的改进,通过减少锁及加速执行得到若干令人满意的特性。
一般推荐的做法是:在解决锁问题之前,无论如何先要检查出在你的PostgreSQL数据库服务器上正在运行的是什么,这非常有必要。我建议参考pg_stat_statements并仔细地检查系统瓶颈(bottleneck)。下面是给出的是其运行机制:
改进SELECT FOR UPDATE语句
假设两人同时试图修改数据库中同一行的内容,每个用户会首先选择(SELECT)一行来检查它的内容,然后开始更新。令人讨厌的事情是:这两个用户很可能会找到原来的行并且覆盖彼此做的改变。这是一个经典的竞态事件。
在现实生活中,这样会导致恶劣的后果:例如两个人也许会预定了同一架飞机的同一个航班;或者取款时取出的钱可能会比帐户中实际的数额更多。这显然不是我们想要的。
再拿前面的航班机票预定为例,假设有人想要预定飞机的某一座位:
SELECT ...FROM tableWHERE class = 'economy'AND empty = trueLIMIT 1FOR UPDATE
现在的麻烦是:如果另外一个人也试图抢占一个座位,他会发现该座位已经被第一个人选择。但是,这一行是被锁定的。第二个人的SELECT FOR UPDATE操作必须等到第一个人的事务处理完成。值得提醒的是,乘客可能非常乐意接受该航班上的其它任意座位,所以没有必要等待某个特定的座位。
PostgreSQL 9.5将会解决这一问题。下面是一种新的读取行的方式:
SELECT ...FROM tableWHERE class = 'economy'AND empty = trueLIMIT 1FOR UPDATE SKIP LOCKED
这里的巧妙之处在于PostgreSQL将会简单地忽略被锁定的行,并返回一个没有被别人锁定的行。这样是非常有意义的,因为100个同时在查看一个免费座位的用户会得到100个不同的行。这样的结果是你没有死守一个CPU,而是巧妙地横向扩展了系统中的所有CPU。由于冲突不再发生,没有人必须等其他人。
SELECT FOR SHARE
还有一种可以使PostgreSQL提供更高并发的方法。看下面的例子:
SELECT *FROM account AS a, currency AS cWHERE a.currency = c.idAND a.account = 4711FOR UPDATE
在这个例子中,某人想查看他的银行账户。现在主要的问题是:哪些行是被锁定的?答案是:account和currency。仅仅因为一个人想从ATM中取钱而锁定整个currency表显然并不是个好办法,而应该让很多人可以同时取钱,在这一问题上,PostgreSQL的解决方法是提前告知需要更新哪张表。
方法很简单:
FOR UPDATE OF account FOR SHARE OF currency
通过告诉PostgreSQL我们要做的事情,PostgreSQL数据库会在currency表上使用一个无害的锁。这样大部分人可以同时查看相同的currency而无需相互锁定,同时又保证了account表的安全。
并发就是一切
请记住:如果你只有单个CPU,并发将会是问题。因此,多个CPU能够同时进行资源共享在某种程上来讲是很有必要的。
原文链接:More Concurrency: Improved Locking In PostgreSQL(译者/牛亚真 审校/朱正贵 责编/仲浩)
译者简介:牛亚真,中科院计算机信息处理专业硕士研究生,关注大数据技术和数据挖掘方向。