
Python连接MySQL数据库操作详解:从基础连接到实战避坑指南
作为一名和数据库打了多年交道的开发者,我深知在Python项目中连接MySQL数据库,看似简单,实则暗藏玄机。尤其是中文乱码和连接超时这两个“老朋友”,几乎在每个新项目初期都会跳出来打个招呼。今天,我就结合自己的实战经验,手把手带你搞定Python连接MySQL,并重点解决这两个高频问题。
一、环境准备与基础连接
首先,我们需要一个Python的MySQL驱动。最主流的选择是 PyMySQL(纯Python实现)和 mysql-connector-python(MySQL官方)。我个人更偏爱PyMySQL,兼容性好,安装简单。当然,如果你用的是SQLAlchemy这类ORM,底层驱动选择会更灵活。
安装命令很简单:
pip install pymysql
接下来,让我们完成一个最基础的连接示例。假设我们本地有一个名为 test_db 的数据库。
import pymysql
# 基础连接参数
connection = pymysql.connect(
host='localhost', # 数据库主机地址
user='root', # 数据库用户名
password='your_password', # 数据库密码
database='test_db', # 要连接的数据库名
port=3306, # 端口,默认3306
charset='utf8mb4', # 字符集,非常重要!先写这里,后面详解
cursorclass=pymysql.cursors.DictCursor # 让游标返回字典形式的数据
)
try:
with connection.cursor() as cursor:
# 执行一个简单的查询
sql = "SELECT VERSION()"
cursor.execute(sql)
result = cursor.fetchone()
print(f"Database version: {result}")
finally:
connection.close() # 务必关闭连接
如果运行成功输出了你的MySQL版本号,那么恭喜你,最基础的一步已经完成了。但真实的项目环境远比这复杂。
二、攻克头号敌人:中文编码乱码问题
这是我踩过的第一个大坑。从前端提交的数据,一进数据库就变成了一堆问号“???”,或者显示为乱码。其根源在于字符编码在传输和处理过程中没有统一。
核心解决方案是确保“四个统一”:
- 数据库/表/字段字符集统一为
utf8mb4:MySQL的utf8其实是阉割版(最多3字节),无法存储完整的UTF-8字符(如emoji)。utf8mb4才是真正的完整支持。创建数据库时请务必指定:CREATE DATABASE `my_app` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - 连接字符集指定为
utf8mb4:如上例代码所示,在pymysql.connect()时,charset参数必须设为'utf8mb4'。这是告诉驱动,后续所有通信都使用此编码。 - Python文件本身使用UTF-8编码保存:确保你的
.py文件开头没有BOM头,并用UTF-8保存。 - 操作系统或终端环境支持UTF-8:这通常在现代开发环境中已默认配置。
让我们看一个插入中文数据的完整示例:
import pymysql
def insert_user(name, city):
conn = pymysql.connect(
host='localhost',
user='root',
password='your_password',
database='my_app',
charset='utf8mb4', # 关键参数
cursorclass=pymysql.cursors.DictCursor
)
try:
with conn.cursor() as cursor:
# 插入包含中文的数据
sql = "INSERT INTO `users` (`name`, `city`) VALUES (%s, %s)"
# 使用参数化查询,避免SQL注入,同时PyMySQL会自动处理编码
cursor.execute(sql, (name, city))
# 提交事务
conn.commit()
print("数据插入成功!")
except Exception as e:
print(f"插入数据时发生错误: {e}")
conn.rollback() # 发生错误时回滚
finally:
conn.close()
# 测试插入中文
insert_user('张三', '北京市')
遵循以上四点,中文乱码问题基本可以绝迹。
三、应对连接超时与连接管理
在Web应用或长时运行脚本中,你可能会遇到这两个错误:pymysql.err.OperationalError: (2013, 'Lost connection to MySQL server during query') 或超时错误。这通常是因为连接闲置过久,被MySQL服务器主动断开(由 wait_timeout 参数控制)。
实战中,我推荐以下两种策略:
- 使用连接池:对于Web应用,这是最佳实践。我们可以使用
DBUtils或SQLAlchemy内置的连接池。这里以DBUtils为例:pip install DBUtilsfrom dbutils.pooled_db import PooledDB import pymysql # 创建连接池 pool = PooledDB( creator=pymysql, # 使用的数据库驱动 maxconnections=10, # 连接池最大连接数 mincached=2, # 初始化时创建的空闲连接 host='localhost', user='root', password='your_password', database='my_app', charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor, ping=1, # 每次连接检查时ping服务器,0=从不,1=每次请求,2=按需检查 ) # 从连接池获取连接 def get_data(): conn = pool.connection() # 从池中获取,非新建 try: with conn.cursor() as cursor: cursor.execute("SELECT * FROM users LIMIT 5") return cursor.fetchall() finally: conn.close() # 注意:这里不是关闭,而是将连接归还给连接池 data = get_data() print(data)ping=1这个参数非常有用,它确保每次从池中取出的连接都是活跃的,自动解决了闲置超时问题。 - 手动设置连接参数与重连机制:如果不使用连接池,可以在创建连接时设置相关超时参数,并实现简单的重连逻辑。
import pymysql import time def create_connection_with_retry(max_retries=3, retry_delay=2): """创建带重试机制的连接""" for i in range(max_retries): try: conn = pymysql.connect( host='localhost', user='root', password='your_password', database='my_app', charset='utf8mb4', connect_timeout=10, # 连接超时时间(秒) read_timeout=30, # 读取超时时间 write_timeout=30, # 写入超时时间 cursorclass=pymysql.cursors.DictCursor ) print("数据库连接成功!") return conn except pymysql.err.OperationalError as e: print(f"连接失败 (尝试 {i+1}/{max_retries}): {e}") if i < max_retries - 1: time.sleep(retry_delay) else: raise # 重试多次后仍失败,抛出异常 return None # 使用带重试的连接 connection = create_connection_with_retry() if connection: # ... 执行你的操作 ... connection.close()
四、完整实战示例:安全的CRUD操作
最后,我们整合以上所有要点,写一个包含连接池、正确处理编码、具备基本错误处理的工具类,并演示增删改查(CRUD)。
import pymysql
from dbutils.pooled_db import PooledDB
class MySQLHelper:
def __init__(self, host, user, password, database):
self.pool = PooledDB(
creator=pymysql,
maxconnections=5,
mincached=2,
host=host,
user=user,
password=password,
database=database,
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
ping=1,
autocommit=False # 我们手动控制事务
)
def execute_query(self, sql, args=None):
"""执行查询语句,返回结果列表"""
conn = self.pool.connection()
try:
with conn.cursor() as cursor:
cursor.execute(sql, args or ())
result = cursor.fetchall()
return result
finally:
conn.close()
def execute_update(self, sql, args=None):
"""执行更新/插入/删除语句,返回受影响行数"""
conn = self.pool.connection()
try:
with conn.cursor() as cursor:
rows = cursor.execute(sql, args or ())
conn.commit()
return rows
except Exception as e:
conn.rollback()
raise e
finally:
conn.close()
# 使用示例
if __name__ == '__main__':
db = MySQLHelper('localhost', 'root', 'your_password', 'my_app')
# 1. 创建表 (仅示例,实际项目建议用迁移工具)
create_table_sql = """
CREATE TABLE IF NOT EXISTS `articles` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(200) NOT NULL,
`content` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
"""
# db.execute_update(create_table_sql) # 第一次运行时取消注释
# 2. 插入数据(防止SQL注入,使用参数化查询)
insert_sql = "INSERT INTO `articles` (`title`, `content`) VALUES (%s, %s)"
affected_rows = db.execute_update(insert_sql, ('Python教程', '这是一篇关于MySQL连接和中文编码的详细教程。'))
print(f"插入了 {affected_rows} 行数据。")
# 3. 查询数据
select_sql = "SELECT `id`, `title`, `created_at` FROM `articles` WHERE `title` LIKE %s"
articles = db.execute_query(select_sql, ('%Python%',))
for article in articles:
print(f"ID: {article['id']}, 标题: {article['title']}, 时间: {article['created_at']}")
# 4. 更新数据
update_sql = "UPDATE `articles` SET `title` = %s WHERE `id` = %s"
db.execute_update(update_sql, ('更新后的Python教程', 1))
# 5. 删除数据
# delete_sql = "DELETE FROM `articles` WHERE `id` = %s"
# db.execute_update(delete_sql, (1,))
五、总结与最终建议
回顾一下,要让Python稳定、高效地操作MySQL数据库,你需要牢记以下几点:
- 字符集是根本:全程使用
utf8mb4,从数据库到连接参数,一以贯之。 - 连接需要管理:短时脚本用基础连接即可;Web或长时运行服务,务必使用连接池,并合理设置
ping和超时参数。 - 安全不容忽视:永远使用参数化查询(
cursor.execute(sql, (args,)))来拼接SQL,这是防止SQL注入的底线。 - 资源务必释放:确保在
finally块或使用with上下文管理器关闭连接(或归还给连接池)。
数据库连接是应用的基石,一开始就搭建好稳健的框架,能为后续开发省去无数调试的烦恼。希望这篇融合了我个人踩坑经验的教程,能帮助你顺利搭建起与MySQL沟通的桥梁。如果在实践中遇到新的问题,不妨去官方文档或社区看看,那里总有你想要的答案。祝你编码愉快!

评论(0)