【问题标题】:make python wait for stored procedure to finish executing让python等待存储过程完成执行
【发布时间】:2014-06-27 18:15:35
【问题描述】:

我有一个使用 pyodbc 调用 MSSQL 存储过程的 python 脚本,如下所示:

cursor.execute("exec MyProcedure @param1 = '" + myparam + "'")

我在循环中调用这个存储过程,我注意到有时,该过程在最后一次执行完成之前被再次调用。我知道这一点,因为如果我添加该行

time.sleep(1)

在执行行之后,一切正常。

有没有更优雅、更省时的说法,“睡到 exec 结束”?

更新(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.235\SQLEXPRESS;
    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()

【问题讨论】:

  • 查看my answer 以获得不需要对存储过程进行任何更改的现代工作解决方案。
  • 我知道这看起来很奇怪,但是在 cursor.execute("exec ...") 之后我添加了 cursor.close() ,出于某种原因,这是在移动到下一个语句之前关闭光标。

标签: python sql sql-server stored-procedures pyodbc


【解决方案1】:

我知道这已经过时了,但我只是花了几个小时试图弄清楚如何让我的 Python 代码等待 MSSQL 上的存储过程完成。

问题不在于异步调用。

解决此问题的关键是确保您的程序在完成运行之前不会返回任何消息。否则,PYDOBC 将来自 proc 的第一条消息解释为它的结尾。

使用SET NOCOUNT ON 运行您的程序。此外,请确保您可能用于调试的任何 PRINT 语句或 RAISERROR 都已静音。

在你的过程中添加一个像@muted这样的BIT参数,并且只有当它是0时才会引发你的调试消息。

在我的特殊情况下,我正在执行一个过程来处理加载的表,并且我的应用程序在过程完成运行之前退出并关闭游标,因为我正在获取行数和调试消息。

总而言之,按照

的思路做一些事情

cursor.execute('SET NOCOUNT ON; EXEC schema.proc @muted = 1')

PYODBC 将等待 proc 完成。

【讨论】:

  • 太棒了! SET NOCOUNT ON 拯救了我的一天!
【解决方案2】:

这是我的解决方法:

在数据库中,我创建了一个名为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。

这有点笨拙,但对我来说效果很好!

【讨论】:

  • 如果存储过程可以保证运行没有错误,这很好,但如果您有一个在执行过程中可能出错的存储过程,请小心。如果您从未将状态设置回 0,则会导致 Python 脚本中的无限循环。
【解决方案3】:

我找到了一个不需要“静音”您的存储过程或以任何方式更改它们的解决方案。根据the pyodbc wiki

nextset()

此方法将使光标跳到下一个可用结果 set,从当前结果集中丢弃任何剩余的行。如果 没有更多的结果集,该方法返回 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()

【讨论】:

    【解决方案4】:

    没有允许您等待异步调用完成的内置 python。但是,您可以使用 Tornado 的 IOLoop 实现此行为。 Tornado 的gen 接口允许您将函数调用注册为Task,并在调用完成后返回到函数的下一行。这是使用gengen.Task 的示例

    from tornado import gen
    
    @gen.engine
    def func(*args, **kwargs)
        for _ in range(5):
            yield gen.Task(async_function_call, arg1, arg2)
    
        return
    

    在示例中,func 的执行在 async_function_call 完成后恢复。这样,对asnyc_function_call 的后续调用就不会重叠,并且您不必使用time.sleep 调用暂停主进程的执行。

    【讨论】:

    • 感谢您的回答!我正在阅读 tornado.gen 文档,我对 Task 函数如何与 pyodbc 光标一起工作有点困惑。对cursor.execute 的调用是否包含在async_function_call 中,或者它是否在没有pyodbc 的情况下执行存储过程?如果是后者,如何连接数据库?
    • 您最好将async_function_call 替换为cursor.execute,并将您要执行的过程作为参数传递。所以你的行会写成:yield gen.Task(cursor.execute, "exec MyProcedure @param1 '%s'" % myparam)
    • 我试过这个确切的例子,但它给了我TypeError: execute() takes no keyword arguments。它是否适合您?
    • 嗯,你能把代码中出现错误的那一行粘贴到这里吗?另外,由于要执行的命令涉及字符串插值,我会尝试先将命令存储为变量,然后将变量作为参数传递
    • 我制作了一个测试 python 脚本,它简单地连接到 MSSQL 数据库,然后执行您答案中的确切代码。我创建了一个名为longtest 的存储过程,它基本上只是循环并多次递增计数器。我在线上遇到错误yield gen.Task(cursor.execute, 'exec longtest')。有什么想法吗?
    【解决方案5】:

    我认为我的方式有点粗略,但同时更容易理解:

    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”从作业活动表中返回“作业未运行”时,睡眠 1 秒并再次检查结果。 这项工作适用于 sql 作业,对于 SP 来说应该在另一个表中

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-10
      • 2021-10-14
      • 2014-03-18
      • 2021-06-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多