背景:
最近找到一个异步数据库驱动,TorMySQL,ab测试后效果是其他几个异步驱动最好的,遂用之。
因为尽可能业务逻辑层和数据访问层分离,并且考虑到单一负责原则,把每一个数据库的操作简称为service,如user_service,work_service之类的。
很多情况下,每一个逻辑业务都是要N个service合作才完成的。
所以就不在单个service中try except,而是在业务逻辑层中try except。
一是:
少写很多代码,不然每一个service都要try except之后,logging.error(ex, exc_info=1)打印traceback,然后再raise ex,多麻烦...
二是:
考虑到很多情况下有用到事务,只能在业务逻辑层捕获exception,捕获到就conn.rollback(),没捕获到就conn.commit()。
然而,我上面这种设计竟然没捕获到traceback,简直匪夷所思。
好了,背景交代完毕。
版本:
python 2.7
tormysql==0.2.9
tornado==4.2
测试1:
import logging
from tornado.ioloop import IOLoop
from tornado import gen
import tormysql
pool = tormysql.ConnectionPool(
max_connections = 20, #max open connections
idle_seconds = 7200, #conntion idle timeout time, 0 is not timeout
wait_connection_timeout = 3, #wait connection timeout
host = "127.0.0.1",
user = "root",
passwd = "123qwe",
db = "chanzai_dev",
charset = "utf8"
)
@gen.coroutine
def aa(conn):
sql = "SELECT * FROM UserDevice where user_id = %s" % 100002
with conn.cursor() as cursor:
yield cursor.execute(sql)
cursor.fetchone()
# 下面是故意出错代码
a = '10'
b = a / 10
print b
@gen.coroutine
def bb(conn):
yield aa(conn)
@gen.coroutine
def cc():
with (yield pool.Connection()) as conn:
try:
yield bb(conn)
except KeyError:
pass
except Exception, ex:
yield conn.rollback()
logging.error(ex, exc_info=1)
ioloop = IOLoop.instance()
ioloop.run_sync(cc)
测试结果:
G:\projects\myApp\tmp>a.py
ERROR:root:unsupported operand type(s) for /: 'str' and 'int'
None
测试2:
import logging
from tornado.ioloop import IOLoop
from tornado import gen
import tormysql
pool = tormysql.ConnectionPool(
max_connections = 20, #max open connections
idle_seconds = 7200, #conntion idle timeout time, 0 is not timeout
wait_connection_timeout = 3, #wait connection timeout
host = "127.0.0.1",
user = "root",
passwd = "123qwe",
db = "chanzai_dev",
charset = "utf8"
)
@gen.coroutine
def aa(conn):
sql = "SELECT * FROM UserDevice where user_id = %s" % 100002
with conn.cursor() as cursor:
yield cursor.execute(sql)
cursor.fetchone()
# 下面是故意出错代码
a = '10'
b = a / 10
print b
@gen.coroutine
def bb(conn):
yield aa(conn)
@gen.coroutine
def cc():
with (yield pool.Connection()) as conn:
try:
yield bb(conn)
except KeyError:
pass
except Exception, ex:
logging.error(ex, exc_info=1)
yield conn.rollback()
ioloop = IOLoop.instance()
ioloop.run_sync(cc)
测试结果:
G:\projects\myApp\tmp>a.py
ERROR:root:unsupported operand type(s) for /: 'str' and 'int'
Traceback (most recent call last):
File "G:\projects\myApp\tmp\a.py", line 40, in cc
yield bb(conn)
File "G:\Python27\lib\site-packages\tornado\gen.py", line 1015, in run
value = future.result()
File "G:\Python27\lib\site-packages\tornado\concurrent.py", line 237, in result
raise_exc_info(self._exc_info)
File "G:\Python27\lib\site-packages\tornado\gen.py", line 1021, in run
yielded = self.gen.throw(*exc_info)
File "G:\projects\myApp\tmp\a.py", line 34, in bb
yield aa(conn)
File "G:\Python27\lib\site-packages\tornado\gen.py", line 1015, in run
value = future.result()
File "G:\Python27\lib\site-packages\tornado\concurrent.py", line 237, in result
raise_exc_info(self._exc_info)
File "G:\Python27\lib\site-packages\tornado\gen.py", line 1024, in run
yielded = self.gen.send(value)
File "G:\projects\myApp\tmp\a.py", line 30, in aa
b = a / 10
TypeError: unsupported operand type(s) for /: 'str' and 'int'
G:\projects\myApp\tmp>
观察结果:
except Exception, ex:
logging.error(ex, exc_info=1)
yield conn.rollback()
和
except Exception, ex:
yield conn.rollback()
logging.error(ex, exc_info=1)
前者才能得到想要的结果,为什么有后者这种写法呢,是因为,想着对于数据库的操作出错了还是尽早rollback为好,没想到就踩了坑。
原因分析:
看了下pymysql的connections.py源码(因为TorMySQL是对pymysql的封装),发现确实有很多try except,我都不用具体往下看了,因为很大可能性是我们的ex被pymysql给捕获到了,但是raise出来的是pymysql处理过的ex,所以看不到traceback...吧
解决方案就是先logging.error(ex, exc_info=1) 再 yield conn.rollback()