只用Mysql搞一个分布式锁

在web开发中,分布式的锁的应用场景甚多,我们可以通过分布式锁来进行一些仅依赖于数据库的事务很难直接保证原子性的操作,比如多种不同数据源的访问,网络通信等等。多数情况下我们会使用memcache的add, redis中在set中指定nx参数等来完成。

下面介绍一个仅依赖Mysql来完成分布式锁的方式,如果项目比较小且主要使用的数据库是Mysql,那么就不需要引入额外的架构上的依赖了。

这里的方法就是通过Mysql的GET_LOCK函数和RELEASE_LOCK函数来完成的。我们可以通过GET_LOCK(lock_key, timeout)函数来创建一个key:

SELECT GET_LOCK('user_id_XXXX', 10)

除了获取到lock_key的进程,其他进程就无法进入被这个锁锁住的代码逻辑了。之后,在同一个db session中,可以再通过RELEASE_LOCK(lock_key)来释放这个lock_key:

SELECT RELEASE_LOCK('user_id_XXXX')

被释放的lock_key就可以被别的进程获取了。

我写了一个python的例子,可以看一下

class DbLock(object):
    def __init__(self, key, connection, timeout=5):
        '''
        key: lock key.
        connection: a db connection object.
        '''
        self.key = key
        self.connection = connection
        self.timeout = timeout
        self.cursor = None

    def __enter__(self):
        self.cursor = self.connection.cursor()
        self.cursor.execute("SELECT GET_LOCK(%s, %s)",
                            [self.key, self.timeout])
        result, = self.cursor.fetchone()
        if result != 1:
            raise Exception("DbLock %s error, timeout %s, returned %s."
                            % (self.key, self.timeout, result))

    def __exit__(self, exc_type, exc_value, traceback):
        self.cursor.execute("SELECT RELEASE_LOCK(%s)",
                            [self.key])
        self.cursor.close()
        if exc_type is not None:
            pass
            # deal with error

这样在实际的代码中,就可以通过如下方式来使用这个lock了(我们假设是django的数据库connection对象):

from django.db import connection

with DbLock(key, connection, 5):
    # your own code