我正在尝试在python MySQLDB中强制执行查询的时间限制。我有一种情况,我无法控制查询,但需要确保它们不会超过设定的时间限制。我已经尝试使用signal.SIGALRM中断执行的调用,但这似乎不起作用。信号被发送,但在执行完成后才会被捕获。
我写了一个测试用例来证明这种行为:
#!/usr/local/bin/python2.6
import time
import signal
from somewhere import get_dbc
class Timeout(Exception):
""" Time Exceded """
def _alarm_handler(*args):
raise Timeout
dbc = get_dbc()
signal.signal(signal.SIGALRM, _alarm_handler)
signal.alarm(1)
try:
print "START: ", time.time()
dbc.execute("SELECT SLEEP(10)")
except Timeout:
print "TIMEOUT!", time.time()'
“SELECT SLEEP(10)”模拟慢速查询,但我确实看到了与实际慢速查询相同的行为。
结果:
START: 1254440686.69
TIMEOUT! 1254440696.69
正如你所看到的,它已经睡了10秒然后我得到了Timeout Exception。
问题:
答案 0 :(得分:8)
@nosklo's twisted-based solution优雅且可行,但如果你想避免依赖扭曲,那么任务仍然可行,例如:
import multiprocessing
def query_with_timeout(dbc, timeout, query, *a, **k):
conn1, conn2 = multiprocessing.Pipe(False)
subproc = multiprocessing.Process(target=do_query,
args=(dbc, query, conn2)+a,
kwargs=k)
subproc.start()
subproc.join(timeout)
if conn1.poll():
return conn1.recv()
subproc.terminate()
raise TimeoutError("Query %r ran for >%r" % (query, timeout))
def do_query(dbc, query, conn, *a, **k):
cu = dbc.cursor()
cu.execute(query, *a, **k)
return cu.fetchall()
答案 1 :(得分:2)
我已经尝试使用signal.SIGALRM来中断执行的调用,但这似乎不起作用。信号被发送,但在执行完成后才会被捕获。
mysql库在内部处理中断的系统调用,因此在API调用完成之前你不会看到SIGALRM的副作用(没有杀死当前的线程或进程)
您可以尝试修补MySQL-Python并使用MYSQL_OPT_READ_TIMEOUT选项(在mysql 5.0.25中添加)
答案 2 :(得分:1)
为什么在执行完成之后我才收到信号?
查询是通过C函数执行的,该函数阻止Python VM执行直到它返回。
是否有另一种可靠的方法来限制查询执行时间?
这是(IMO)一个非常难看的解决方案,但 工作。您可以在单独的过程中运行查询(通过fork()
或multiprocessing
module)。在主进程中运行闹钟计时器,当您收到它时,请向子进程发送SIGINT
或SIGKILL
。如果您使用multiprocessing
,则可以使用Process.terminate()
方法。
答案 3 :(得分:1)
使用adbapi。它允许您异步执行db调用。
from twisted.internet import reactor
from twisted.enterprise import adbapi
def bogusQuery():
return dbpool.runQuery("SELECT SLEEP(10)")
def printResult(l):
# function that would be called if it didn't time out
for item in l:
print item
def handle_timeout():
# function that will be called when it timeout
reactor.stop()
dbpool = adbapi.ConnectionPool("MySQLdb", user="me", password="myself", host="localhost", database="async")
bogusQuery().addCallback(printResult)
reactor.callLater(4, handle_timeout)
reactor.run()
答案 4 :(得分:1)
我最近遇到了同样的问题,我必须遇到几个条件:
我们有以下课程布局(遗憾的是我无法发布真实来源):
class AbstractModel: pass
class FirstDatabaseModel(AbstractModel): pass # Connection to one DB host
class SecondDatabaseModel(AbstractModel): pass # Connection to one DB host
为每个模型创建了几个线程。
在我们的应用程序中一个模型=一个数据库。所以我为每个模型创建了“服务连接”(因此我们可以在并行连接中执行KILL
)。因此,如果创建了FirstDatabaseModel
的一个实例,则创建了2个数据库连接;如果创建了5个实例,则只使用了6个连接:
class AbstractModel:
_service_connection = None # Formal declaration
def __init__(self):
''' Somehow load config and create connection
'''
self.config = # ...
self.connection = MySQLFromConfig(self.config)
self._init_service_connection()
# Get connection ID (pseudocode)
self.connection_id = self.connection.FetchOneCol('SELECT CONNECTION_ID()')
def _init_service_connection(self):
''' Initialize one singleton connection for model
'''
cls = type(self)
if cls._service_connection is not None:
return
cls._service_connection = MySQLFromConfig(self.config)
现在我们需要一个杀手:
def _kill_connection(self):
# Add your own mysql data escaping
sql = 'KILL CONNECTION {}'.format(self.connection_id)
# Do your own connection check and renewal
type(self)._service_connection.execute(sql)
注意:connection.execute
=创建游标,执行,关闭游标。
使用threading.Lock
确保杀手线程安全:
def _init_service_connection(self):
''' Initialize one singleton connection for model
'''
cls = type(self)
if cls._service_connection is not None:
return
cls._service_connection = MySQLFromConfig(self.config)
cls._service_connection_lock = threading.Lock()
def _kill_connection(self):
# Add your own mysql data escaping
sql = 'KILL CONNECTION {}'.format(self.connection_id)
cls = type(self)
# Do your own connection check and renewal
try:
cls._service_connection_lock.acquire()
cls._service_connection.execute(sql)
finally:
cls._service_connection_lock.release()
最后使用threading.Timer
添加定时执行方法:
def timed_query(self, sql, timeout=5):
kill_query_timer = threading.Timer(timeout, self._kill_connection)
kill_query_timer.start()
try:
self.connection.long_query()
finally:
kill_query_timer.cancel()
答案 5 :(得分:-1)
为什么在执行完成之后我才收到信号?
等待网络I / O的进程处于不可中断状态(UNIX事物,与Python或MySQL无关)。它在系统调用结束后得到信号(可能是EINTR
错误代码,虽然我不确定。)
是否有另一种可靠的方法来限制查询执行时间?
我认为它通常由像mkill
这样的外部工具完成,它监视MySQL长时间运行的查询并杀死它们。