返回

PyMySQL 插入数据重复?Commit后依然失败原因解析

mysql

PyMySQL 插入数据重复:使用 commit 后仍然失败?

当使用 PyMySQL 与 MySQL 数据库交互时,经常会遇到一种令人困惑的场景:即使已经使用了 commit() 方法,尝试插入重复数据时仍然会引发错误。这个问题的根源在于数据库的完整性约束以及PyMySQL的错误处理机制。

理解 IntegrityError 及其背后的 ER.DUP_ENTRY

IntegrityError 是 PyMySQL 中用于处理违反数据库完整性约束的错误类型。其中,ER.DUP_ENTRY 是一个特定的错误代码,它代表试图插入的数据违反了唯一约束(Unique Key Constraint)。PyMySQL 会将多种错误归类到 IntegrityError 中,不仅仅是 ER.DUP_ENTRY, 这使得直接捕获特定的 ER.DUP_ENTRY 变得稍微复杂。

解决方案一:针对特定错误码进行捕获

要精确处理 ER.DUP_ENTRY 错误,可以通过检查 IntegrityError 异常对象的 args 属性中的错误代码来实现。

原理: PyMySQL 会将MySQL服务器返回的错误代码放入异常的 args 属性中。args[0] 通常会包含 MySQL 的错误代码。我们可以检查该值是否与 ER.DUP_ENTRY 的错误码相等。

代码示例:

import pymysql
from pymysql.err import IntegrityError
from pymysql.constants import ER

def insert_data(conn, query, values):
  cur = conn.cursor()
  try:
    cur.execute(query, values)
    conn.commit() # 注意:这里也使用了commit()
    print("数据插入成功")
  except IntegrityError as e:
      if e.args[0] == ER.DUP_ENTRY:
         handle_duplicate_entry(e, query, values)
      else:
          handle_other_integrity_error(e,query, values)
  except Exception as e:
        handle_unknown_error(e, query, values)


def handle_duplicate_entry(e, query, values):
  print(f"捕获重复数据异常: {e}")
  print(f"当前执行的SQL语句:{query} with values:{values}")
  # 这里编写针对重复数据的处理逻辑,例如更新已存在的数据,记录日志,等等
def handle_other_integrity_error(e, query, values):
   print(f"捕获其他违反数据完整性错误:{e}")
   print(f"当前执行的SQL语句:{query} with values:{values}")
   # 这里可以记录日志或者发出报警

def handle_unknown_error(e,query, values):
     print(f"捕获未知异常: {e}")
     print(f"当前执行的SQL语句:{query} with values:{values}")
     # 处理未知异常的逻辑

# 示例
connection = pymysql.connect(host='your_host', user='your_user', password='your_password', db='your_db', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor)

sql_insert_duplicate="""
    INSERT INTO `users` (
        `username`,
        `email`
        )
        VALUES(%s,%s)
"""

insert_values = ("test_user","test@test.com")

try:
   insert_data(connection, sql_insert_duplicate, insert_values) # 模拟重复数据插入
   insert_data(connection,sql_insert_duplicate, insert_values) # 再插入一遍模拟重复数据
except Exception as ex:
    print("发生其他错误:{}".format(ex))
finally:
     connection.close()

步骤:

  1. 引入需要的pymysql的模块和错误类型.
  2. 创建一个 insert_data 函数用来封装执行sql和commit.
  3. 在try语句中,使用 cur.execute() 执行SQL查询,使用connection.commit() 方法提交更改。
  4. except IntegrityError as e 块中,使用条件语句 if e.args[0] == ER.DUP_ENTRY 判断是否为 ER.DUP_ENTRY, 并针对这个错误使用handle_duplicate_entry(e) 来处理。对于其他违反完整性错误的逻辑交给handle_other_integrity_error(e) 函数处理。对于未知的错误则用 handle_unknown_error(e)来处理。

注意:

  • 需要事先知道 ER.DUP_ENTRY 对应的错误码.
  • 直接依赖 args 索引可能在PyMySQL版本升级后存在风险。更安全的方法是从 pymysql.constants import ER导入错误代码, 就像上述例子中那样,使用 ER.DUP_ENTRY, 提升代码的可读性.

解决方案二:通过 SQL ON DUPLICATE KEY UPDATE

当插入重复数据时,有时希望更新已存在的数据,而不是直接报错。ON DUPLICATE KEY UPDATE 子句 可以让MySQL实现插入或者更新的原子操作,并且避免 ER.DUP_ENTRY 错误。

原理: ON DUPLICATE KEY UPDATE 子句与 INSERT 语句一同使用。如果新插入的数据因为违反唯一键约束而导致冲突,数据库不会抛出异常,而是执行 UPDATE 子句中的更新语句。

SQL示例:

INSERT INTO users (username, email, update_time)
VALUES ('test_user', 'test@test.com', NOW())
ON DUPLICATE KEY UPDATE
    email = VALUES(email),
    update_time = NOW();

步骤:

  1. 修改sql语句增加ON DUPLICATE KEY UPDATE 子句,当发生唯一约束时执行相应的update操作.

注意:

  • 如果更新的字段没有设置NOT NULL约束,可以将字段的值设置成 VALUES(field_name), 这时候数据库会尝试用新的值来更新这些列. 如果想要保留原本的的值, 则需要根据具体的业务来修改, 可以使用类似IFNULL 的函数来更新。
  • 需要确保执行更新的字段已经存在于表结构中.
  • ON DUPLICATE KEY UPDATE 可以用于解决部分业务场景, 但不是所有的需求都适用。

安全建议

  • 使用参数化查询或预处理语句, 避免SQL注入漏洞. 例如上面的示例代码都使用 %s 进行占位, 并通过cur.execute(query, values) 传参,这样更安全.
  • 记录错误日志,可以跟踪应用行为,也方便问题排查。
  • 严格校验用户输入, 减少插入脏数据的风险。

处理PyMySQL中的重复数据插入问题, 理解错误类型及其具体的错误码,可以使用 ON DUPLICATE KEY UPDATE ,也可以直接针对性的捕捉IntegrityError 以及内部的错误代码。 选择何种方式,取决于你的具体需求。