Python处理MySQL数据库常见错误
连接数据库错误
1. 驱动未安装
在Python中与MySQL数据库交互,常用的驱动有mysql - connector - python
和pymysql
。如果没有安装相应驱动,运行代码时会直接报错。例如,使用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
。
- 用户名或密码错误:当
user
或password
参数填写错误时:
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
表有name
和address
两个必填列,上述代码就会报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. 脏读
在并发环境下,脏读可能发生。假设两个事务T1
和T2
,T1
更新了一条数据但未提交,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数据库常见错误的分析和示例代码的展示,希望开发者在实际项目中能够更加准确地排查和解决问题,确保数据库操作的稳定和可靠。