PyMySQL 插入数据重复?Commit后依然失败原因解析
2024-12-30 12:16:57
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()
步骤:
- 引入需要的pymysql的模块和错误类型.
- 创建一个
insert_data
函数用来封装执行sql和commit. - 在try语句中,使用
cur.execute()
执行SQL查询,使用connection.commit()
方法提交更改。 - 在
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();
步骤:
- 修改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
以及内部的错误代码。 选择何种方式,取决于你的具体需求。