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上就完事了:)