我用 DuckDB 分析了整台手机,顺便搞懂了数据湖是不是必需的


起因:一个被误解的问题

我一直以为用 DuckDB 得先有个「数据湖」——那种企业级的、堆在云上的海量数据仓库。听起来就很重,得搭一套平台。

结果完全想错了。DuckDB 的定位恰恰相反,它是「数据分析界的 SQLite」——单个文件、零服务、import 就能用。它能查数据湖,但完全不需要数据湖。

验证这个最好的办法,是拿它分析点真实的东西。我手头正好有一批数据:这台旧安卓手机。它同时是我的家庭服务器,也是这个博客的宿主。于是我把它当成一个数据源,做了一次「全面体检」。

方法始终是同一个:用脚本把某类数据抽成 CSV,再用 DuckDB 直接查那个文件。 没有数据库服务器,没有数据湖,全程在手机本地,几分钟出结果。

四个维度,四张侧写。

一、四千张照片的拍摄习惯

先从照片开始。一个 Python 脚本用 PIL 把每张照片的 EXIF(拍摄时间、相机型号、分辨率)抽成 CSV,四千多张,一分钟跑完。然后:

SELECT model, COUNT(*) FROM 'photos.csv'
WHERE model != '' GROUP BY model ORDER BY 2 DESC;

结果拼出了一张我自己都没意识到的画像。我有好几台相机:

realme GT Neo5 SE      2309 张   手机,绝对主力
ZV-E10                  313 张   索尼微单
NIKON Z 5              121 张   尼康全画幅
Canon EOS 5D Mark IV    20 张
松下 DC-S5M2            17 张

一路往前翻,还能看到设备的变迁史:2011 年的索尼摄像机、2016 年的卡西欧卡片机、2020 年的旧手机……一部个人的「影像器材编年史」,全藏在 EXIF 里。

而拍摄量在爆发式增长:

2022   180 张
2023   875 张
2024  1744 张   ← 高峰
2025   618 张

2022 年入手第一台微单之后,逐年翻倍,2024 年到顶。

但最有意思的一条,是把相机分成「手机」和「专业相机」两类,分别算平均拍摄时刻:

手机     平均 14.1 点
专业相机 平均 16.0 点

手机是白天随手记录,专业相机明显偏晚——那是「我决定今天要出门拍照」的有意识创作,更常发生在下午光线变好的时候。一条 GROUP BY 把两种完全不同的行为区分了出来,而我自己从没这样意识到过。

二、640GB 里到底存了什么

接着扫全部文件的元数据(路径、大小、类型、时间),25283 个文件,12 秒完成:

SELECT topdir, ROUND(SUM(size)/1073741824.0, 2) AS GB
FROM 'files.csv' GROUP BY topdir ORDER BY GB DESC;

一目了然:

DCIM           94 GB   ← 照片+视频,占了 84%
Pictures        3 GB
微信缓存        2 GB
音乐            1.6 GB

这台手机 84% 的空间是影像。 它本质上是个相机,附带打电话功能——和照片分析的结论互相印证。

而最有用的一条发现是意外的:文件管理器的回收站里躺着 8GB 没清空的垃圾——几个删掉的游戏安装包,删是删了,回收站却没清。查一条 SQL 就看见了,清空立省 8GB。数据分析的第一个实际价值,往往就是「发现你以为删掉了、其实还占着地方的东西」。

三、视频:和照片同步的创作曲线

902 个视频,33.7GB。按年份看:

2023   321 个 / 10.6 GB
2024   363 个 / 15.7 GB   ← 高峰
2025   200 个 / 7.1 GB

和照片的年份分布完全吻合——2024 是影像创作的巅峰年。数据在不同维度上互相印证:照片多的那年,视频也多。这种一致性本身就说明数据是可信的。

四、最「元」的一个:手机给自己做体检

这台手机跑着 Prometheus,一直在记录自己的温度、内存、电量。我把这些时序数据也导成 CSV,用 DuckDB 查——用数据分析这台机器自己的健康史。

内存最紧张的几个时刻:

00:26   可用 0.22 GB   ← 我把内存压测推到极限那一刻
00:02   可用 0.89 GB   ← 一次进程压力测试
14:46   可用 1.38 GB   ← 跑了个大模型

每一个内存低谷,都精确对应我那天做过的一次实验。 温度峰值 80.3°C 也一样,几个时间点全是压测时段。这台机器把我对它做的每件事,都默默记了下来。

还有一个值得说的细节:监控数据的「在线率」显示 100%,但这不代表它没崩过——它崩的时候,连记录自己崩了的程序也一起崩了,那些时段是纯粹的数据空洞。空洞本身就是事故的痕迹。 有时候「没有数据」比「数据异常」更能说明问题。

一点技术上的体会

四个分析,DuckDB 咬了我两次,都是同一个原因:它会自动推断 CSV 的列类型。我按字符串去处理一个它已经认成数字的列,它就报类型错误。

一开始觉得烦,后来觉得这恰恰是它聪明的地方——你不用定义 schema(像 SQLite 那样随便存),但它又坚持类型正确(像 PostgreSQL 那样严谨)。这个「零配置 + 强类型」的平衡,是它比 SQLite 更适合做分析的关键。

尾声

回到最开始那个问题:DuckDB 需要数据湖吗?

四次体检给了最彻底的答案:不需要。它需要的只是一个文件。 照片、文件清单、视频列表、监控历史——每一个都只是磁盘上一个几 MB 的 CSV,DuckDB 对着它直接跑 SQL,就把一台用了几年的手机,拆解成了一张清晰的数字自画像。

工具轻到这个程度,「分析自己的数据」这件事,门槛就消失了。而一旦数据能被查询,它总会讲出一些你不知道的故事。


全部分析跑在一台旧安卓手机上,数据没有离开过它。25000 个文件、4000 张照片、900 个视频、900 个监控点——加起来处理时间不到十分钟。