我有一个python脚本,它使用pyodbc调用MSSQL存储过程,如下所示:
cursor.execute("exec MyProcedure @param1 = '" + myparam + "'")
我在循环中调用这个存储过程,我注意到,有时,在最后一次执行完成之前,这个过程会被再次调用。我知道这个是因为如果我加上
time.sleep(1)
在执行行之后,一切正常。
有没有一种更优雅、更省时的方式来表达"sleep until the exec Is finished"?
更新(Divij的解决方案):这段代码目前不能为我工作:
from tornado import gen
import pyodbc
@gen.engine
def func(*args, **kwargs):
# connect to db
cnxn_str = """
Driver={SQL Server Native Client 11.0};
Server=172.16.111.235SQLEXPRESS;
Database=CellTestData2;
UID=sa;
PWD=Welcome!;
"""
cnxn = pyodbc.connect(cnxn_str)
cnxn.autocommit = True
cursor = cnxn.cursor()
for _ in range(5):
yield gen.Task(cursor.execute, 'exec longtest')
return
func()
我知道这是旧的,但我刚刚花了几个小时试图弄清楚如何让我的Python代码等待MSSQL上的存储过程完成。
问题不在于异步调用。
解决此问题的关键是确保您的过程在完成运行之前不返回任何消息。否则,PYDOBC会将来自进程的第一条消息解释为进程的结束。
用SET NOCOUNT ON
运行你的程序。此外,确保您可能用于调试的任何PRINT
语句或RAISERROR
都是静音的。
添加一个BIT参数,如@muted
到您的进程,只有当它是0
时,才会引发您的调试消息。
在我的特殊情况下,我正在执行一个进程来处理一个加载的表,我的应用程序在过程完成运行之前退出并关闭游标,因为我正在获取行计数和调试消息。
所以总结一下,按照
的思路做一些事情 cursor.execute('SET NOCOUNT ON; EXEC schema.proc @muted = 1')
和PYODBC将等待进程完成。
这是我的解决方案:
在数据库中,我创建了一个名为RunningStatus
的表,其中只有一个字段status
,这是一个bit
,只有一行,初始设置为0。
在存储过程的开头,我执行
行update RunningStatus set status = 1;
在存储过程的末尾,
update RunningStatus set status = 0;
在我的Python脚本中,我打开一个新连接并游标到同一个数据库。在execute
行之后,我只需添加
while 1:
q = status_check_cursor.execute('select status from RunningStatus').fetchone()
if q[0] == 0:
break
您需要创建一个新的连接和游标,因为来自旧连接的任何调用都将中断存储过程,并且可能导致status
永远不会返回0。
这是一个小麻烦,但它对我来说工作得很好!
我已经找到了一个解决方案,不需要"muting"您的存储过程或以任何方式更改它们。根据pyodbc
wiki:
nextset ()
此方法将使游标跳转到下一个可用结果设置,丢弃当前结果集中的任何剩余行。如果如果没有更多的结果集,则该方法返回False。否则,它返回True,后续对fetch方法的调用将返回下一个结果集中的行。
此方法主要用于存储过程返回多个结果。
若要等待存储过程完成执行,然后再继续执行程序的其余部分,请在执行游标中运行存储过程的代码后使用以下代码:
slept = 0
while cursor.nextset():
if slept >= TIMEOUT:
break
time.sleep(1)
slept += 1
您也可以将time.sleep()
的值从1
秒更改为略低于一秒,以减少额外的等待时间,但我不建议在一秒钟内调用它很多次。
下面是一个完整的程序,展示了如何实现这段代码:
import time
import pyodbc
connection = pyodbc.connect('DRIVER={SQL Server};SERVER=<hostname>;PORT=1433;DATABASE=<database name>;UID=<database user>;PWD=password;CHARSET=UTF-8;')
cursor = connection.cursor()
TIMEOUT = 20 # Max number of seconds to wait for procedure to finish execution
params = ['value1', 2, 'value3']
cursor.execute("BEGIN EXEC dbo.sp_StoredProcedureName ?, ?, ? END", *params)
# here's where the magic happens with the nextset() function
slept = 0
while cursor.nextset():
if slept >= TIMEOUT:
break
time.sleep(1)
slept += 1
cursor.close()
connection.close()
python没有内置允许您等待异步调用完成的功能。然而,你可以使用Tornado的IOLoop实现这种行为。Tornado的gen
接口允许您将函数调用注册为Task
,并在调用完成执行后返回到函数的下一行。下面是使用gen
和gen.Task
的示例
from tornado import gen
@gen.engine
def func(*args, **kwargs)
for _ in range(5):
yield gen.Task(async_function_call, arg1, arg2)
return
在本例中,async_function_call
执行完毕后,func
继续执行。这样对asnyc_function_call
的后续调用就不会重叠,并且您不必在调用time.sleep
时暂停主进程的执行。
我认为我的方法有点粗糙,但同时也更容易理解:
cursor = connection.cursor()
SQLCommand = ("IF EXISTS(SELECT 1 FROM msdb.dbo.sysjobs J JOIN
msdb.dbo.sysjobactivity A ON A.job_id = J.job_id WHERE J.name ='dbo.SPNAME' AND
A.run_requested_date IS NOT NULL AND A.stop_execution_date IS NULL) select 'The job is
running!' ELSE select 'The job is not running.'")
cursor.execute(SQLCommand)
results = cursor.fetchone()
sresult= str(results)
while "The job is not running" in sresult:
time.sleep(1)
cursor.execute(SQLCommand)
results = cursor.fetchone()
sresult= str(results)
当"SPNAME"从jobactivity表返回"作业未运行"时,休眠1秒并再次检查结果。此工作为sql作业,对于SP应该像在另一个表