很多人问过我一个问题:tmp 能不能替代 Parquet,成为主流的数据格式?说实话,第一次听到这个说法的时候我愣了一下,因为这两个名字根本不是同一个维度的东西。tmp 只是一个扩展名、一个文件生命周期的标记;Parquet 是一套有完整设计的开源列式存储格式,是整个大数据生态里事实上的主流数据文件格式。之所以会有这种疑问,大概率是工作中见多了带.tmp的中间文件,也见过 Spark 写 Parquet 时先落盘成.parquet.tmp再改名的过程,于是有人误以为它们是可以互相对标的两种格式。
这个问题拆开讲其实特别有意思。它背后包含的是:临时文件到底算什么?数据文件为什么需要“格式”?所谓主流格式,靠什么赢得生态地位?把这些想清楚了,你自然能理解为什么 tmp 替代不了 Parquet,同时也会明白 tmp 在什么场景下才是合理的选择。
这篇文章适合所有做数据开发、数仓、数据平台、数据同步的人,也适合刚开始接触大数据、正在为“文件到底存成什么格式”而纠结的新手。我会把 tmp 和 Parquet 从原理到实操拆开讲透,再顺手把日常开发里围绕/tmp和.parquet的那些坑一并梳理出来。
1. 先搞清楚一个前提:tmp 从来不是一种数据格式
1.1 tmp 的三种常见身份
tmp 在日常开发里至少有三种完全不同的身份,很多人把它们混在一起讨论。
第一是/tmp目录,操作系统的临时目录。几乎所有程序都会往这里写运行时产生的中间文件,Linux 和 macOS 下尤其常见。比如 Java 的java.io.tmpdir默认就指向/tmp,Java 程序创建的临时文件默认都在这个目录里。你随便打开一台 Linux 服务器,/tmp底下大概率躺着一堆乱七八糟的文件,有些是安装程序的缓存,有些是软件的 PID 文件,有些是数据库的 socket 文件。
第二是.tmp后缀。程序运行过程中需要“暂时存在”的数据,很多开发人员会随手命名成xxx.tmp。这类文件的语义很明确:它不是最终产物,任务跑完就可以删除。Java 里的File.createTempFile("prefix", ".tmp")就是这种模式的典型代表,生成的临时文件通常带一串随机数字,用完即弃。
第三是“内容不确定”。这个东西最重要——.tmp文件的内容可以是任何东西。它可能是纯文本、CSV、JSON、二进制序列化对象、加密数据、或者干脆只是一个空的锁标记文件。你拿到一个.tmp文件,如果不了解它是哪个程序、在什么阶段、以什么编码方式生成的,就完全没法解析它。tmp 描述的是文件的生命周期,而不是文件的内容结构。
所以结论已经很清楚了:tmp 不是一种数据格式,它只是一个约定俗成的命名标签。你完全可以把一个 Parquet 文件命名为data.tmp,文件内容在解析器眼里依然是 Parquet;你也可以把一个 CSV 命名为data.parquet,但它依然不是 Parquet。后缀不决定格式,内容才决定格式。
1.2 Java 程序里的 tmp 到底是干什么的
热搜词“tmp在java中的意思”一直有人搜,我顺便把这个点讲透。Java 里最常用的临时文件 API 是File.createTempFile,调用一次会在系统临时目录下生成一个带随机数的新文件,避免文件名冲突。它解决了什么问题?多进程/多线程并发写临时文件时的命名冲突问题。
Java 程序里 tmp 文件最常见的用途有几个。第一,大对象溢写,比如 JVM 在做外部排序、计算引擎在做 shuffle 时,内存不够就把中间数据 spill 到磁盘临时文件里。第二,文件上传和下载的中转,比如 Web 应用接收到一个 2GB 的上传文件,先把完整内容落到.tmp文件,校验通过后再 rename 成正式文件名,这样即使用户上传中断也不会产生半截的正式文件。第三,解压和压缩的中间过程,比如解压一个 ZIP 包时先把当前条目释放到临时文件,再移动到目标目录。
这些 tmp 文件有一个共同特点:只在单个进程的短生命周期内有价值。进程退出、任务完成、连接断开之后,它们就变成了纯粹的垃圾文件。它们没有 schema,没有格式规范,没有跨进程可识别的结构,甚至连命名都是随机生成的。
理解了这一点,再看 Parquet,就能感受到“真正的数据格式”和“临时文件标签”之间的差距有多大。
1.3 Parquet 是真正的“数据格式”
Parquet 是 Apache 基金会的顶级项目,由 Twitter 和 Cloudera 合作开发,目标是提供一种面向分析场景的、跨语言、自描述的列式存储格式。它跟 tmp 完全是两个物种。
Parquet 的“格式能力”体现在几个地方。文件内部自带 schema 描述,列名、列类型、编码方式、压缩算法全部写在文件尾部的 footer 里。读取任何 Parquet 文件,不需要额外的元数据服务,不需要建表语句,解析器从文件里就能还原出完整的结构信息。数据按列组织,同一列的数据连续存放,配合压缩编码,既能减小体积,又能让查询只读取需要的列。文件内部还划分为多个 row group,每个 row group 都带着自己的统计信息,为分布式并行读取和谓词下推提供了基础。
这些设计不是拍脑袋定的,而是针对大数据场景下“文件体积大、扫描次数多、单次查询只关心少数列”的特点做的。你拿一个.tmp文件跟它对比,会发现 tmp 连“格式”的门都没有入——它只是告诉别人“这是个临时文件”,仅此而已。
2. 四个关键维度:Parquet 凭什么成为主流格式
我把 tmp 和 Parquet 的核心差异整理成一个表,后文再逐个展开。
| 对比维度 | tmp 文件 | Parquet 文件 |
|---|---|---|
| 本质 | 临时文件命名约定 | 列式存储格式规范 |
| schema 描述 | 无 | 内嵌在文件 footer,自描述 |
| 数据布局 | 与内容无关,按生产顺序写入 | 列式存储 + row group 分区 |
| 压缩能力 | 取决于内含格式,通常无优化 | 内置多种编码,天然适合压缩 |
| 读取优化 | 无 | 列裁剪、谓词下推、统计信息 |
| 生态兼容 | 无固定规则 | Hadoop、Spark、Hive、DataX 等广泛支持 |
| 生命周期 | 短,用完即弃 | 可长期存储、归档、跨系统交换 |
2.1 schema 自描述:Parquet 把“约定”写进了文件
数据文件最容易出问题的地方不是读写性能,而是“两个系统之间怎么理解同一份数据”。后端开发都很熟悉“统一返回数据格式”这套东西——接口层把响应统一成{code, message, data}这样的结构,调用方才能只写一套解析逻辑。数据文件其实也需要这种“统一格式约定”。
CSV 和 JSON 为什么在大数据场景里让人头疼?因为它们的 schema 是外置的。两个人传 CSV,如果没约定好字段顺序和类型,接收方就只能靠猜。今天写任务的人把order_id放第一列,明天维护的人把userId放第一列,下游解析全部错位。JSON 稍微好一点,字段名内嵌在文件里,但每个文件都要扫描全部内容才能知道有哪些字段,而且类型信息很弱,数字可能是字符串,字符串也可能被解析成数字。
Parquet 把 schema 直接写进文件,读取端拿到的是一份完整的、强类型的结构描述。列名、类型、嵌套结构、空值标记,全部自包含。这意味着什么?意味着任何人拿到一个 Parquet 文件,即使没有任何外部文档,也能通过标准工具看到它的字段结构。你可以把 Parquet 理解成“自带说明书”的数据文件,而.tmp文件连说明书都没有。
2.2 列式存储带来的压缩红利:同样数据体积差几倍
压缩率是 Parquet 最直观的优势,也是最容易打动人的地方。这要从数据布局说起。
行式存储(CSV、JSON Line)写入时一行接一行,一行的所有字段连续存放。压缩算法虽然能压掉一些重复内容,但不同类型的数据混在一起,压缩效率天然受限。比如一个订单文件里,金额是小数、时间是字符串、状态是枚举值,它们在一个压缩块里混杂出现,通用压缩算法很难找到足够长的重复模式。
列式存储则把同一列的数据连续放在一起。时间列全是时间戳,状态列全是那几个枚举值,枚举字段用字典编码后,原来几万个重复的PAID、PENDING字符串只需要存一次字典表,数据区全部换成短编号。RLE(Run-Length Encoding)还能把连续出现的相同值压成“值+重复次数”两个字段。数字列用 Delta Encoding 存相邻值的差值,差值通常很小,再配合压缩效果非常好。
我做个具体例子。一批电商订单日志,JSON Line 格式大概 20GB,转成 Parquet 通常是 5GB 左右,视数据重复度而定,压缩 4 倍很正常,有些高重复度数据能做到 10 倍以上。这不仅是省钱,还意味着扫描相同数据量的磁盘 IO 能减少一个数量级。你拿一个.tmp文件去比,如果它内部装的是 CSV,它享受不到任何列式布局带来的压缩红利,体积就是实打实的原始大小。
2.3 读取性能:列裁剪与谓词下推
文件存储格式的第二个硬性指标是查询效率。这在分析场景里往往比压缩更重要,因为磁盘扫描才是最大的瓶颈。
列裁剪(Column Pruning)很好理解。Parquet 按列存储,查询只需要读涉及的那几列。一张订单宽表有 100 列,业务查询只关心金额和状态,Parquet 可以只读取这两列对应的数据块,其余 98 列完全不碰。行式存储做不到这件事,你必须把每一行的完整内容读出来再丢掉不需要的字段,100 列就得读 100 列的量。
谓词下推(Predicate Pushdown)更进一步。每个 Parquet 文件由多个 row group 组成,每个 row group 在文件 footer 里记录了统计信息,比如某列的最小值、最大值、空值数量。Spark 读 Parquet 时,如果过滤条件和一个 row group 的统计信息完全不匹配,比如status = 'CLOSED',而某个 row group 的状态列最大值是PENDING,那这个 row group 会被直接跳过,连数据块都不用打开。在执行计划里你能看到PushedFilters这一项,看到它就意味着过滤条件下推到文件读取层了。
这两种优化叠加起来的效果非常夸张。同样是几十 GB 的表,行式格式全表扫描可能要跑几分钟,Parquet 配合分区裁剪和谓词下推可能几十秒就出结果。tmp 文件没有这种结构,它就是一块“死数据”,查询引擎面对它只能老老实实扫描。
2.4 可拆分性:文件结构决定分布式并行能力
大数据场景还有一个隐性的硬要求——文件必须能被切分成多个分片,交给多台机器并行处理。这决定了任务的扩展性。
Parquet 文件内部天然被划分为多个 row group,每个 row group 都是一个独立的读取单元。HDFS 上一个大文件按 block 切分(默认 128MB),Spark、Hive 可以把不同 block 分给不同 Executor 并行处理。即使某个 row group 跨越了 block 边界,解析器也能正确处理,因为 row group 在文件里是有明确边界标记的。
.tmp文件呢?如果里面是未压缩的文本,切开很容易把一行记录从中间劈开;如果是自定义二进制,切分点在哪都找不到。这也是为什么 MapReduce 时代的文本格式要依赖 InputFormat 傻傻地找换行符,而自定义二进制格式基本没法在分布式框架里安全拆分。一个不能安全切分的文件,在分布式计算里就没有并行度可言。这就是 tmp 不可能成为主流格式的底层原因之一。
3. 大数据链路里,tmp 只是临时落脚点,Parquet 才是正式格式
3.1 写入过程中那个.tmp是什么来头
很多人在数据目录里看到xxx.parquet.tmp或_temporary文件夹,就误以为 tmp 和 Parquet 是某种“竞争关系”。实际上恰恰相反,这些.tmp是引擎在写入 Parquet 过程中使用的原子性保护机制。
Spark 写 Parquet 时的大致流程是:所有 task 先把数据写到临时目录或带.tmp标记的文件里,当所有 task 都成功完成后,driver 统一做一次 rename 操作,把临时文件改成正式文件名,对数据消费者“一次性”暴露结果。这样做的目的很简单:在写入过程中如果某个 task 失败,实际数据和外部能看到的数据保持隔离,不会出现下游读到半截文件的情况;消费者永远只能看到完整提交成功的版本。
Hive、Flink、DataX 写 HDFS 也有类似的机制。这个设计本身其实是 tmp 的“合理用法”——tmp 就是用来做中间态的。它是写入流程的保护罩,不是数据的最终归宿。你单独挑出一个.parquet.tmp文件看,它的格式核心还是 Parquet,只是当前处于“未提交”状态。这跟“用 tmp 替代 Parquet 格式”完全是两码事。
3.2 DataX、Spark、Hive 为什么都认 Parquet
文件格式的“主流”地位,最终体现在生态兼容性上。DataX 是阿里开源的数据同步工具,它的 HDFS Reader 原生支持读取 Parquet 文件。配置里指定fileType为parquet,DataX 就会用 Parquet 解析逻辑去读取数据:
{ "reader": { "name": "hdfsreader", "parameter": { "defaultFS": "hdfs://nameservice1", "path": "/user/hive/warehouse/ods.db/order_daily", "fileType": "parquet", "column": [ { "index": 0, "type": "string" }, { "index": 1, "type": "long" } ] } } }如果你把fileType配成text,DataX 就会按文本行解析,即使文件真实格式是 Parquet,读出来的也是一堆乱码。所以这不仅是“支持不支持”的问题,而是每个主流工具都针对 Parquet 实现了专门的解析和优化路径。
Spark 更夸张,Parquet 是它默认的高性能数据源之一,spark.read.parquet、df.write.parquet基本可以无缝对接。Hive 建表时写STORED AS PARQUET,就能让查询引擎自动识别列式布局,享受列裁剪和谓词下推。为什么大家都围绕 Parquet 做适配?因为它开放、标准、自描述,而且生态已经滚雪球滚起来了。你今天选一个只有 A 公司能读的私有二进制格式,明天你的下游同事就会因为打不开文件而骂人。
3.3 tmp 文件走出单机就“没人认识”
我见过太多这样的场景:开发机上的 Python 脚本生成了一批中间结果,文件名是result.tmp,放在/tmp下。当天任务跑完没问题,三天后要复用这份数据,写脚本的同事已经忘了文件里是什么序列化格式,最后只能重跑任务。
这不是个别现象,而是 tmp 作为“非格式”的必然结果。/tmp下的文件是单机生命周期内的产物,跟具体进程、具体文件系统、具体机器强绑定。换一台机器、换一个进程,文件里的字节流就变成天书。Parquet 不会这样,它是跨平台、跨语言的标准。Python 用pandas能读,Java 用ParquetReader能读,Go、Rust 都有自己的库,DuckDB 一条 SQL 就能查。所有工具都依赖文件内嵌的 schema 工作,不需要额外传一份元数据。
“tmp 文件用什么打开”这个问题本身就证明了 tmp 不适合做主流格式。因为答案是“看情况”——看你当初是用什么程序、什么序列化方式、什么编码规则写的。而“parquet 文件怎么打开”的答案是确定性的:任何支持 Parquet 的工具,输入路径直接读。
4. 工作里那些和 tmp、Parquet 纠缠的常见问题实录
4.1 MySQL 报 error 2002:/tmp被清理后的经典连锁反应
热搜词里有一个高频报错:ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock'。这个问题十有八九跟/tmp目录被清理或权限被改有关。
MySQL 在本地连接时默认走 Unix socket,路径通常是/tmp/mysql.sock。有些运维脚本为了腾磁盘空间,会对整个/tmp做无差别清理,把 socket 文件一起删掉。结果就是 MySQL 服务进程还活着,但客户端找不到连接入口了,于是报 error 2002。更隐蔽的情况是/tmp目录权限被改成 000,或者挂载选项里带了noexec,即使 socket 文件还在,客户端也无法正常访问。
遇到这个报错,先确认两件事:第一,MySQL 进程是否存活,用ps -ef | grep mysqld看;第二,my.cnf 里配置的 socket 路径和报错路径是否一致。如果服务在但 socket 没了,重启 mysqld 会重新生成 socket 文件;如果频繁被误删,最好把 socket 路径配置到受控目录,比如/var/run/mysqld/mysqld.sock,并让清理脚本避开这个目录。
注意:别把清理脚本设置成对整个
/tmp一刀切。真正稳妥的做法是清理超过一定时间的文件,并维护一个排除名单,把 socket、锁文件、运行中进程占用的临时文件都排除在外。
4.2 Oracle Grid 安装时 PRVF-7546:临时目录也能卡住部署
另一个跟/tmp相关的经典报错是 Oracle Grid Infrastructure 安装时的PRVF-7546,完整信息类似:The work directory "/tmp/gridSetupActions2026-09-24_04-14-35PM"不可用。Oracle 安装程序会在/tmp下创建gridSetupActions开头的临时工作目录,用来存放安装过程中的操作记录、校验脚本和日志。
报错原因基本就三类。第一,/tmp所在文件系统空间不足,安装程序写不了临时文件。第二,/tmp目录权限不对,安装用户没有写权限。第三,/tmp被挂载成noexec,安装程序无法执行拷过去的脚本。排查方式也很简单:df -h /tmp看空间,ls -ld /tmp看权限,mount | grep /tmp看挂载选项。该清理的清理,该改权限的改权限,该换临时目录的就在安装参数里指定别的路径。
这种问题不复杂,但很烦人,因为安装过程卡在 1% 然后报个目录不可用,排查半天才发现是/tmp的空间被日志撑满了。这类经历其实就是在提醒我们:依赖无规范临时目录的流程,天然有脆弱性。数据格式选型也是同样的逻辑,你把正式数据也塞进一个没有规范、没有结构、随时可能被清理的载体里,那整个数据链路的稳定性都得陪葬。
4.3 Parquet 文件怎么打开:四种标准姿势
跟 tmp 的“无从打开”相反,Parquet 的打开方式非常确定。我给新手整理四种最常用的姿势。
第一种,DuckDB,一行 SQL 就能查:
SELECT * FROM read_parquet('order_daily.parquet') LIMIT 20;第二种,Python 环境,pandas 配合 pyarrow:
import pandas as pd df = pd.read_parquet('order_daily.parquet', engine='pyarrow') print(df.head())第三种,命令行工具 parquet-tools:
parquet-tools show order_daily.parquet --limit 20 parquet-tools schema order_daily.parquet第四种,Spark SQL:
SELECT * FROM parquet.`/path/to/order_daily.parquet` LIMIT 20;这些工具的共同点:完全依赖 Parquet 文件内嵌的 schema,输入路径直接读,不需要任何外部元数据。这才是“主流格式”应该有的底气。
4.4 特定领域的专用格式:在自己的生态里很强,出了门就没人认
顺便说一下热搜词里的“iq15 数据格式”。在 DSP、音频、通信这类硬件领域,Q15 或 IQ15 这种定点数编码格式是很常见的,一个符号位加 15 个小数位,纯粹为了定点 DSP 的计算效率。它在自己的芯片和编译器生态里是“标准格式”,性能极好,但拿到大数据平台、数据仓库里,几乎没有任何通用工具能直接解析它,必须自己写解码逻辑。
这个例子恰好能说明“主流格式”的本质:一种格式能成为主流,不是因为它在某个特定场景下最优,而是因为它获得了足够广泛的生态共识。Parquet 胜出的地方就在于,它虽然是列式存储,但在数据仓库、数据湖、ETL、分析引擎、云存储这些大数据的主要场景里,它就是那个“通用语言”。tmp 没有共识,没有规范,没有工具链,自然不可能成为主流。
5. 格式选型的实操建议与个人心得
5.1 什么场景用 tmp 完全没问题
tmp 不是一无是处,它有非常合理的生存空间。凡是在单个进程内、短生命周期、不跨系统交换的中间态数据,用 tmp 都没有问题,甚至是最合理的选择。
举几个例子:排序算法的磁盘溢写、大文件上传的暂存、解压过程的中间文件、进程间通信的临时锁文件。这些场景的共同点是“自己写自己读”,生命周期以分钟甚至秒为单位,不需要 schema,不需要跨工具解析,不需要长期归档。这时候给文件加.tmp后缀,反而是一种好习惯,它明确告诉所有人“这不是正式产物,别对它负责”。
关键是要有边界感。tmp 可以用在临时态,但不能充当正式数据的容器。一旦数据需要跨进程、跨机器、跨团队传递,或者需要长期保留,再扔进.tmp里就是给自己埋雷。
5.2 什么场景应该果断用 Parquet
反过来,以下场景我建议你直接上 Parquet。
数仓分层表。不管 ODS、DWD 还是 ADS,只要是落表到 HDFS 或对象存储上的数据,用 Parquet 基本是数仓的最佳实践。数仓的核心操作是列式分析,Parquet 的列裁剪、谓词下推、压缩能力就是为这个场景量身定做的。
多引擎共享的数据湖。Spark 批处理、Flink 流写、Presto/Trino 即席查询、DataX 离线同步,如果大家读写的是同一个 Parquet 文件,各自的解析逻辑都能正常工作。换成私有格式,每接入一个引擎就要做一次特殊适配。
需要长期归档的数据。Parquet 自描述 + 开放标准意味着十年后你不需要一个快失传的 SDK 才能打开当年的数据。这一点对归档场景尤其重要。
实操上有一个配置组合值得参考:Parquet + Zstd 压缩 + 合理的分区策略。row group 大小也不用刻意调,保持 Spark 默认(128MB 左右)就比较稳。字段变更靠 schema 演进解决,新增列直接追加,老文件不重写也能被新逻辑读取。
5.3 我的格式选型心得与踩坑记录
最后分享两个我自己踩过的坑。
第一个是临时结果被“临时”了三个星期。当时为了图省事,把中间结果写成一个自定义二进制.tmp文件,想着“第二天跑关联任务时读一下就行”。结果第二天任务延期,三天后我又要处理别的事,等想起这份数据时已经不知道那串字节流是什么结构了。最后只能重跑整个链路,浪费时间也浪费算力。那之后我给自己定了一条规矩:中间结果如果有可能被二次使用,直接写 Parquet,哪怕多花几行代码。
第二个是 CSV 的 schema 错位问题。早期团队用 CSV 做接口间的数据交换,字段顺序一旦调整,下游组那边解析全乱。查了半天才发现是生产链路里加了一个列,但下游的表结构没有同步。换成 Parquet 之后,schema 内嵌在每个文件里,新增列是老文件不存在的字段,读取端按自己的 schema 解释,配合演进规则,再也没出现过这种“列错位”的故障。
我个人的原则很简单:文件的生命周期决定格式选择。如果它只能活几十秒,tmp 完全够用;如果它要活过任务重跑、要跨越引擎和团队边界,就老老实实写成 Parquet。格式不是玄学,它本质上是在回答“谁会在什么时候、通过什么工具、读这份数据”的问题。想清楚这个问题,选型就不会跑偏。