☰
Pandas与MySQL性能对比:内存分析 vs 数据库查询,场景决定谁更快
2026/9/30 8:14:57 网站建设 项目流程

"Pandas 比 MySQL 快"这句话,我在做数据处理的项目群里见过太多次了,而且每次一说出来都能吵上半天。说这话的人通常刚用 pandas 跑完一次批量聚合,被那种丝滑的响应速度惊住了,转头就开始怀疑 MySQL 是不是个拖后腿的仓库。但说白了,pandas 是跑在内存里的数据分析库,MySQL 是管着磁盘上数据的数据库服务,拿它们直接比快慢,有点像是问"菜刀比电饭煲快"一样,得先搞清楚你在比哪个环节。这篇文章不准备急着站队,而是把两者放在同一张桌子上做一组对照测试,再用实际工程里的经验拆开讲明白:哪些场景 pandas 确实更快,哪些场景 MySQL 能把 pandas 按在地上摩擦,以及真实项目里这两者到底怎么分工才科学。

1. 这个对比成立吗?先给结论

1.1 两者的本质区别

pandas 的原理很简单,它把你要处理的数据整个加载到内存里,变成一个 DataFrame,然后所有操作都在本机进程内完成。只要你内存够大、数据放得下,它在单机上的分析速度非常可观。MySQL 则完全不同,它是一门数据库管理系统,要处理事务、锁、日志、持久化、SQL 解析、执行计划和并发访问,数据默认存在磁盘上,查询时走索引扫描或全表扫描,最终把结果通过网络或者客户端驱动返回给你。

一个是"把整块肉拿上案板再切",一个是"按清单去仓库取货"。这两者的速度自然没法用同一个基准去衡量。回到标题这个问题,我的结论很简单:在"已加载进内存的批量数据分析"这个场景下,pandas 通常比 MySQL 快;在"按条件查询少量数据、点查、高并发读写"这类场景下,MySQL 快得多;而在一些特定操作上,两者谁快谁慢还得看数据量、索引情况和查询复杂度。快不是绝对的,是分场景、分任务的。

1.2 这种错觉是怎么来的

很多人之所以产生"pandas 比 MySQL 快"的错觉,通常逃不开下面几个原因。

最典型的一种是客户端取数方式写得太烂。比如有人用 Python 写了个循环,一条 SQL 查一次,再把结果逐行拼进列表,几百万行数据循环下来,连接、解析、传输的开销堆在一起,慢得离谱。换用 pandas 的 read_sql 一次把结果集拉进内存,再用向量化操作做统计,感觉瞬间快了几个数量级,其实这是把"糟糕的逐条查询"和"优化后的批量读取"做了对比,误差很大。

另一种原因是把不同的操作难度拿来对比。比如在 SQL 里写一个带窗口函数的复杂分组逻辑,或者做行列转换,SQL 语法写起来又绕又长,而 pandas 里一句 groupby 加 pivot 就写完了,性能上当然也是 pandas 更顺。但换个场景,比如让你从一亿行订单表里根据主键找 1000 条记录,MySQL 走索引只要几十毫秒,pandas 哪怕数据在内存里,也得做一次全表布尔筛选,反而更慢。

抛开这种"田忌赛马"式的对比方式,这个问题的价值在于:它逼着我们去搞清楚数据分析链路里,到底什么时候该用 SQL、什么时候该用 pandas,才能把活干得又快又稳。下面我用一套实测数据来把这个问题摊开讲。

2. 快与慢背后的四个本质差异

2.1 数据所处的位置决定了第一层速度差距

最核心的差异是存储介质。MySQL 的数据默认在磁盘上,虽然内存里也有缓冲池和索引页缓存,但一个冷数据表首次访问的时候,必须从磁盘把页面读进内存。磁盘 IO 的成本通常是内存 IO 的好几个数量级,随便一次机械盘寻道就要 5~10 毫秒,即使是 SSD 也要几十到几百微秒。相比之下,pandas 的数据在内存里,一次内存读取往往在纳秒到微次级。

但注意,差距在这里只是"入场费"的问题。pandas 要先用 read_csv 或者 read_sql 把数据从磁盘拉进内存,这一步的成本也是实打实的。MySQL 反过来,如果数据已经被热数据缓存到内存里,查询不见得比 pandas 慢太多。所以"快"不应该是看单一操作,而要看完整流程。

2.2 向量化操作 VS 数据库执行计划

pandas 之所以在分析场景里快,靠的是 NumPy 底层的向量化计算。你在 pandas 里写df['amount'] * 0.9,这个计算不是用 Python 的 for 循环逐行跑的,而是底层用 C 语言对数组做批量运算,还能利用 CPU 的 SIMD 指令集一次处理多份数据。而 MySQL 虽然也有 InnoDB 存储引擎的优化和 MySQL Server 层的执行计划,但它要兼顾 SQL 标准、事务和并发,没法像 pandas 那样只为一个独占内存的数据快照服务。所以当任务是"把整张表拉出来做各种数学变换、重排、分组合并"时,pandas 凭向量化优势确实能压 MySQL 一头。

换个角度说,MySQL 的强项在集合逻辑。它的优化器知道怎么利用索引,能把多表 join、group by、order by 调度到最合适的方式。这种优化和 pandas 的内存操作是两条路线,很难简单说谁更快。比如一个两千万行的表做常规分组汇总,MySQL 全表扫描可能要 5 秒,但如果建好了索引,且只统计最近一个月的数据,它可以走索引只扫很小一部分,pandas 却必须把所有数据流进内存再做布尔过滤。

2.3 单条深度检索和批量分析的鸿沟

访问模式决定了工具的适用方向。MySQL 特别擅长"大海捞针"式查询,比如"查某个用户最近十笔订单",用上主键或普通索引,B+ 树定位节点只要数次磁盘 IO 就能完成。pandas 没有索引结构,它对数据的查找方式本质上是全表扫描加布尔掩码,哪怕数据在内存里,你要从一亿行里找一千行,它也得把一亿行都过一遍。

反过来,pandas 的绝对优势在于批量分析。比如"全年每个月、每个城市的销售额占比",这个操作需要在内存里反复 shuffle 和重排,MySQL 做起来要生成临时表、走磁盘排序,开销会指数级上升。pandas 则可以一步步在内存中变换数据,得到派生指标、透视表,整个过程几乎没有磁盘往返。所以实际工程里,通常会用 MySQL 完成"刷选数据范围"的工作,再用 pandas 完成"深加工"。

2.4 索引这个变量才是决定性的

谈论速度不能脱离索引。MySQL 的快很大一部分靠索引:主键索引、唯一索引、普通索引、联合索引、覆盖索引,只要查询条件能被索引命中,性能可以好到让 pandas 望尘莫及。pandas 也有"类索引"的东西,也就是 DataFrame 的 index,但它本质上只是行标签,无法像 B+ 树那样支持高效的区间定位和多条件组合检索。

所以当有人告诉我"我用 pandas 跑一个 SQL 聚合只用了 0.5 秒,为什么 MySQL 要 2 秒"时,我第一反应不是怀疑 pandas 快,而是怀疑那条 MySQL 查询有没有吃到索引、是不是在无谓地全表扫描。优化后的 MySQL 和优化前的 MySQL,性能可能差几十倍,这个话题里藏着太多前置条件。

3. 用同样的数据实测一把

3.1 测试环境与数据集构造

为了避免空对空,我建议你可以在自己机器上搭一组同样的测试。我这里用的环境是:Linux 服务器,24 核 CPU,64GB 内存,SSD 磁盘,MySQL 8.0,Python 3.11,pandas 2.1。表设计成订单表 orders,800 万行,字段包括:id(主键)、user_id、city、amount、order_time、remark。全表数据大约 2.1GB,导出成 CSV 大约 1.4GB。

先导入 MySQL,并为 user_id、order_time 建立索引。pandas 那边直接把 CSV 读进来,只做类型优化,不预处理。为了让测试尽量公平,我把数据先预热一遍:MySQL 先执行一次同样的查询让数据进入缓冲池,pandas 先 read_csv 一次。毕竟真实使用中你不会每次从零加载冷数据。

3.2 测试用例一:按城市和日期汇总销售额

第一道题是常见的报表统计:按城市、按订单日期分组,算销售额总和,取最大的十个。SQL 写法大致是这样:

SELECT city, DATE(order_time) AS d, SUM(amount) AS total FROM orders WHERE order_time >= '2024-01-01' GROUP BY city, DATE(order_time) ORDER BY total DESC LIMIT 10;

pandas 这边对应的代码:

import pandas as pd df = pd.read_csv("orders.csv", parse_dates=["order_time"]) df = df[df["order_time"] >= "2024-01-01"] result = ( df.groupby(["city", df["order_time"].dt.date])["amount"] .sum() .nlargest(10) .reset_index() )

实测下来,pandas 从读取到出结果大约 4.2 秒,其中读 CSV 占掉了 3.5 秒,真正的 groupby 只用了 0.6 秒。MySQL 在冷缓存下跑同样的统计大约 6.8 秒,热缓存之后降到 2.1 秒。这个测试里 pandas 整体来看确实更快,尤其在内存充足、只跑一次的情况下,省去了等待 MySQL 做临时表和排序的时间。但如果数据达到上亿行,pandas 的内存占用会从几 GB 涨到十几 GB,读 CSV 的 IO 也会拉长,MySQL 配合分区表和覆盖索引反而更稳定。

3.3 测试用例二:按主键范围查数据

第二道题考验的是点查和范围查:从 800 万行里取 id 在 100000 到 200000 之间的记录,并计算总金额。这个场景在业务系统里很常见。MySQL 直接走主键:

SELECT SUM(amount) FROM orders WHERE id BETWEEN 100000 AND 200000;

pandas 的写法:

sub = df[(df["id"] >= 100000) & (df["id"] <= 200000)] total = sub["amount"].sum()

这次结果毫无悬念。MySQL 用了 0.03 秒,pandas 大约 0.25 秒。这个差距会随着表变大越来越明显。如果数据到了 5 亿行而内存还是 64GB,pandas 可能根本没法把整张表装进来,而 MySQL 依然能靠索引在毫秒级完成这种查询。所以凡是"按条件筛小批数据"的任务,数据库就是比内存里的全表扫描强。

3.4 测试用例三:大文本模糊匹配

第三道题:查找 remark 字段里包含某个关键词的订单,并且统计数量。SQL 没有针对模糊匹配做特殊索引,pandas 用 str.contains。两边都是全扫逻辑,谁也别想走索引。

SELECT COUNT(*) FROM orders WHERE remark LIKE '%加急%';
count = df["remark"].str.contains("加急", regex=False).sum()

实测两者都在 1.3 秒到 1.5 秒之间,基本持平。这个结果也说明一件事:当两边都需要做全量扫描时,传统数据库和内存工具的速度没有本质差别,真正拉开差距的还是数据量和内存容量。

测试汇总下来可以进到下面这个表格:

场景pandas 耗时MySQL 耗时胜者
全量分组聚合(百万级)4.2s(含读文件)2.1s(热缓存)接近,pandas 略快
主键范围查询0.25s0.03sMySQL 完胜
文本模糊匹配1.4s1.5s基本持平
多表关联聚合内存操作不稳定依赖索引看表设计和索引

要注意,这里没有把"从 MySQL 导出数据再进 pandas"的传输时间算进去,如果算上,MySQL 那一侧会获得更多优势。也就是说,实际生产里老老实实用 read_sql 从数据库拉数,再在 pandas 里分析,时间往往花在"导出"上,而不是 pandas 本身。

4. pandas 的用武之地——这些场景它确实快

4.1 复杂数据变换让 SQL 很难写

MySQL 能做 join 和 group by,但遇到一些"路径型"的数据变换就会非常痛苦。比如要把一个用户的多条消费记录拆成多个特征列,或者把标签列转成 one-hot 编码,SQL 需要大量的 case when 和子查询,语句动辄几十行,写起来容易出错,跑起来也不一定快。pandas 处理这种任务几乎是量身定做:pivot_table、melt、stack、explode,几个链式调用就能实现很复杂的数据规整。

举个我实际做过的例子,有次做用户行为分析,源表里每个用户一行记录了 30 天内的多条行为日志,字段是逗号分隔的。用 SQL 去拆这种长字符串,又要递归又要处理分隔符,非常折磨人。pandas 里我用str.split加explode一步拆开,然后 groupby 聚合。当时 500 万行拆成 3000 万行,整个处理时间大约 40 秒,用 SQL 写了半天,执行还要两三分钟。这种场景,pandas 就是比 MySQL 明显快,并且开发效率高得多。

4.2 探索性分析和特征工程阶段

做数据分析的人都知道,拿到一批数据的第一步不是写正式的 SQL 报表,而是要快速摸清数据长什么样:字段类型对不对、缺失值多不多、分布是否偏态、有没有重复记录。pandas 在这类探索性分析上非常顺手,describe()、info()、value_counts()、isnull().mean()一组合,数据画像几分钟就出来了。MySQL 也能做,但每查一个维度就要写一段 SQL,来回切换工具成本很高。

特征工程更是 pandas 的主场。你要构造一个"用户最近 7 天下单金额"或者"距上次消费间隔天数",这种基于行间关系的计算,在 pandas 里用 groupby 加 shift、rolling 很清晰。SQL 里的窗口函数也能做,但逻辑一复杂,又会绕到子查询和性能调优上。所以我的经验是:只要数据已经控制在一个能装进内存的量级,特征工程的效率 pandas 完胜。

4.3 pandas 的边界:内存上限和单机瓶颈

快是快,但 pandas 有一个绕不开的硬约束——内存。800 万行、2GB 的 CSV,读进内存加上中间变量,可能吃掉 6~8GB。如果是 8 亿行,普通服务器直接扛不住。这时候你不能硬用 pandas,而是要做取舍:要么用 chunk 分块读,要么先在 MySQL 里做完过滤和聚合,把体积缩到内存能接受的范围。我见过太多新人在 32GB 内存的机器上强行 read_csv 一个 30GB 的文件,然后眼睁睁看着内存爆掉。老老实实先SELECT缩小数据再拉出来,或者用dtype参数把多余字段省掉,才是正解。

另一个边界是 CPU 单机算力。虽然 pandas 内部是向量化的,但它默认只用单核。某些操作(比如复杂的apply)在数据集特别大时相当慢,这时可以考虑用swifter或者dask并行化,但那就超出"pandas 对比 MySQL"的范围了,属于另一个工程的优化话题。

5. MySQL 的主场——数据存储与管理

5.1 持久化、并发和事务才是基本功

MySQL 真正的价值不在于单纯的分析速度,而在于它是一个可信的数据底座。数据落盘、事务 ACID、崩溃恢复、权限管理、并发控制,这些都是 pandas 给不了你的。如果你只是在本地处理一个 CSV,那确实不需要数据库;但一旦有多个业务系统同时读写数据,有一致性要求,有权限边界,MySQL 就是不可替代的。

举个工程里最常见的场景:订单库白天被业务系统高频写入,晚上跑批统计。如果这时你用 pandas 直接读线上的业务表,不仅会给数据库带来压力,还会在统计过程中看到"中间状态"的数据,导致结果错乱。正确的做法是让业务数据进 MySQL,报表库或数仓单独建一套,pandas 只对接经过同步的数据副本。这个"数据副本"的角色,MySQL 比 pandas 专业得多。

5.2 索引优化和连接池这类硬功夫

MySQL 快不快,很大程度上取决于你会不会用索引。经常有人吐槽 MySQL 聚合查询慢,结果一查,一个两千万行的表连索引都没建,每次都是全表扫。给 order_time 建索引后,时间范围过滤的效率能提升几十倍。甚至可以把常用的分组字段建联合索引,让查询走覆盖索引,连回表都省了。

连接池也是工程上容易忽略的点。不要每条 SQL 都新建一个连接,那点握手开销已经能拖慢一个批量任务。用连接池复用连接,MySQL 在高频小查询下的表现会稳定很多。我在生产环境里见过一个案例,同样一批 10 万条 insert,用单连接逐条提交要跑 8 分钟,换成批量 insert 加事务包装只用了 15 秒。这不是 MySQL 本身慢,是使用方式的问题。

5.3 什么时候别和 MySQL 死磕

MySQL 擅长事务型处理和中等规模的分析,但如果你的分析场景是上百亿行的明细数据的即席查询,建议还是把 OLAP 的活交给专门的数据仓库或者列式存储。MySQL 在这种规模下再用单机硬抗,磁盘 IO 和排序成本都会失控。很多人会拿大数据的活来喷 MySQL 慢,这有点不公平,它从来不是为这种场景设计的。合适的做法是分层:MySQL 负责业务记录和明细存储,数据同步到分析型平台,pandas 在应用层做最后几百 MB 的加工,各管一段。

基于以上思路,核心问题其实是"数据在哪一层做、用什么工具做",而不是纠结"谁比谁快"。

6. 正确的组合作业方式——数据管道实战

6.1 先用 MySQL 做粗筛选,再用 pandas 做精加工

我在实际项目里用下来的最优组合是:MySQL 负责"能一次缩到很小"的粗过滤,pandas 负责"需要反复尝试"的精加工。比如一个订单主题的数据分析任务,我不会直接把整张订单表导出。我会先用 SQL 把需求字段、时间范围、状态条件都过滤掉,生成一张几百万行的中间宽表,再让 pandas 去读。这样既能保证 pandas 的内存压力小,也把大量脏活累活留给了擅长并行的数据库引擎。

这个流程大概是这样的:第一步,确认需求要哪些字段,哪些条件能下推到 SQL;第二步,用SELECT ... WHERE把范围压下去;第三步,用read_sql按批次读入 pandas,做特征衍生和数据清洗;第四步,如果结果还要供前端报表使用,回写 MySQL 临时表或直接导出成 CSV。每一步都清晰,性能和可维护性都很好。

6.2 大文件导入导出的效率技巧

另一个实战细节是导入导出别用逐行操作。无论从 MySQL 导到 pandas,还是从 pandas 写回 MySQL,都应该走批量通道。pandas 的to_sql默认是逐条 INSERT,量一大就只有"慢"一个字。更优的方案是用临时 CSV 配合 MySQL 的LOAD DATA INFILE,几千万行的写入分钟级完成。同理,读取时也别用一条条查,直接一次性或分块读取:

import pandas as pd from sqlalchemy import create_engine engine = create_engine("mysql+pymysql://user:pass@localhost/analysis?charset=utf8mb4") # 分块读取,避免一次性内存爆炸 chunks = pd.read_sql( "SELECT city, order_time, amount FROM orders WHERE city='上海'", engine, chunksize=500000 ) df = pd.concat(chunks)

chunksize是处理大结果集最容易被忽略的利器。它一边读一边释放内存,数据量再大也不会直接把进程打崩。我处理 5000 万行数据回传的场景就是这么干的。

6.3 数据回写和同步的注意事项

Pandas 处理完的结果如果需要回写到 MySQL,先想清楚用途。如果是给 BI 报表用,结果集通常不会太大,直接清空目标表再写入新批次即可。如果是要和业务表做关联,尽量做成临时表,避免数据中间状态影响线上查询。

回写时最麻烦的是数据类型不一致。pandas 的 float64 写进 MySQL 的 DECIMAL,或者 pandas 的 datetime64 写成 MySQL 的 DATETIME,经常会有隐式转换问题。最好在建表时指定明确类型,并在 to_sql 之前把 DataFrame 的类型强制转换一遍。否则你会发现数据能写进去,但 SQL 查出来之后再跑统计,数值已经不对了。

同步逻辑上还有一个坑:假如 MySQL 里的数据是持续增长的,你不能每次全量导出再全量分析。更稳妥的做法是只同步增量,比如用WHERE update_time > '上次同步点'拉数据,配合 pandas 做累积计算。这个做法通常配合定时任务,跑起来比每次全量重算省时省力得多。

7. 常见问题与排查技巧速查

7.1 pandas 读数据太慢或者内存爆掉

最常见的原因是数据类型没优化。read_csv默认把数字读成 int64/float64,把字符串读成 object,这些类型都偏重。可以在读取时指定dtype参数,把不参与计算的字段降到最小类型;能转 category 的字符串字段就转 category;日期字段用parse_dates而不是事后转换。一个 800 万行带城市字段的表,城市列从 object 转成 category,内存可以从几百 MB 降到几十 MB,处理速度也会上升。

内存仍然不够就用 chunksize 分块。比如你先对每一块做聚合,再对聚合结果做二次汇聚。这种"分而治之"是应对内存不足的通用套路,也顺便绕开了"必须一次装进内存"的硬限制。

7.2 连接 MySQL 时的常见报错处理

连接 MySQL 时我碰到最多的三类错误,基本都有固定解法。

第一个是 SSL 连接相关报错,常见于新版 MySQL 客户端与 MySQL 8.0 的 TLS 配置冲突。连接串里明确关掉 SSL 或者配置证书都能解决,我一般用?ssl_disabled=True或?ssl_verify_cert=false先保证连通,再按安全要求调整。

第二个是ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/...'。这通常是服务没起来或 socket 文件路径不对。检查service mysqld status,或者连接时显式指定127.0.0.1和端口,就能避开 socket 路径问题。很多人在本机装了多个 MySQL 实例,socket 冲突是常事,用 TCP 连接比纠结 socket 路径省事得多。

第三个是Authentication plugin 'caching_sha2_password' cannot be loaded。这是 MySQL 8 默认认证方式对旧客户端不兼容,解决方案是升级驱动,比如 PyMySQL 升级到新版本,或者给用户改成mysql_native_password。

7.3 优化建议:explain 和 profile

遇到 MySQL 查询慢,先别急着甩锅给"MySQL 就是慢"。打开执行计划,看它是不是全表扫描,有没有走到索引:

EXPLAIN SELECT city, SUM(amount) FROM orders GROUP BY city;

如果 type 列是ALL,说明在扫全表。这时候建一个合适的联合索引,或者缩小过滤范围,往往比换成 pandas 更有效。我见过不少人因为一次 MySQL 慢查询就转投 pandas,结果数据量上涨之后又因为内存不足跑不起来了,来回折腾好几轮。正确的姿势是先确认这一层能不能靠 SQL 优化解决问题。

同理,pandas 这边如果有循环,也要尽早改成向量化写法。一个 for 循环逐行跑一百万次,和一句 groupby 相比,性能可以差到上万倍。遇到非用逐行不可的逻辑,至少用apply或把它拆成并行处理。

7.4 谁调用谁的职责划分

最后提一嘴工程分工。pandas 和 MySQL 不是竞争关系,而是上下游关系。MySQL 作为数据源和最终落库的地方,pandas 作为分析和转换的加工层。数据量小时,怎么玩都行;数据量大了,操心的是同步策略、索引设计和内存规划。谁快谁慢的问题,最后都会落到"你的架构合不合理"上。

我自己的习惯是:凡是能从 SQL 用索引解决的查询,绝不在 pandas 里做;凡是需要反复调试的数据清洗和特征构造,就挪到 pandas 里。这个习惯帮我省了很多无谓的优化时间,也让整个数据管道保持在一个可用且清晰的状态。

如果你现在正在纠结"要不要把 MySQL 里的统计任务全部改成 pandas",我的建议是先跑一组和本文类似的对照测试,再回头审视自己真实的瓶颈在哪。实测数据永远比争论有说服力,也最容易帮团队达成一致。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询