【问题标题】:PYMSSQL/SQL Server 2014: is there a limit to the length of a list of PKs to use as a subquery?PYMSSQL/SQL Server 2014:用作子查询的 PK 列表的长度是否有限制?
【发布时间】:2017-08-12 07:19:46
【问题描述】:

我已经实现了一个 python 脚本,以便使用以下方案将数百万个文档(由 .NET Web 应用程序生成并且所有内容都放在一个目录中)划分到子文件夹中:年/月/批次,因为所有这些文件来自的任务最初被分成批次。 我的 python 脚本对 SQL Server 2014 执行查询,其中包含每个文档所需的所有数据,特别是创建它的月份和年份。然后它使用shutil 模块来移动 pdf。因此,我首先执行第一个查询以获取给定月份和年份的批次列表:

queryBatches = '''SELECT DISTINCT IDBATCH
                FROM [DBNAME].[dbo].[WORKS]
                WHERE YEAR(DATETIMEWORK)={} AND MONTH(DATETIMEWORK)={}'''.format(year, month)

然后我执行:

for batch in batches:
  query = '''SELECT IDWORK, IDBATCH, NAMEDOCUMENT
             FROM [DBNAME].[dbo].[WORKS]
             WHERE NAMEDOCUMENTI IS NOT NULL and
                   NAMEDOCUMENT not like '/%/%/%/%.pdf' and 
                   YEAR(DATETIMEWORK)={} and 
                   MONTH(DATETIMEWORK)={} and 
                   IDBATCH={}'''.format(year,month,batch[0])

根据 PYMSSQL 使用文档,将其记录收集到游标中。所以我继续:

IDWorksUpdate = []
row = cursor.fetchone()
while row:

  if moveDocument(...):
    IDWorksUpdate.append(row[0])
  row = cursor.fetchone()

最后,当循环结束时,在IDWorksUpdate 中,我拥有所有 WORKS 的 PK,其文档已成功正确移动到子文件夹中。所以,我关闭光标和连接并实例化新的。 最后我表演:

subquery = '('+', '.join(str(x) for x in IDWorksUpdate)+')'
query = '''UPDATE [DBNAME].[dbo].[WORKS] SET NAMEDOCUMENT = \'/{}/{}/{}/\'+NAMEDOCUMENT WHERE IDWORK IN {}'''.format(year,month,idbatch,subquery)

newConn = pymssql.connect(server='localhost', database='DBNAME')
newCursor = newConn.cursor()

try:
    newCursor.execute(query)
    newConn.commit()
except:
    newConn.rollback()
    log.write('Error on updating documents names in database of works {}/{} of batch {}'.format(year,month,idbatch))
finally:
    newCursor.close()
    del newCursor
    newConn.close() 

今天早上我看到只有几批更新查询在数据库中执行失败,即使文档已正确移动到子目录中也是如此。 该批次有超过 55000 个文档要移动,所以 IDWorksUpdate 可能溢出,它有助于创建最终的更新查询?我认为 55000 并不是一个很大的整数列表。问题在于,在 PYMSSQL 中,我们一次不能有多个连接/游标到同一个数据库,因此我无法在移动相应文件时更新记录。所以我想创建一个文档被正确移动的作品的 PK 列表,最后用新的连接/光标更新它们。可能发生了什么?我做错了吗?

更新

我刚刚编写了一个简单的脚本来重现将要执行以更新记录的查询,这是我从 SQL Server 得到的错误:

查询处理器用尽了内部资源,无法生成查询计划。这是一个罕见的事件,仅适用于极其复杂的查询或引用大量表或分区的查询。请简化查询。如果您认为自己错误地收到了此消息,请联系客户支持服务以获取更多信息。

这是查询:

UPDATE [DBNAME].[dbo].[WORKS] SET NAMEDOCUMENT = '/2016/12/1484/'+NAMEDOCUMENT WHERE IDWORK IN (list of 55157 PKs)

事实上,这张表非常大(大约有 1400 万条记录)。但我需要该 PK 列表,因为只有文档已正确处理和移动的任务才能更新。我不能简单地运行:

UPDATE [DBNAME].[dbo].[WORKS] SET NAMEDOCUMENT = '/2016/12/1484/'+NAMEDOCUMENT WHERE YEAR(DATETIMEWORK)=2016 and 
MONTH(DATETIMEWORK)=12 and IDBATCH=1484

这是因为我们的服务器受到了加密锁的攻击,我必须只处理和移动仍然存在的文件,等待其他文件被释放。 我应该将这些字符串拆分为子列表吗?怎么样?

更新 2

似乎以下可能是一个解决方案:我将 PK 列表拆分为 10000 个块(一个完全实验性的数字),然后我执行与块一样多的查询,每个块都有一个块作为子查询。

def updateDB(listID, y, m, b, log):

newConn = pymssql.connect(server='localhost', database='DBNAME')
newCursor = newConn.cursor()

if len(listID) <= 10000:

    subquery = '('+', '.join(str(x) for x in listID)+')'
    query = '''UPDATE [DBNAME].[dbo].[WORKS] SET NAMEDOCUMENT= \'/{}/{}/{}/\'+NAMEDOCUMENT WHERE IDWORKIN {}'''.format(y,m,b,subquery)

    try:
        newCursor.execute(query)
        newConn.commit()
    except:
        newConn.rollback()
        log.write('...')
        log.write('\n\n')
    finally:
        newCursor.close()
        del newCursor
        newConn.close()   
else:
    chunksPK = [listID[i:i + 10000] for i in xrange(0, len(listID), 10000)]

    for sublistPK in chunksPK:

        subquery = '('+', '.join(str(x) for x in sublistPK)+')'
        query = '''UPDATE [DBNAME].[dbo].[WORKS] SET NAMEDOCUMENT= \'/{}/{}/{}/\'+NAMEDOCUMENT WHERE IDWORK IN {}'''.format(y,m,b,subquery)

        try:
            newCursor.execute(query)
            newConn.commit()
        except:
            newConn.rollback()
            log.write('Could not execute partial {}'.format(query))
            log.write('\n\n')

    newCursor.close()
    del newCursor
    newConn.close()

这可能是一个好的/安全的解决方案吗?

【问题讨论】:

    标签: python sql sql-server batch-processing pymssql


    【解决方案1】:

    如 MSDN 文档中所述

    IN (Transact-SQL)

    在括号内显式包含大量值(以逗号分隔的数千个值),在 IN 子句中会消耗资源并返回错误 8623 或 8632。要解决此问题,请将项目存储在 IN 中在表中列出,并在 IN 子句中使用 SELECT 子查询。

    (您引用的错误消息是错误 8623。)

    将 IN 列表值放入临时表中,然后使用

    ... WHERE IDWORK IN (SELECT keyValue FROM #inListTable)
    

    让我觉得比你描述的“分块”方法更直接。

    【讨论】:

    • 谢谢你的朋友,这正是我需要知道的确认。无论如何,恐怕我对临时表一无所知。或者更好的是,我的意思是:它们是我的 SQL Server 数据库中我应该用作临时表的真正通用表,还是它们是保存在内存中的不同对象或还要别的吗?我该如何使用它们?
    • 此外,还有一个问题是,在 PYMSSQL 中,我们一次只能与一个游标连接到同一个数据库。在我的程序中,我使用游标来获取我需要更新的记录,当我阅读时,使用 fetchone() 方法我不能让另一个游标在另一个表中执行 INSERT 查询...
    • 我相信你是正确的:对于给定的连接,一次只处理一个光标。但是我很确定 pymssql 将支持从同一个程序到同一个 SQL Server 上的同一个数据库的两个单独的连接,因此例如程序可以在 cnxn1 上读取并在 cnxn2 上写入。
    • 我已经尝试管理两个连接,但脚本几乎立即阻塞。也许我犯了某种错误。如果你确定的话,我会在下周一一到办公室就尝试写一个简单的脚本来测试它。我会告诉你。目前非常感谢您的友好解释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-10
    • 1970-01-01
    相关资源
    最近更新 更多