PyMySQL 详解:从入门到实战,Python 操作 MySQL 一站式指南

目录

[一、PyMySQL 是什么?为什么选择它?](#一、PyMySQL 是什么?为什么选择它?)

[1.1 PyMySQL 的定义](#1.1 PyMySQL 的定义)

[1.2 为什么选择 PyMySQL?](#1.2 为什么选择 PyMySQL?)

二、快速上手:安装与第一个连接

[2.1 安装 PyMySQL](#2.1 安装 PyMySQL)

[2.2 建立数据库连接](#2.2 建立数据库连接)

[2.3 关闭连接](#2.3 关闭连接)

三、核心操作:增删改查

[3.1 游标对象(Cursor)](#3.1 游标对象(Cursor))

[3.2 查询数据(SELECT)](#3.2 查询数据(SELECT))

[3.3 插入数据(INSERT)](#3.3 插入数据(INSERT))

[3.4 更新数据(UPDATE)](#3.4 更新数据(UPDATE))

[3.5 删除数据(DELETE)](#3.5 删除数据(DELETE))

[3.6 批量操作(executemany)](#3.6 批量操作(executemany))

四、进阶技巧:游标类型与事务管理

[4.1 游标类型详解](#4.1 游标类型详解)

[4.2 事务管理](#4.2 事务管理)

五、性能优化:连接池与批量操作

[5.1 为什么需要连接池?](#5.1 为什么需要连接池?)

[5.2 使用 DBUtils 实现连接池](#5.2 使用 DBUtils 实现连接池)

[5.3 批量操作优化建议](#5.3 批量操作优化建议)

[六、安全第一:防止 SQL 注入](#六、安全第一:防止 SQL 注入)

[6.1 什么是 SQL 注入?](#6.1 什么是 SQL 注入?)

[6.2 正确的做法:参数化查询](#6.2 正确的做法:参数化查询)

七、实战案例:用户管理系统

八、常见问题与解决方案

九、总结


一、PyMySQL 是什么?为什么选择它?

1.1 PyMySQL 的定义

PyMySQL 是一个纯 Python 编写的 MySQL 和 MariaDB 客户端库,遵循 Python 数据库 API v2.0 规范(PEP 249)。它让 Python 程序能够轻松连接和操作 MySQL 数据库,执行各种 SQL 语句。

简单来说,PyMySQL 就是 Python 与 MySQL 之间的"翻译官"------把 Python 代码写给它,它帮你转换成 SQL 发给数据库,再把结果翻译回 Python 数据格式。

1.2 为什么选择 PyMySQL?

目前 Python 连接 MySQL 的主流驱动有三个:PyMySQL、MySQLdb(也称 MySQL-python)和 mysql-connector-python。为什么推荐 PyMySQL?

|-------------|--------------------------|----------------|------------------------|
| 对比项 | PyMySQL | MySQLdb | mysql-connector-python |
| 实现语言 | 纯 Python | C 扩展 | 纯 Python / C 两种 |
| Python 3 支持 | 完美支持 | 仅 Python 2,已停更 | 支持 |
| 安装难度 | 极低:`pip install pymysql | 高:需要 C 编译环境 | 中等 |
| 跨平台性 | 完美 | Windows 安装困难 | 良好 |
| 社区活跃度 | 非常活跃 | 已停止维护 | 官方支持但社区较冷 |
| 性能 | 对绝大多数场景足够 | 更快(C 底层) | 中等 |
| 使用成本 | 极低 | 高 | 中等 |

结论:对于绝大多数 Python 3 项目,PyMySQL 是最佳选择。它的纯 Python特性带来的安装便捷性和跨平台兼容性,远超它与 C 扩展驱动之间微小的性能差异。除非你有极致的性能需求,否则选 PyMySQL 就可以。

MySQLdb 作为 Python 2 时代的产物,年久失修且安装复杂,已基本被淘汰。因此对于现代 Python 项目,PyMySQL 已然成为主流选择。

二、快速上手:安装与第一个连接

2.1 安装 PyMySQL

一条命令搞定:

python 复制代码
pip install pymysql

验证安装是否成功:

python 复制代码
python
import pymysql
print(pymysql.__version__)  # 输出版本号

2.2 建立数据库连接

python 复制代码
python
import pymysql

# 建立数据库连接
connection = pymysql.connect(
    host='localhost',      # 数据库主机地址(也可用 '127.0.0.1')
    user='root',           # 数据库用户名
    password='your_password',  # 数据库密码
    database='test_db',    # 要连接的数据库名称
    port=3306,             # MySQL 默认端口
    charset='utf8mb4',     # 字符集,推荐 utf8mb4 以支持 emoji
    cursorclass=pymysql.cursors.DictCursor  # 游标类型,让结果返回字典
)

# 检查连接是否成功
if connection.open:
    print("连接成功!")

connect() 常用参数说明:

|-------------|----------|------------------------|
| 参数 | 说明 | 默认值 |
| host | 数据库服务器地址 | localhost |
| user | 登录用户名 | 当前用户 |
| password | 登录密码 | 输入自己设置的 |
| database | 要连接的数据库名 | 连接自己的 |
| port | 端口号 | 3306 |
| charset | 字符集 | utf8mb4 |
| cursorclass | 游标类型 | pymysql.cursors.Cursor |

2.3 关闭连接

使用完数据库后,务必关闭连接释放资源:

python 复制代码
connection.close()

三、核心操作:增删改查

3.1 游标对象(Cursor)

游标是执行 SQL 语句的"手"。执行任何 SQL 都需要通过游标:

python 复制代码
python
cursor = connection.cursor()
# 执行 SQL...
cursor.close()  # 用完关闭

3.2 查询数据(SELECT)

python 复制代码
python
cursor = connection.cursor()
cursor.execute("SELECT id, name, age FROM users")

# 获取单条记录
one = cursor.fetchone()

# 获取所有记录
all = cursor.fetchall()

# 获取指定条数
many = cursor.fetchmany(5)

for row in all:
    print(row)

三种获取方式对比:

|--------------|-------------|-----------|
| 方法 | 返回值 | 适用场景 |
| fetchone() | 单条记录(元组/字典) | 只取一条数据 |
| fetchmany(n) | 最多 n 条记录的列表 | 分批次获取大量数据 |
| fetchall() | 所有记录的列表 | 数据量较小 |

3.3 插入数据(INSERT)

python 复制代码
python
sql = "INSERT INTO users (name, age, email) VALUES (%s, %s, %s)"
cursor.execute(sql, ('张三', 25, 'zhangsan@example.com'))
connection.commit()  # 重要!必须提交才会写入数据库
print(f"插入行数: {cursor.rowcount}")
print(f"自增ID: {cursor.lastrowid}")

3.4 更新数据(UPDATE)

python 复制代码
python
sql = "UPDATE users SET age = %s WHERE name = %s"
cursor.execute(sql, (26, '张三'))
connection.commit()

3.5 删除数据(DELETE)

python 复制代码
python
sql = "DELETE FROM users WHERE name = %s"
cursor.execute(sql, ('张三',))
connection.commit()

3.6 批量操作(executemany)

当需要插入大量数据时,使用 executemany() 可以显著提升性能:

python 复制代码
python
users_data = [
    ('李四', 20, 'lisi@example.com'),
    ('王五', 22, 'wangwu@example.com'),
    ('赵六', 23, 'zhaoliu@example.com'),
]

sql = "INSERT INTO users (name, age, email) VALUES (%s, %s, %s)"
cursor.executemany(sql, users_data)
connection.commit()

性能对比:在插入 20000 条数据的测试中,executemany() 的执行时间仅为 0.175 秒,而逐条插入则需要数秒。

四、进阶技巧:游标类型与事务管理

4.1 游标类型详解

PyMySQL 提供了多种游标类型,根据场景选择合适的使用:

|--------------|--------------------------|---------------|
| 游标类型 | 说明 | 适用场景 |
| Cursor(默认) | 返回元组,每条记录是 (值1, 值2, ...) | 简单场景 |
| DictCursor | 返回字典,键为列名,值为数据 | 推荐!代码可读性最高 |
| SSCursor | 服务端游标,不缓存结果 | 处理百万级大数据,节省内存 |
| SSDictCursor | 服务端游标 + 字典格式 | 大数据量且需要字典格式 |

使用示例:

python 复制代码
python
# 普通游标(元组形式)
conn = pymysql.connect(host='localhost', user='root', password='123456', database='test')
cursor = conn.cursor()
cursor.execute("SELECT name, age FROM users")
print(cursor.fetchone())  # 输出: ('张三', 25)

# 字典游标(推荐!)
conn = pymysql.connect(
    host='localhost', user='root', password='123456', database='test',
    cursorclass=pymysql.cursors.DictCursor  # 指定游标类型
)
cursor = conn.cursor()
cursor.execute("SELECT name, age FROM users")
print(cursor.fetchone())  # 输出: {'name': '张三', 'age': 25}

为什么推荐 DictCursor?

  • 通过列名访问数据,代码自解释
  • 避免因列顺序变化而出错
  • 与 JSON 序列化天然兼容

4.2 事务管理

PyMySQL 默认关闭自动提交模式(autocommit=False),需要手动控制事务。

python 复制代码
python
try:
    # 开启事务(通过执行语句隐式开始)
    cursor.execute("INSERT INTO accounts (user_id, balance) VALUES (%s, %s)", (1, 100))
    cursor.execute("UPDATE accounts SET balance = balance - %s WHERE user_id = %s", (50, 2))
    
    # 所有操作成功,提交事务
    connection.commit()
    print("事务提交成功")

except Exception as e:
    # 出现异常,回滚事务
    connection.rollback()
    print(f"事务回滚: {e}")

finally:
    cursor.close()
    connection.close()

事务操作总结:

|------|-----------------------|--------------|
| 操作 | 方法 | 说明 |
| 提交事务 | connection.commit() | 将修改永久写入数据库 |
| 回滚事务 | connection.rollback() | 撤销当前事务中的所有操作 |

注意:不要忘记 commit()!很多新手只执行了 execute() 却不提交,导致数据根本没写进去。

五、性能优化:连接池与批量操作

5.1 为什么需要连接池?

每次数据库操作都创建和关闭连接,开销非常大。连接池可以复用数据库连接,避免频繁创建和销毁,大幅提升性能。

5.2 使用 DBUtils 实现连接池

python 复制代码
python
import pymysql
from DBUtils.PooledDB import PooledDB

# 创建连接池
pool = PooledDB(
    creator=pymysql,      # 使用 pymysql 作为驱动
    maxconnections=10,    # 连接池允许的最大连接数
    mincached=2,          # 初始化时创建的连接数
    maxcached=5,          # 连接池中闲置的最大连接数
    blocking=True,        # 连接数达到上限时是否阻塞等待
    host='localhost',
    user='root',
    password='your_password',
    database='test_db',
    charset='utf8mb4'
)

# 从连接池获取连接
connection = pool.connection()
cursor = connection.cursor()
cursor.execute("SELECT * FROM users")
results = cursor.fetchall()
# 使用完毕后归还连接(调用 close 并不会真正关闭,而是放回池中)
cursor.close()
connection.close()

DBUtils 的两种连接模式:

模式一:为每个线程创建一个独立连接,线程结束时连接归还池中。

模式二:线程之间共享连接池,适合多线程 Web 应用。

安装 DBUtils:pip install DBUtils

5.3 批量操作优化建议

  1. 使用 executemany() 代替循环 execute():减少网络往返次数

  2. 适当批量提交:每 N 条数据提交一次,而不是每条都提交

  3. 关闭自动提交:批量操作期间设置 autocommit=False,操作完成后再 commit()

六、安全第一:防止 SQL 注入

6.1 什么是 SQL 注入?

SQL 注入是攻击者通过构造恶意输入,绕过 SQL 语句逻辑,非法获取或篡改数据的安全漏洞。

错误示例( 绝对不要这样写):

python 复制代码
python
# 用户输入:' OR '1'='1
user_input = "' OR '1'='1"
sql = f"SELECT * FROM users WHERE name = '{user_input}'"
cursor.execute(sql)  # 结果变成 SELECT * FROM users WHERE name = '' OR '1'='1'
# 所有用户数据都被查出来了!

6.2 正确的做法:参数化查询

使用 execute() 的参数化功能,让数据库将参数当作纯数据对待:

python 复制代码
python
# 正确写法 
sql = "SELECT * FROM users WHERE name = %s AND age = %s"
cursor.execute(sql, (user_input, age_input))

# 插入操作
sql = "INSERT INTO users (name, age) VALUES (%s, %s)"
cursor.execute(sql, (name, age))

# 批量操作同样安全
cursor.executemany(sql, data_list)

为什么参数化查询能防注入?

  • SQL 语句的结构和数据是分离传输的
  • 数据库先编译 SQL,再将参数作为纯值代入
  • 任何恶意代码都会被当作普通字符串处理,不会改变 SQL 结构

记住:永远不要用字符串拼接 SQL!永远使用参数化查询!

七、实战案例:用户管理系统

综合运用以上知识,构建一个简单的用户管理模块:

python 复制代码
python
import pymysql
from pymysql.cursors import DictCursor
from contextlib import contextmanager

class UserManager:
    def __init__(self, host, user, password, database):
        self.conn = pymysql.connect(
            host=host, user=user, password=password,
            database=database, charset='utf8mb4',
            cursorclass=DictCursor
        )
    
    @contextmanager
    def get_cursor(self):
        cursor = self.conn.cursor()
        try:
            yield cursor
            self.conn.commit()
        except Exception:
            self.conn.rollback()
            raise
        finally:
            cursor.close()
    
    def create_user(self, name, age, email):
        """创建用户"""
        sql = "INSERT INTO users (name, age, email) VALUES (%s, %s, %s)"
        with self.get_cursor() as cursor:
            cursor.execute(sql, (name, age, email))
            return cursor.lastrowid
    
    def get_user(self, user_id):
        """根据 ID 查询用户"""
        sql = "SELECT id, name, age, email FROM users WHERE id = %s"
        with self.get_cursor() as cursor:
            cursor.execute(sql, (user_id,))
            return cursor.fetchone()
    
    def update_user(self, user_id, **kwargs):
        """更新用户信息"""
        fields = ', '.join([f"{k} = %s" for k in kwargs.keys()])
        sql = f"UPDATE users SET {fields} WHERE id = %s"
        with self.get_cursor() as cursor:
            cursor.execute(sql, (*kwargs.values(), user_id))
            return cursor.rowcount
    
    def delete_user(self, user_id):
        """删除用户"""
        sql = "DELETE FROM users WHERE id = %s"
        with self.get_cursor() as cursor:
            cursor.execute(sql, (user_id,))
            return cursor.rowcount
    
    def list_users(self, limit=10, offset=0):
        """分页查询用户列表"""
        sql = "SELECT id, name, age, email FROM users LIMIT %s OFFSET %s"
        with self.get_cursor() as cursor:
            cursor.execute(sql, (limit, offset))
            return cursor.fetchall()
    
    def close(self):
        self.conn.close()


# 使用示例
if __name__ == '__main__':
    manager = UserManager('localhost', 'root', 'password', 'test_db')
    
    # 创建用户
    user_id = manager.create_user('张三', 25, 'zhangsan@example.com')
    print(f"创建成功,ID: {user_id}")
    
    # 查询用户
    user = manager.get_user(user_id)
    print(f"查询结果: {user}")
    
    # 更新用户
    manager.update_user(user_id, age=26)
    
    # 分页查询
    users = manager.list_users(limit=5)
    print(f"用户列表: {users}")
    
    manager.close()

八、常见问题与解决方案

问题1:ModuleNotFoundError: No module named 'pymysql'

原因:PyMySQL 未安装

解决:pip install pymysql

问题2:pymysql.err.OperationalError: (2003, "Can't connect to MySQL server")

原因:MySQL 服务未启动或连接参数错误

解决:检查 MySQL 服务是否运行,确认 host、port、user、password 是否正确

问题3:插入中文变成 ??? 或报 Incorrect string value

原因:字符集设置不一致

解决:连接时设置 charset='utf8mb4', 确保数据库和表的字符集也是 utf8mb4

sql 复制代码
sql
-- 修改数据库字符集
ALTER DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 修改表字符集
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

问题4:修改操作没有生效

原因:忘记调用 commit()

解决:所有增、删、改操作后都要执行 connection.commit()

问题5:pymysql.err.ProgrammingError: (1064, ...)

原因:SQL 语法错误

解决:检查 SQL 语句拼写、引号、关键字是否正确

问题6:多线程环境下连接报错

原因:PyMySQL 连接本身不是线程安全的

解决:使用连接池(如 DBUtils),让每个线程从池中获取独立连接

九、总结

安装: pip install pymysql

连接: pymysql.connect(host, user, password, database)

增删改查: 通过游标执行 SQL,修改后需 commit()

参数化查询:必须使用! 防止 SQL 注入

游标类型:推荐 DictCursor,结果更直观

批量操作: 使用 executemany() 提升性能

连接池: 高并发场景必备,使用 DBUtils

事务commit():提交,rollback() 回滚

PyMySQL 是 Python 连接 MySQL 的纯 Python 驱动,安装简单、跨平台兼容、参数化查询防注入,是现代 Python 项目的首选 MySQL 客户端库。

相关推荐
小松加哲2 小时前
MyBatis完整流程详解
java·开发语言·mybatis
迷你可可小生2 小时前
二叉树知识点
python·算法
泷羽Sec-静安2 小时前
AICTFer一天速成指南
python·sql·ctf
Z1Jxxx2 小时前
C++ P1151 子数整数
开发语言·c++·算法
User_芊芊君子2 小时前
Python+Agent入门实战:0基础搭建可复用AI智能体
开发语言·人工智能·python
迷你可可小生2 小时前
图像视觉面经学习(一)
图像处理·人工智能·python·学习
平安的平安2 小时前
用 Python 玩转 AI 绘图:Stable Diffusion 本地部署指南
人工智能·python·stable diffusion
Artech2 小时前
我所理解的Python元模型
python·meta class·meta model
寒山-居士2 小时前
量化客户端核心业务解析
python·金融