Python操作MySQL数据库的两种方式实例分析【pymysql和pandas】

前言

先说一个标题里的不准确之处:pandas 本身并不是一个 MySQL 驱动,也没有自己的 数据库连接能力 。它自己不会连 MySQL,所谓"用 pandas 操作 MySQL", 实际是 pandas 把这件事委托给别的库:要么交给一个 DBAPI 驱动(比如 pymysql), 要么交给 SQLAlchemy 建立连接再交给驱动执行。

所以"两种方式"其实准确的说法是:

  • 驱动直连方式 :用 pymysql 自己建连接、自己执行 SQL、自己取结果,

想变 DataFrame 再手动构造;

  • pandas 方式 :用 pandas.read_sql / DataFrame.to_sql,

背后由 SQLAlchemy + 驱动(MySQL 场景下通常还是 pymysql)干活。

两者不是对立的,而是"底层"和"上层封装"的关系。本文分别演示,并说明各自适用场景。 需要强调:pymysql 与 pandas 都是第三方库,本机没有安装、也没有 Python 解释器, 示例无法运行验证,只能逐行人工推演。参数细节请以 pymysql 与 Pandas 官方文档为准。

一、方式一:pymysql 直连

pymysql.connect 的签名里,连接参数都是仅关键字 的(关键字参数), 常用的是 host、user、password、database、port、charset。 中文场景建议 charset="utf8mb4",否则 emoji 和部分生僻字会丢。

python 复制代码
# 需要 pip install pymysql;Python 3.8+

import pymysql



conn = pymysql.connect(

    host="127.0.0.1",

    user="app",

    password="secret",

    database="shop",

    port=3306,

    charset="utf8mb4",

)

try:

    with conn.cursor() as cur:

        cur.execute("SELECT id, name FROM users WHERE age > %s", (18,))

        rows = cur.fetchall()          # 返回元组的元组

        columns = [d[0] for d in cur.description]

    conn.commit()

finally:

    conn.close()



print(columns)

print(rows[:3])

要点:

  • cursor.execute(sql, params) 里的 %s 是参数占位符(不是字符串拼接),

参数用元组或列表传入,由驱动负责转义------这是防 SQL 注入的正确姿势。

  • cur.description 里每项的第一个元素是列名,可用来拿列头。
  • fetchall() 取全部、fetchone() 取一行、fetchmany(n) 取 n 行。
  • 增删改之后要 conn.commit(),否则事务不落库;默认 autocommit=False。
  • conn.cursor() 也支持 with,退出时自动关闭游标。

把结果转成 DataFrame 只是把 columns 和 rows 组装起来:

python 复制代码
# 需要 pip install pymysql pandas

import pandas as pd



df = pd.DataFrame(rows, columns=columns)

因为 fetchall() 返回的是元组的元组,DataFrame(rows, columns=columns) 正好能吃下这个形状。数据量很大时,一次性 fetchall() 会把全部结果读进内存, 此时更该用 pandas 的 chunksize 分批读。

二、方式二:pandas + SQLAlchemy

pandas 官方文档给出的连接方式是 SQLAlchemy。MySQL 场景下, 连接串形如 mysql+pymysql://用户:密码@主机/库名,+ 后面就是驱动名。

python 复制代码
# 需要 pip install pandas sqlalchemy pymysql

import pandas as pd

from sqlalchemy import create_engine



engine = create_engine(

    "mysql+pymysql://app:secret@127.0.0.1:3306/shop?charset=utf8mb4"

)



# 参数化查询:用 params,别自己拼字符串

df = pd.read_sql(

    "SELECT id, name FROM users WHERE age > %(age)s",

    engine,

    params={"age": 18},

)

print(df.head())

read_sql(sql, con, ...) 的第一个参数是 SQL、第二个才是连接,别写反 。 它的 params 支持序列或字典两种占位风格,具体用哪种取决于驱动的 paramstyle。

写回数据库用 to_sql:

python 复制代码
# pandas 1.0+

df.to_sql("users_snapshot", engine, if_exists="append", index=False)

if_exists 可取 "fail"(默认,表已存在就报错)、"replace"(覆盖)、 "append"(追加)。index=False 防止把行号当成一列写进去。

对比项 pymysql 直连 pandas + SQLAlchemy

|----|--------------|------------------------------|
| 依赖 | 只要 pymysql | pandas + sqlalchemy + 驱动 |

|------|----------|-----------------|
| 结果形态 | 元组,需自己组装 | 直接是 DataFrame |

|----|-----------|-----------|
| 适用 | 精细控制事务、游标 | 快速取数、直接分析 |

|----|--------------|-------------|
| 写入 | 自己拼 INSERT | to_sql 一步 |

|-----|-----------|-----------|
| 参数化 | %s + 元组 | params= |

三、该选哪一种

  • 只要"把表取出来分析",用 read_sql,代码最短。
  • 需要精细事务控制、逐行处理、调用存储过程,用 pymysql 直连。
  • 两者也可以混用:pandas 取数做分析,pymysql 负责小而频繁的写入。

这里有个细节值得特别说明:pandas 官方文档在"没有 SQLAlchemy 时怎么办"这一节里, 给出的回退方案是使用 sqlite3.Connection(标准库自带的 SQLite 驱动), 并没有承诺接受任意 DBAPI 驱动对象 。因此在 MySQL 场景下, 把 pymysql 的原始连接直接丢给 read_sql 并不是文档推荐的路径; 稳妥做法仍然是通过 SQLAlchemy 建 engine,再把 engine 交给 pandas。 如果你在旧代码里见到 pd.read_sql(sql, pymysql_conn),迁移到 engine 写法更安全。 engine 还有一个附带好处:连接池由 SQLAlchemy 统一管理,不必每次手动开关连接。

四、把结果做成一张汇总

python 复制代码
# 需要 pip install pandas sqlalchemy pymysql

import pandas as pd

from sqlalchemy import create_engine





def load(engine, days: int) -> pd.DataFrame:

    sql = (

        "SELECT region, amount, created_at FROM orders "

        "WHERE created_at >= DATE_SUB(NOW(), INTERVAL %(days)s DAY)"

    )

    return pd.read_sql(sql, engine, params={"days": days})





if __name__ == "__main__":

    eng = create_engine("mysql+pymysql://app:secret@127.0.0.1:3306/shop")

    data = load(eng, 30)

    print(data.groupby("region", as_index=False)["amount"].sum())

常见坑点

  1. 把用户输入拼进 SQL 字符串

❌ cur.execute("SELECT * FROM users WHERE name='%s'" % name), 遇到引号就注入,是严重安全问题 ✅ cur.execute("SELECT * FROM users WHERE name=%s", (name,)),交给驱动转义

  1. read_sql 的参数顺序写反

❌ pd.read_sql(engine, "SELECT ..."),把连接当成 SQL ✅ pd.read_sql("SELECT ...", engine)

  1. 忘记 commit

❌ 插入后直接 close(),数据库里查不到新数据 ✅ 增删改后 conn.commit()(或理解 autocommit 的含义)

  1. 字符集没设 utf8mb4

❌ 存 emoji 或部分中文时报错/变成问号 ✅ charset="utf8mb4",连接串里也写成 ?charset=utf8mb4

  1. fetchall() 拉大表

❌ 千万行结果一次性读进内存,进程被撑爆 ✅ 用 read_sql(..., chunksize=...) 分批,或 LIMIT 先取样本

  1. to_sql 默认 if_exists="fail"

❌ 第二次运行就报"表已存在",还以为代码坏了 ✅ 明确写 if_exists="append" 或 "replace"

  1. 连接串里写明文密码并提交进仓库

❌ 把 mysql+pymysql://root:真实密码@... 直接写进代码 ✅ 密码从环境变量或配置文件读取,敏感信息不进版本库

  1. 用 pandas 时以为它自带驱动

❌ 只装了 pandas 就报"缺少模块 pymysql"/"需要 SQLAlchemy" ✅ 记住 pandas 只是上层,必须另装 SQLAlchemy 和驱动器

总结

主题 结论

|--------|-------------------------------------------------------|
| 标题中的说法 | pandas 不是数据库驱动,"pandas 方式"实为 pandas + SQLAlchemy + 驱动 |

|------|---------------------------------------------------|
| 直连方式 | pymysql.connect + cursor.execute + fetchall |

|-----------|---------------------------------------------|
| pandas 方式 | read_sql(sql, con, params=...) / to_sql |

|------|--------------|
| 安全要点 | 一律参数化,绝不拼字符串 |

|------|-----------------------------|
| 内存要点 | 大结果集用 chunksize 或 LIMIT |

|------|---------------------|
| 事务要点 | 增删改要 commit,注意字符集 |

掌握这两种方式的关键,是理解"分层":pymysql 管连接和事务,pandas 管取数和分析。 把密码、字符集、参数化这三件小事做对,剩下的就是写 SQL 和拼报表了。

相关推荐
外收内放1 小时前
Python基础语法练习题(64)
python·学习
苏辰澈1 小时前
C++独属于自己的变身:多态
c语言·开发语言·c++·visualstudio·编辑器
谢亮_vipxieliang1 小时前
PHP 异常处理全解析:try-catch、自定义异常与全局异常处理器
开发语言·php
fanjiu20201 小时前
flask接收消息
python·flask
外收内放1 小时前
Python基础语法练习题(65)
开发语言·python
浅尝辄止;1 小时前
设计模式在HIS/LIS医疗系统中的完整实践指南(C#版)
开发语言·设计模式·c#
iReachers1 小时前
Termux:把安卓手机变成随身的命令行工具箱
python·安卓·termux·命令行·实用工具
xy_zoe1 小时前
揭秘市面上那些热门的SEO优化服务机构!
python·facebook
Python图像识别1 小时前
43-【2027毕设】YOLO11暴力检测识别系统 - Python完整源码+PyQt5界面+训练模型+数据集
python·qt·课程设计