在Twisted下用MySQL adbapi获取自增id


D jango的ORM有一个很便捷的功能,其实也应该说是一个很基本的功能吧。就是在对一个model调用 save() 插入到数据库后,会将创建的自增id同步到当前model上。SQL中调用 INSERT 默认的返回值是插入的行数,就目前的应用来说,其实是一个没啥意义的返回值,所以Django的ORM能够处理好自增id的同步是一件很让人愉悦的事。

不过没有使用Django,最近用的是Twisted提供的adbapi,如何获取自增id呢?

如果是我力挺的PostgreSQL的话,很简单,给 INSERT 加上 RETURNING 语句就可以了:

INSERT INTO distributors (did, dname)
VALUES (DEFAULT, 'XYZ Widgets') RETURNING did;
不过MySQL呢?

最近用了一阵子MySQL,只是从命令行自动补全这方面来说,就已经明显地感觉到和PostgreSQL的差距了。老实说真不知道为啥那么多人如此热爱深爱着MySQL……

在StackOverflow上搜了一下,找到两种方法,要么是使用

SELECT LAST_INSERT_ID();
或者是使用connector的 mysql_insert_id() 函数,这个对于Python中的 MySQLdb 来说就是connection的 insert_id() 函数,比如:

conn = MySQLdb.connect(host='heaven', user='god',
                       passwd='jesus', db='elysium')
cursor = conn.cursor()
cursor.execute('INSERT INTO account VALUES (%s, %s)',
               ('satan', 'male or female, who knows'))
new_id = conn.insert_id() # <= 就是这玩意儿
过程也还算简单,但是对于Twisted的adbapi来说,我们的活并没有结束。为啥呢?因为adbapi用的是线程池来管理MySQL的连接的,每次Query调用,都会从线程池中获取一个线程,然后将相应的事务defer到该线程来处理。其默认的事务是以单个SQL语句为划分的,所以说,对于MySQL这样在执行完后还需要做其他操作的需求来说,默认的接口是无法满足的。这儿我再吐一下槽,还是pg好啊……

默认接口不够用,那我们就只能扩展它了。稍稍看一下adbapi的源代码我们可以发现,对于通常事务,adbapi其实是使用了 runInteraction 这个接口函数的。具体不同的事务,adbapi是将相应的callback作为其第一个参数,然后在 deferToThreadPool 时指定线程运行该callback来实现。所以,我们只需要为MySQL定义一种新的事务就可以了。

下面就是我们需要定义的新事务:

def runMySQLInsert(self, *args, **kw):
    assert self.dbapiName == 'MySQLdb'
    return self.runInteraction(self._runMySQLInsert, *args, **kw)

def _runMySQLInsert(self, trans, *args, **kw):
    trans.execute(*args, **kw)
    return trans.connection.insert_id()
Ok,接下来,把这两个函数Monkey Patch到adbapi上就完事了:)

相关内容