Elixir使用mongodb驱动导出MongoDB数据至CSV性能咨询
嘿,作为Elixir新手能搞定数据导出流程已经很厉害了!针对你导出10万条数据耗时8秒的情况,咱们可以从几个方向优化,试试看能不能显著提速:
1. 分批拉取MongoDB数据,避免一次性加载全量
一次性把10万条数据全部拉到内存里,不仅会占用大量内存,还会让MongoDB的返回速度变慢。你可以利用驱动的游标功能,通过batch_size参数分批获取数据,比如每次拉取1000-5000条:
cursor = Mongo.find(conn, "your_collection", %{}, batch_size: 2000)
这样游标会分批从MongoDB取数据,内存压力小了,整体处理速度也会提升。
2. 用Stream代替Enum做流式处理
Elixir的Enum是贪婪求值,会把所有数据先加载到内存再处理;而Stream是惰性求值,处理一条算一条,非常适合大数据量场景。把你原来的Enum.map换成Stream.map,再配合流式写入文件,能大幅减少内存占用并提升效率。
3. 使用专业CSV库做流式写入
如果是自己手动拼接CSV字符串再写入文件,效率会很低。推荐用Hex上的csv库,它支持流式编码和写入,能边处理数据边写入文件,不用把所有CSV内容都存在内存里:
首先在mix.exs添加依赖:
defp deps do [ {:mongodb, "~> 0.9"}, {:csv, "~> 3.0"} ] end
然后修改导出逻辑,结合Stream和CSV库:
defmodule MongodbExport do import Mongo def export do {:ok, conn} = start_link(url: "mongodb://localhost:27017/your_database") # 带分批参数的游标 cursor = find(conn, "your_collection", %{}, batch_size: 2000) # 流式处理:转换文档→编码CSV→写入文件 cursor |> Stream.map(&transform_to_csv_row/1) |> CSV.encode() |> Stream.into(File.stream!("export.csv", [:write, :utf8])) |> Stream.run() stop(conn) end defp transform_to_csv_row(doc) do # 根据你的文档结构,提取需要导出的字段,返回列表即可 [doc["id"], doc["name"], doc["created_at"]] end end
4. 利用Elixir并发特性(谨慎使用)
既然你看中Elixir的并发,可以试试用Task.async_stream来并行处理分批的数据,但要注意两个点:
- 不要让并发数超过MongoDB的连接池限制,避免把数据库打垮
- 写入文件时要保证顺序或者用进程安全的写入方式(比如用一个专门的进程负责写入)
比如简单的并发处理示例:
cursor |> Enum.chunk_every(2000) |> Task.async_stream(fn batch -> Enum.map(batch, &transform_to_csv_row/1) end) |> Stream.flat_map(fn {:ok, rows} -> rows end) |> CSV.encode() |> Stream.into(File.stream!("export.csv")) |> Stream.run()
这个方式适合数据转换逻辑比较复杂的场景,如果只是简单提取字段,可能提升不明显,反而增加开销,建议先测试。
5. 优化MongoDB查询性能
先检查你的查询语句有没有走索引:如果导出时用了查询条件,但是没有对应索引,MongoDB会做全表扫描,速度会很慢。你可以在MongoDB shell里用db.your_collection.find(你的查询条件).explain()查看查询计划,添加合适的索引,能大幅提升数据读取速度。
最后,你可以先调整batch_size的数值(比如1000、2000、5000)测试,找到最适合你数据大小的批次;也可以用time mix run -e 'MongodbExport.export()'对比优化前后的耗时。
内容的提问来源于stack exchange,提问作者Vinay Pandya

