【问题标题】:Python Connection to MariaDB server closes about every 24 hours与 MariaDB 服务器的 Python 连接大约每 24 小时关闭一次
【发布时间】:2020-05-04 10:05:57
【问题描述】:

我正在为我的公司创建一个产品来跟踪附有 RFID 标签的物品,他们在信息亭刷卡,它已记录在数据库中,很简单。

数据库实际上托管在信息亭上(我知道这不是最佳实践),并且在信息亭上运行的主程序是带有 GuiZero 的 python 前端。该信息亭通过 Raspberry Pi 运行 Raspbian Desktop。

一切运行良好,但(大约)程序启动 24 小时后,它就像正常运行一样,直到向服务器发送数据库查询。 python-mariadb-connector 回复:2055: Lost connection to MySQL server at '127.0.0.1:3306', system error: 32 Broken pipe,我已经解决这个问题大约一个月了,现在我确定什么可以解决它。

我尝试在发生这种情况时从 python 前端简单地更新连接,但这也不起作用。唯一能解决这个问题的方法是重新启动程序,用户不能相信它会弄清楚如何去做。

需要注意的一点是,一切都完美运行,直到达到约 24 小时标记。

有什么帮助吗?

依赖关系:

Python 3.7
(pip) mysql-connector v2.2.9
MariaDB: 10.3.17-MariaDB-0+deb10ul Raspbian 10
Raspbian GNU/Linux v10

超时变量

MariaDB [locker]> show variables like "%timeout";
+---------------------------------------+-------+
| Variable_name                         | Value |
+---------------------------------------+-------+
| connect_timeout                       | 10    |
| delayed_insert_timeout                | 300   |
| idle_readonly_transaction_timeout     | 0     |
| idle_transaction_timeout              | 0     |
| idle_write_transaction_timeout        | 0     |
| innodb_flush_log_at_timeout           | 1     |
| innodb_lock_wait_timeout              | 50    |
| innodb_rollback_on_timeout            | OFF   |
| interactive_timeout                   | 28800 |
| lock_wait_timeout                     | 86400 |
| net_read_timeout                      | 30    |
| net_write_timeout                     | 60    |
| rpl_semi_sync_master_timeout          | 10000 |
| rpl_semi_sync_slave_kill_conn_timeout | 5     |
| slave_net_timeout                     | 60    |
| thread_pool_idle_timeout              | 60    |
| wait_timeout                          | 28800 |
+---------------------------------------+-------+

这是代码,第 166 行是处理错误的地方。 (并不是说我的处理有任何作用) Ctrl+F 表示“aghiulg”以到达该行。

#!/usr/bin/python3.7
from datetime import datetime
from sys import exit

# Classes
class User:
    def __init__ (self, rfid, name):
        self.name = name
        self.rfid = rfid

class Key:
    def __init__ (self, rfid, kid, prop, loc):
        self.rfid = rfid
        self.kid = kid
        self.prop = prop.split("/")
        self.loc = loc

    def toString (self):
        return "[{}] {}".format(self.kid, "/".join(self.prop[:3]))

# Slack
import slack

slackBotToken = "OBFUSCATEDFORSTACKOVERFLOW"
slackClient = slack.WebClient(slackBotToken)
slackID = None

def getTimeString ():
    now = datetime.now()
    return now.strftime("%m/%d/%Y %H:%M:%S")

def log (s):
    time = getTimeString()

    # stdout
    print("[{}] {}".format(time, s))


    # slack
    slackErr = False
    try:
        slackClient.chat_postMessage(channel = "#keys", text = s)
    except concurrent.futures._base.TimeoutError:
        slackErr = True

    # file
    with open("/home/pi/key-locker/log.txt", "a+") as f:
        f.write("[{}] {}\n".format(time, s))
        if slackErr:
            f.write("Couldn't write that to Slack, oops.")

def xlog (s):
    try:
        slackClient.chat_postMessage(channel = "OBFUSCATED", text = s)
        return True
    except concurrent.futures._base.TimeoutError:
        return False

# guizero
from guizero import App, Text, TextBox, info

app = App(title = "Key Locker")
app.tk.attributes("-fullscreen", True)
REFOCUS_TIMER = 500
USER_TIMEOUT = 60 * 1000

# MariaDB
import mysql.connector as mariadb

def connectDB ():
    xlog("Reconnecting to database...")
    return mariadb.connect(user="OBFUSCATED", password="OBFUSCATED", database="OBFUSCATED")

# mdb = mariadb.connect(user="OBFUSCATED", password="OBFUSCATED", database="OBFUSCATED")
mdb = connectDB()
cursor = mdb.cursor(buffered = True)

def focusUIN (uin = None):
    if uin:
        uin.focus()

def onTextBoxKey (e, f = None):
    global uidBox, kidBox, bigText, underText, currentUser, currentKey, escapeKeys

    if len(e.key) == 0:
        return

    if f == uidBox and ord(e.key) == 13:
        uid = uidBox.value
        if uid.lower() in escapeKeys:
            exit(0)
        else:
            currentUser = codeToUser(uid)
            if currentUser:
                uidBox.cancel(focusUIN)
                uidBox.disable()

                kidBox.enable()
                kidBox.repeat(REFOCUS_TIMER, focusUIN, args = [kidBox])
                kidBox.when_key_pressed = lambda e: onTextBoxKey(e, f = kidBox)

                bigText.value = "Scan Key"
                underText.value = "Welcome, {}.".format(currentUser.name)

                app.after(USER_TIMEOUT, restart, args = ["User Timeout"])
            else:
                restart("That user doesn't exist.")
    elif f == kidBox and ord(e.key) == 13:
        kid = kidBox.value
        if kid.lower() in escapeKeys:
            exit(0)
        else:
            app.cancel(restart)

            currentKey = codeToKey(kid)
            if currentKey:
                kidBox.cancel(focusUIN)
                kidBox.disable()

                inLocker = (currentKey.loc.lower() == "locker")
                success = (checkout(currentUser, currentKey) if inLocker else checkin(currentUser, currentKey))
                if success:
                    restart("{} checked {} the {} keys.".format(currentUser.name, "out" if inLocker else "in", currentKey.toString()))
                else:
                    restart("System error, try again.")
            else:
                restart("That key doesn't exist.")

def restart (subText = ". . ."):
    global uidBox, kidBox, bigText, underText

    uidBox.value = ""
    uidBox.enable()
    uidBox.cancel(focusUIN)
    uidBox.repeat(REFOCUS_TIMER, focusUIN, args = [uidBox])
    uidBox.focus()

    kidBox.value = ""
    kidBox.cancel(focusUIN)
    kidBox.disable()

    bigText.value = "Scan Badge"
    underText.value = subText

# App
escapeKeys = "letmeout pleaseijustwanttoseemywifeandkidsagain".split(" ")
currentUser = None
currentKey = None

def codeToUser (uid = None):
    xlog("Trying to find user by RFID...")
    if uid:
        try:
            cursor.execute("select Name from user where RFID = '{}';".format(uid))
            names = []
            for n in cursor:
                names.append(n)
            if len(names) != 1:
                xlog("Ran the function, and literally got no matches for that user rfid.")
                xlog("Restarting...")
                exit(0)
                return None
            else:
                xlog("Found user: {}".format(names[0][0]))
                return User(uid, names[0][0])
        except mariadb.Error as e: # aghiulg
            xlog("Database error trying to find the user.\n{}".format(e))
            # fatalError(e)
            return None
    else:
        xlog("They didn't give me an RFID.")
        return None

def codeToKey (kid = None):
    if kid:
        try:
            cursor.execute("select KeyringID, Properties, Location from keyring where RFID = {};".format(kid))
            keys = []
            for k in cursor:
                keys.append(k)
            if len(keys) != 1:
                return None
            else:
                keys = keys[0]
                return Key(kid, keys[0], keys[1], keys[2])
        except mariadb.Error as e:
            # fatalError(e)
            return None
    else:
        return None

def checkout(user, key):
    global mdb

    log("Checking out k{} ({}) to {}.".format(key.kid, ", ".join(key.prop), user.name))
    try:
        cursor.execute("update keyring set Location = '{}' where KeyringID = {}".format(user.name, key.kid))
        mdb.commit()
        return True
    except mariadb.Error as e:
        # fatalError(e)
        mdb = connectDB()
        return False

def checkin (user, key):
    global mdb

    log("Checking in k{} ({}) from {}.".format(key.kid, ", ".join(key.prop), user.name))
    try:
        cursor.execute("update keyring set Location = 'locker' where KeyringID = {}".format(key.kid))
        mdb.commit()
        return True
    except mariadb.Error as e:
        # fatalError(e)
        mdb = connectDB()
        return False

def fatalError (e):
    log("Error: {}".format(e))
    log("Fatal error encountered, Key Locker turning off. Next user must open it by double clicking the desktop link.")
    exit()

# First Run
if __name__ == "__main__":
    bigText = Text(app, "Scan Badge", size = 40, color = "black")
    underText = Text(app, ". . .", size = 15, color = "black")

    uidBox = TextBox(app)
    uidBox.repeat(REFOCUS_TIMER, focusUIN, args = [uidBox])
    uidBox.when_key_pressed = lambda e: onTextBoxKey(e, f = uidBox)

    kidBox = TextBox(app, enabled = False)

    auditText = Text(app, size = 10, color = "black")

    app.display()

【问题讨论】:

  • 请提供SHOW VARIABLES LIKE '%timeout';
  • 是否始终使用单个连接?还是代码断开了?
  • @RickJames 我已经更新了帖子以显示这些变量。使用单个连接,它会在错误发生后尝试重新打开连接,但这没有帮助。

标签: python mysql mariadb


【解决方案1】:

interactive_timeout 的默认值为 28800 secs = 8 hrs,这意味着在 8 小时不活动后,服务器将自动关闭连接。

我会尝试增加这个值并检查断开是否仍然发生。

根据 PEP-249,一旦连接句柄变为无效,所有游标对象都会变为无效。由于 MySQL 连接器/Python 没有自动重新连接选项并且不允许将连接对象重新分配给游标,因此您必须在重用游标之前重新创建游标。

【讨论】:

    【解决方案2】:

    在出现数据库错误时,您正在创建新连接,但不是新游标。现有游标仍绑定到现在已关闭的初始连接,这就是您不断收到错误的原因。

    每当您创建新连接时重新初始化光标,您应该会很好。

    【讨论】:

    • 这很好,我没有意识到这一点。我会编辑它,明天我们会看看这是否是问题,
    • 那么,当完成一个动作后,关闭光标和连接?
    • @XavierHorn 运气好吗?
    • @kristaps 刚才试了一下,不行,还是一样的错误。
    • @XavierHorn 这个 4 年前的问题可能对您的团队很有价值。 stackoverflow.com/questions/33228743/… - 你检查过 python.ini 超时值吗 - 如果 python 超时小于 mysql 超时,python 可能放弃得太早了,以秒为单位。
    【解决方案3】:

    由于您使用的是本地托管的 MariaDB 实例,请尝试使用 Unix 套接字而不是 TCP/IP 连接到数据库。如果任何基于主机的防火墙或其他网络怪异与您的 TCP 堆栈发生干扰,您基本上可以通过这种方式绕过它。

    MariaDB 的套接字通常位于/var/run/mysqld/mysqld.sock(除非您另有配置)。验证该路径是否存在套接字(或者追踪它的位置),

    > show variables like 'socket';
    +---------------+-----------------------------+
    | Variable_name | Value                       |
    +---------------+-----------------------------+
    | socket        | /var/run/mysqld/mysqld.sock |
    +---------------+-----------------------------+
    

    然后将您的连接命令设置为这样的:

    mariadb.connect(user="Donuts", password="Candy", database="Cake", unix_socket="/var/run/mysqld/mysqld.sock")
    

    参考:https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html

    【讨论】:

      猜你喜欢
      • 2020-09-05
      • 2017-06-24
      • 2019-08-17
      • 2019-08-30
      • 2011-10-31
      • 1970-01-01
      • 2018-12-11
      • 2019-12-12
      • 1970-01-01
      相关资源
      最近更新 更多