3.3 数据导入与导出:三条路线的选型与实操 本节摘要:把数据搬进图有三条主流路线:LOAD CSV 在线流式导入、neo4j-admin import 离线全量建库、驱动程序批量写入。三者的适用量级、时效性与调优手段完全不同。本节用同一份商品数据把三条路线各走一遍,给出选型表与高频坑位,最后交代导出的正确姿势。 图建好了,规模化的数据从哪来?本节的判断框架一句话:量级决定路线,时效决定事务粒度。 一、路线一:LOAD CSV——在线流式导入 LOAD CSV 直接在 Cypher 里读服务器上的 CSV,逐行流式处理,适合十万级以内、需要与其他写入同库共存的导入: 三个必须知道的细节:文件要放在导入目录并使用 前缀; 声明首行是列名,省略则从 取;
本节摘要:把数据搬进图有三条主流路线:LOAD CSV 在线流式导入、neo4j-admin import 离线全量建库、驱动程序批量写入。三者的适用量级、时效性与调优手段完全不同。本节用同一份商品数据把三条路线各走一遍,给出选型表与高频坑位,最后交代导出的正确姿势。
图建好了,规模化的数据从哪来?本节的判断框架一句话:量级决定路线,时效决定事务粒度。
LOAD CSV 直接在 Cypher 里读服务器上的 CSV,逐行流式处理,适合十万级以内、需要与其他写入同库共存的导入:
// products.csv:sku,title,category,price LOAD CSV WITH HEADERS FROM 'file:///products.csv' AS row MERGE (p:Product {sku: row.sku}) ON CREATE SET p.title = row.title, p.price = toFloat(row.price) MERGE (c:Category {name: row.category}) MERGE (p)-[:IN_CATEGORY]->(c)
导入 5000 行后的验证查询: MATCH (p:Product) RETURN count(p) → 5000
三个必须知道的细节:文件要放在导入目录并使用 file:/// 前缀;WITH HEADERS 声明首行是列名,省略则从 row[0] 取;CSV 一切皆字符串,数值要显式 toFloat/toInteger,否则价格进来的是文本,后续聚合直接报错。
⚠️ 大文件给 LOAD CSV 配上
USING PERIODIC COMMIT(按批提交,防日志膨胀);同时给锚点属性建好索引,否则每行 MERGE 都是一次全量查找——这是 LOAD CSV "越导越慢"的经典原因。
百万到十亿级、可以停机初始化时,离线工具最快。它不走事务引擎,直接按存储格式生成库文件。要求两份输入:一份节点文件、一份关系文件,关系文件用 id 引用两端节点:
products_nodes.csv: :ID,sku,title,price,:LABEL 1,KB-87,机械键盘 87键,299,:Product 2,MS-03,无线鼠标,:Product purchases_rels.csv: :START_ID,:END_ID,qty,:TYPE 1,2,1,IN_CATEGORY
# 停机状态下执行(数据库目录内不能有运行中的实例) neo4j-admin database import full neo4j ^ --nodes=products_nodes.csv ^ --relationships=purchases_rels.csv ^ --overwrite-destination=true
Import summary: nodes: ........ 2,000,000 (3.2 s / 1M) relationships: 5,600,000 (4.1 s / 1M) 总耗时约 1 分 40 秒 -- 千万级数据分钟级完成
它的限制同样明确:只对空库或覆盖导入、不支持在导入中跑 Cypher 逻辑(数据清洗要在文件侧完成)、CSV 结构严格。
上游是消息队列或定时任务时,用驱动 + UNWIND 分批写:
rows = [{"sku": "KB-87", "title": "机械键盘", "price": 299.0}, ...] def batch_import(tx, batch): tx.run(""" UNWIND $rows AS row MERGE (p:Product {sku: row.sku}) ON CREATE SET p.title = row.title, p.price = row.price """, rows=batch) with driver.session(database="neo4j") as s: for i in range(0, len(rows), 500): # 每批 500 行 s.execute_write(batch_import, rows[i:i+500])
每批一个事务,批间自动重试(死锁与瞬时错误),速率可控、可断点续跑——这是三条路线里唯一适合"长期在线"的。
| 维度 | LOAD CSV | admin import | 驱动批量 |
|---|---|---|---|
| 适用量级 | ≤ 十万行 | 百万~十亿行 | 任意(速率可控) |
| 时效 | 在线,与其他写入共存 | 离线,需停机 | 在线,常驻 |
| 数据清洗 | Cypher 内可做 | 文件侧完成 | 应用代码完成 |
| 事务开销 | 逐行或周期提交 | 无(直接建文件) | 按批提交 |
| 典型坑 | 无索引越导越慢 | 只能空库 | 批太大撑爆内存 |

对称的三条路:小规模结果用 Cypher 导出 CSV/JSON;整库迁移用 neo4j-admin database dump 生成单文件备份包(3.4 节的备份用的就是它);持续同步到下游用驱动侧消费订阅或定期全量导出。
# 整库导出为可迁移的 dump 文件(停机或用企业版在线一致性备份) neo4j-admin database dump neo4j --to-path=D:/backup
neo4j.dump 已生成 → 可用 load 命令在任何同版本实例上恢复
数据搬进图不算完,"搬对"才算完:
导入前: 锚点索引是否已建(LOAD CSV 路线) CSV 编码与转义是否统一(UTF-8,含引号字段) 导入后: 节点/关系计数与源文件行数对账 抽查 20 条:属性类型正确(数字不是字符串) 孤儿关系检查:MATCH ()-[r]->() 是否有意外的悬空语义
计数对账一行搞定:MATCH (n:Product) RETURN count(n) 与源行数核对。数字对不上时,多半是 CSV 里重复键被 MERGE 吞了——按设计不该是错误,但得知道吞了多少。
数据进得来、出得去,还差"看得住、救得回"。下一节:监控与备份。