【问题标题】:PyMySQL throws 'BrokenPipeError' after making frequent readsPyMySQL 在频繁读取后抛出“BrokenPipeError”
【发布时间】:2017-05-29 11:41:45
【问题描述】:

我编写了一个脚本来帮助我使用数据库。具体来说,我正在尝试处理磁盘上的文件并将这项工作的结果添加到我的数据库中。我复制了下面的代码,但删除了大部分与我的数据库无关的逻辑,以尽量保持这个问题的广泛性和帮助性。

我使用代码对文件进行操作并将结果添加到数据库中,覆盖与我正在处理的文件具有相同标识符的所有文件。后来,我修改了脚本以忽略已经添加到数据库中的文档,现在每次运行它都会出错:

pymysql.err.OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")

似乎服务器拒绝了请求,可能是因为我的代码写得不好?我注意到错误总是发生在文件列表中的同一位置,这不会改变。如果我重新运行代码,将文件列表替换为仅包含程序崩溃的文件的列表,则它可以正常工作。这让我觉得在发出一定数量的请求后,数据库才刚刚触底。

我在 OS X 上使用 Python 3 和 MySQL Community Edition 14.14 版。

代码(去掉与数据库无关的东西):

import pymysql

# Stars for user-specific stuff
connection = pymysql.connect(host='localhost',
                             user='root',
                             password='*******',
                             db='*******',
                             use_unicode=True, 
                             charset="utf8mb4",
                             )
cursor = connection.cursor()

f_arr = # An array of all of my data objects

def convertF(file_):
    # General layout: Try to work with input and add it the result to DB. The work can raise an exception
    # If the record already exists in the DB, ignore it
    # Elif the work was already done and the result is on disk, put it on the database
    # Else do the work and put it on the database - this can raise exceptions
    # Except: Try another way to do the work, and put the result in the database. This can raise an error
    # Second (nested) except: Add the record to the database with indicator that the work failed

    # This worked before I added the initial check on whether or not the record already exists in the database. Now, for some reason, I get the error:
    # pymysql.err.OperationalError: (2006, "MySQL server has gone away (BrokenPipeError(32, 'Broken pipe'))")

    # I'm pretty sure that I have written code to work poorly with the database. I had hoped to finish this task quickly instead of efficiently.
    try:
        # Find record in DB, if text exists just ignore the record
        rc = cursor.execute("SELECT LENGTH(text) FROM table WHERE name = '{0}'".format(file_["name"]))
        length = cursor.fetchall()[0][0] # Gets the length
        if length != None and length > 4:
            pass
        elif ( "work already finished on disk" ): 
            # get "result_text" from disk
            cmd = "UPDATE table SET text = %s, hascontent = 1 WHERE name = %s"
            cursor.execute(cmd, ( pymysql.escape_string(result_text), file_["name"] ))
            connection.commit()
        else:
            # do work to get result_text
            cmd = "UPDATE table SET text = %s, hascontent = 1 WHERE name = %s"
            cursor.execute(cmd, ( pymysql.escape_string(result_text), file_["name"] ))
            connection.commit()
    except:
        try: 
            # Alternate method of work to get result_text
            cmd = "UPDATE table SET text = %s, hascontent = 1 WHERE name = %s"
            cursor.execute(cmd, ( pymysql.escape_string(result_text), file_["name"] ))
            connection.commit()
        except:
            # Since the job can't be done, tell the database
            cmd = "UPDATE table SET text = %s, hascontent = 0 WHERE name = %s"
            cursor.execute(cmd, ( "NO CONTENT", file_["name"]) )
            connection.commit()

for file in f_arr:
    convertF(file)

【问题讨论】:

    标签: python mysql python-3.x pymysql


    【解决方案1】:

    Mysql Server 消失了

    此问题在http://dev.mysql.com/doc/refman/5.7/en/gone-away.html 中进行了广泛描述,通常的原因是服务器因任何原因断开连接,通常的补救措施是重试查询或重新连接并重试。

    但为什么这会破坏您的代码是因为您编写代码的方式。见下文

    可能是因为我的代码写得不好?

    既然你问了。

    rc = cursor.execute("SELECT LENGTH(text) FROM table WHERE name = '{0}'".format(file_["name"]))
    

    这是一个坏习惯。手动显式警告您不要这样做以避免 SQL 注入。正确的做法是

     rc = cursor.execute("SELECT LENGTH(text) FROM table WHERE name = %s", (file_["name"],))
    

    上述代码的第二个问题是,在尝试更新值之前,您不需要检查值是否存在。您可以删除上面的行,如果 else 则关联并直接跳转到更新。此外,我们的elifelse 似乎做同样的事情。所以你的代码可以是

    try:
            cmd = "UPDATE table SET text = %s, hascontent = 1 WHERE name = %s"
            cursor.execute(cmd, ( pymysql.escape_string(result_text), file_["name"] ))
            connection.commit()
    except:  # <-- next problem.
    

    我们来到下一个问题。永远不要捕获这样的通用异常。您应该始终捕获特定异常,例如 TypeError、AttributeError 等。当捕获通用异常是不可避免的时,您至少应该记录它。

    例如,您可以在此处捕获连接错误并尝试重新连接到数据库。那么当你的服务器消失问题发生时,代码不会停止执行。

    【讨论】:

    • 谢谢,我会解决这些问题的。 if 循环的第一部分是必要的,因为我想避免重新做“工作”来获取数据库的内容,而不是确保记录在更新之前已经存在。
    • 不明白您的评论
    • 我的意思是 if 循环不仅用于在我更新之前检查值是否存在 - 我还有其他必要的逻辑,当我试图摆脱时我清理了这些逻辑对问题不重要的代码。对此感到抱歉。
    • 我已经更新了我的代码,将连接对象作为 convertF 方法的参数,然后在该方法中创建并关闭光标。在 for 循环中,我将 convertF 包含在 try 块中,以便我可以捕获 OperationalError,关闭旧连接,建立新连接,然后重新尝试 convertF。当我重新尝试管道中断的 SQL 语句时,它再次中断。会不会是声明本身?有任何想法吗?另外,我认为删除我的大部分代码让你很难回答这个问题,所以我把整个事情都放在了here
    • 你能把它作为一个新问题发布吗
    【解决方案2】:

    当我尝试通过减少我想在一个命令中插入的行数来进行批量插入时,我已经解决了同样的错误。

    即使批量插入的最大行数要高得多,我也遇到了这种错误。

    【讨论】:

    • 使用pd.to_sql() 函数并传递参数chunksize 是一种对我有用的方法。 chunksize : int, optional Rows 将被分批写入。默认情况下,一次写入所有行。
    猜你喜欢
    • 2018-12-28
    • 2013-07-09
    • 1970-01-01
    • 1970-01-01
    • 2013-02-14
    • 1970-01-01
    • 1970-01-01
    • 2020-03-04
    • 2013-08-08
    相关资源
    最近更新 更多