MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python处理MySQL数据库常见错误

2024-04-105.8k 阅读

连接数据库错误

1. 驱动未安装

在Python中与MySQL数据库交互,常用的驱动有mysql - connector - pythonpymysql。如果没有安装相应驱动,运行代码时会直接报错。例如,使用mysql - connector - python驱动:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)

当未安装mysql - connector - python时,运行上述代码会报ModuleNotFoundError: No module named'mysql.connector'错误。

解决方法很简单,使用pip安装即可。对于mysql - connector - python,运行pip install mysql - connector - python;对于pymysql,运行pip install pymysql

2. 连接参数错误

  • 主机名错误:如果将host参数设置错误,比如将localhost写成了localhot,代码如下:
import mysql.connector

mydb = mysql.connector.connect(
    host="localhot",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)

会报mysql.connector.errors.InterfaceError: 2003: Can't connect to MySQL server on 'localhot:3306' (11001 getaddrinfo failed)错误。这是因为无法解析错误的主机名,从而无法建立连接。

  • 端口号错误:MySQL默认端口是3306,如果数据库配置了其他端口,而连接时未指定正确端口,也会连接失败。例如,数据库实际端口是3307,但连接代码仍使用默认端口:
import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase",
    port = 3306
)

会报mysql.connector.errors.InterfaceError: 2003: Can't connect to MySQL server on 'localhost:3306' (10061 No connection could be made because the target machine actively refused it)错误,因为实际数据库监听在3307端口,3306端口无服务响应。

解决端口问题,需要确认数据库实际使用端口,并在连接时正确指定,如port = 3307

  • 用户名或密码错误:当userpassword参数填写错误时:
import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="wrongusername",
    password="wrongpassword",
    database="yourdatabase"
)

会报mysql.connector.errors.ProgrammingError: 1045 (28000): Access denied for user 'wrongusername'@'localhost' (using password: YES)错误,明确提示用户名或密码错误导致访问被拒绝。

解决办法就是仔细核对用户名和密码,确保与数据库配置一致。

  • 数据库名错误:若指定的database不存在:
import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="nonexistdatabase"
)

会报mysql.connector.errors.ProgrammingError: 1049 (42000): Unknown database 'nonexistdatabase'错误,表明指定的数据库不存在。

解决此问题,需要确认数据库名是否拼写正确,或者提前创建好所需的数据库。

SQL语句语法错误

1. 基本语法错误

在Python中执行SQL语句时,SQL语句本身的语法错误很常见。例如,在插入数据时,少写了一个列名:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "INSERT INTO customers (name) VALUES (%s)"
val = ("John",)
mycursor.execute(sql, val)

mydb.commit()

print(mycursor.rowcount, "record inserted.")

假设customers表有nameaddress两个必填列,上述代码就会报mysql.connector.errors.ProgrammingError: 1136 (21S01): Column count doesn't match value count at row 1错误,因为提供的值的数量与列的数量不匹配。

正确的插入语句应该是:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "INSERT INTO customers (name, address) VALUES (%s, %s)"
val = ("John", "Highway 21")
mycursor.execute(sql, val)

mydb.commit()

print(mycursor.rowcount, "record inserted.")

在编写SQL语句时,要严格按照数据库表结构来确定列名和值的对应关系。

2. 关键字拼写错误

SQL有很多关键字,如果拼写错误,也会导致语法错误。例如,将SELECT写成SELCET

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "SELCET * FROM customers"
mycursor.execute(sql)

myresult = mycursor.fetchall()

for x in myresult:
    print(x)

会报mysql.connector.errors.ProgrammingError: 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELCET * FROM customers' at line 1错误,提示SQL语法错误,仔细检查会发现是关键字拼写错误。

这种错误需要开发者对SQL关键字有清晰的记忆,编写代码时仔细核对。

3. 引号使用不当

在SQL语句中,字符串值需要用引号括起来。如果引号使用不当,会导致语法错误。例如:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "UPDATE customers SET address = Highway 21 WHERE name = 'John'"
mycursor.execute(sql)

mydb.commit()

print(mycursor.rowcount, "record(s) affected")

上述代码中Highway 21未用引号括起来,会报mysql.connector.errors.ProgrammingError: 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Highway 21 WHERE name = 'John'' at line 1错误。

正确的写法是:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "UPDATE customers SET address = 'Highway 21' WHERE name = 'John'"
mycursor.execute(sql)

mydb.commit()

print(mycursor.rowcount, "record(s) affected")

确保字符串值都用引号正确括起来,单引号和双引号都可以,但要注意配对使用。

数据类型不匹配错误

1. 插入数据类型不匹配

当向数据库表中插入数据时,如果提供的数据类型与表中列的数据类型不匹配,就会出现错误。例如,表中有一个age列,数据类型是INT,但插入时提供了字符串:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "INSERT INTO users (name, age) VALUES (%s, %s)"
val = ("Alice", "twenty")
mycursor.execute(sql, val)

mydb.commit()

print(mycursor.rowcount, "record inserted.")

会报mysql.connector.errors.ProgrammingError: 1366 (HY000): Incorrect integer value: 'twenty' for column 'age' at row 1错误,因为无法将字符串twenty转换为INT类型。

正确的做法是提供合适的整数值:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "INSERT INTO users (name, age) VALUES (%s, %s)"
val = ("Alice", 20)
mycursor.execute(sql, val)

mydb.commit()

print(mycursor.rowcount, "record inserted.")

插入数据前,要确保数据类型与表结构定义一致。

2. 查询结果数据类型处理不当

在查询数据时,从数据库获取的数据类型需要正确处理。例如,从一个DECIMAL类型列查询数据,并进行数值计算:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "SELECT price FROM products"
mycursor.execute(sql)

myresult = mycursor.fetchall()

total = 0
for x in myresult:
    total = total + x[0]

print("Total price:", total)

如果price列是DECIMAL类型,上述代码可能会报TypeError: unsupported operand type(s) for +: 'int' and 'decimal.Decimal'错误,因为fetchall()返回的DECIMAL类型数据不能直接与int类型相加。

需要将获取的数据转换为合适的类型,比如float

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "SELECT price FROM products"
mycursor.execute(sql)

myresult = mycursor.fetchall()

total = 0
for x in myresult:
    total = total + float(x[0])

print("Total price:", total)

了解查询结果的数据类型,并进行恰当的类型转换,能避免此类错误。

事务处理错误

1. 未正确开启事务

在Python中使用MySQL事务,如果没有正确开启事务,可能导致数据不一致等问题。例如,在一系列操作中,本应作为一个事务处理,但没有开启事务:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

# 未开启事务
sql1 = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"
mycursor.execute(sql1)

sql2 = "UPDATE accounts SET balance = balance + 100 WHERE account_id = 2"
mycursor.execute(sql2)

mydb.commit()

如果在执行sql1后,数据库服务器崩溃,而sql2还未执行,就会导致数据不一致,account_id = 1的账户余额减少了,但account_id = 2的账户余额未增加。

正确的做法是开启事务:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

mydb.start_transaction()
try:
    sql1 = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"
    mycursor.execute(sql1)

    sql2 = "UPDATE accounts SET balance = balance + 100 WHERE account_id = 2"
    mycursor.execute(sql2)

    mydb.commit()
except Exception as e:
    mydb.rollback()
    print("Transaction rolled back due to error:", e)

这样,如果在事务执行过程中出现错误,通过rollback()方法回滚事务,保证数据一致性。

2. 事务提交或回滚错误

在事务处理中,提交或回滚操作时机不当也会产生问题。例如,在一个事务中,部分操作成功后,错误地进行了回滚:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

mydb.start_transaction()
try:
    sql1 = "INSERT INTO orders (order_number) VALUES ('12345')"
    mycursor.execute(sql1)

    # 这里错误地进行了回滚
    mydb.rollback()

    sql2 = "INSERT INTO order_items (order_id, product) VALUES (last_insert_id(), 'Product A')"
    mycursor.execute(sql2)

    mydb.commit()
except Exception as e:
    mydb.rollback()
    print("Transaction rolled back due to error:", e)

由于在插入orders表后错误地回滚了事务,order_items表插入操作实际上是在一个未开启事务的环境下执行(因为之前事务已回滚),这可能导致数据完整性问题。

要确保提交和回滚操作在正确的逻辑位置执行,保证事务的原子性。

资源管理错误

1. 未关闭游标

在Python中使用游标执行SQL操作后,如果不关闭游标,可能会导致资源泄漏。例如:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "SELECT * FROM employees"
mycursor.execute(sql)

myresult = mycursor.fetchall()

for x in myresult:
    print(x)
# 未关闭游标

虽然程序在运行时可能不会立即报错,但随着时间推移和多次操作,未关闭的游标会占用系统资源,可能导致程序性能下降甚至崩溃。

正确的做法是在使用完游标后关闭它:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "SELECT * FROM employees"
mycursor.execute(sql)

myresult = mycursor.fetchall()

for x in myresult:
    print(x)

mycursor.close()

养成及时关闭游标的习惯,能有效管理资源。

2. 未关闭数据库连接

与未关闭游标类似,未关闭数据库连接也会导致资源浪费和潜在的性能问题。例如:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "SELECT * FROM products"
mycursor.execute(sql)

myresult = mycursor.fetchall()

for x in myresult:
    print(x)

mycursor.close()
# 未关闭数据库连接

数据库连接占用服务器资源,如果大量连接未关闭,会导致服务器负载增加,甚至拒绝新的连接请求。

在程序结束时,应该关闭数据库连接:

import mysql.connector

mydb = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor = mydb.cursor()

sql = "SELECT * FROM products"
mycursor.execute(sql)

myresult = mycursor.fetchall()

for x in myresult:
    print(x)

mycursor.close()
mydb.close()

确保数据库连接在不再使用时被正确关闭,维持系统的高效运行。

并发访问错误

1. 脏读

在并发环境下,脏读可能发生。假设两个事务T1T2T1更新了一条数据但未提交,T2读取了这条未提交的数据。例如: 事务T1(Python代码模拟):

import mysql.connector

mydb1 = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor1 = mydb1.cursor()

mydb1.start_transaction()
sql1 = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"
mycursor1.execute(sql1)
# 未提交事务

事务T2(Python代码模拟):

import mysql.connector

mydb2 = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor2 = mydb2.cursor()

sql2 = "SELECT balance FROM accounts WHERE account_id = 1"
mycursor2.execute(sql2)
result = mycursor2.fetchone()
print("Balance read by T2:", result[0])

如果T2读取到了T1未提交的更新结果,就发生了脏读。当T1回滚事务时,T2读取的数据就是无效的。

为了避免脏读,数据库通常使用锁机制或设置事务隔离级别。例如,将事务隔离级别设置为READ COMMITTED

import mysql.connector

mydb2 = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor2 = mydb2.cursor()

mycursor2.execute("SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED")
sql2 = "SELECT balance FROM accounts WHERE account_id = 1"
mycursor2.execute(sql2)
result = mycursor2.fetchone()
print("Balance read by T2:", result[0])

这样T2只能读取已提交的数据,避免了脏读。

2. 不可重复读

不可重复读是指在一个事务内多次读取同一数据时,得到不同的结果。假设事务T1读取了一条数据,然后事务T2更新并提交了这条数据,当T1再次读取时,得到了不同的值。例如: 事务T1(Python代码模拟):

import mysql.connector

mydb1 = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor1 = mydb1.cursor()

mydb1.start_transaction()
sql1 = "SELECT balance FROM accounts WHERE account_id = 1"
mycursor1.execute(sql1)
result1 = mycursor1.fetchone()
print("First read by T1:", result1[0])

# 模拟一些其他操作
import time
time.sleep(5)

sql1 = "SELECT balance FROM accounts WHERE account_id = 1"
mycursor1.execute(sql1)
result2 = mycursor1.fetchone()
print("Second read by T1:", result2[0])
mydb1.commit()

事务T2(Python代码模拟):

import mysql.connector

mydb2 = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor2 = mydb2.cursor()

mydb2.start_transaction()
sql2 = "UPDATE accounts SET balance = balance + 100 WHERE account_id = 1"
mycursor2.execute(sql2)
mydb2.commit()

在上述例子中,如果T1两次读取到的balance值不同,就发生了不可重复读。

可以通过将事务隔离级别设置为REPEATABLE READ来避免不可重复读:

import mysql.connector

mydb1 = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor1 = mydb1.cursor()

mycursor1.execute("SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ")
mydb1.start_transaction()
sql1 = "SELECT balance FROM accounts WHERE account_id = 1"
mycursor1.execute(sql1)
result1 = mycursor1.fetchone()
print("First read by T1:", result1[0])

# 模拟一些其他操作
import time
time.sleep(5)

sql1 = "SELECT balance FROM accounts WHERE account_id = 1"
mycursor1.execute(sql1)
result2 = mycursor1.fetchone()
print("Second read by T1:", result2[0])
mydb1.commit()

REPEATABLE READ隔离级别下,T1在事务内多次读取同一数据时,会得到相同的结果。

3. 幻读

幻读是指在一个事务内,两次相同的查询,第二次查询返回了第一次查询时不存在的行。例如,事务T1查询满足某个条件的记录,然后事务T2插入了满足该条件的新记录,当T1再次查询时,发现多了一些“幻象”行。

事务T1(Python代码模拟):

import mysql.connector

mydb1 = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor1 = mydb1.cursor()

mydb1.start_transaction()
sql1 = "SELECT * FROM products WHERE price > 100"
mycursor1.execute(sql1)
result1 = mycursor1.fetchall()
print("First query result in T1:", len(result1))

# 模拟一些其他操作
import time
time.sleep(5)

sql1 = "SELECT * FROM products WHERE price > 100"
mycursor1.execute(sql1)
result2 = mycursor1.fetchall()
print("Second query result in T1:", len(result2))
mydb1.commit()

事务T2(Python代码模拟):

import mysql.connector

mydb2 = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor2 = mydb2.cursor()

mydb2.start_transaction()
sql2 = "INSERT INTO products (name, price) VALUES ('New Product', 150)"
mycursor2.execute(sql2)
mydb2.commit()

如果T1两次查询结果的记录数不同,就发生了幻读。

可以将事务隔离级别设置为SERIALIZABLE来避免幻读:

import mysql.connector

mydb1 = mysql.connector.connect(
    host="localhost",
    user="yourusername",
    password="yourpassword",
    database="yourdatabase"
)
mycursor1 = mydb1.cursor()

mycursor1.execute("SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE")
mydb1.start_transaction()
sql1 = "SELECT * FROM products WHERE price > 100"
mycursor1.execute(sql1)
result1 = mycursor1.fetchall()
print("First query result in T1:", len(result1))

# 模拟一些其他操作
import time
time.sleep(5)

sql1 = "SELECT * FROM products WHERE price > 100"
mycursor1.execute(sql1)
result2 = mycursor1.fetchall()
print("Second query result in T1:", len(result2))
mydb1.commit()

SERIALIZABLE隔离级别下,事务串行化执行,避免了幻读,但同时也会降低并发性能。

通过对上述各种Python处理MySQL数据库常见错误的分析和示例代码的展示,希望开发者在实际项目中能够更加准确地排查和解决问题,确保数据库操作的稳定和可靠。