尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

DuckDB与SQLite性能对比:分析型数据库与事务型数据库该如何选型

DuckDB与SQLite性能对比:分析型数据库与事务型数据库该如何选型 我上周末帮朋友处理了一份2600万行的订单明细CSV。他原来的方案是先把它导进SQLite再写SQL做月份和品类的聚合分析。实测下来CSV导入花了将近4分钟一次GROUP BY要跑2分多钟。我换用DuckDB后连导入这步都省了一条SQL直接在CSV上跑全量聚合10秒左右出结果。朋友盯着两边的耗时长按冒出一句这玩意儿能直接干掉SQLite了吧这个说法我见过太多次了每次关于DuckDB的讨论里都有人这么问。作为一个从SQLite时代一路用过来、近两年又把DuckDB当成日常分析主力的开发者我的判断是DuckDB确实在分析型工作负载这个细分赛道上把SQLite按得没有还手之力但要说全面挑战甚至取代SQLite那还差得远。这篇文章就把两边的家底都翻开从架构、性能、SQL能力、生态和选型几个维度说清楚到底谁才是你该用的那个。1. 先说结论DuckDB赢在分析但取代不了SQLite1.1 一个2600万行的实际测试上面那个场景不是个例。我后来在好几个数据集上都重复过类似的测试比如TPC-H基准生成的模拟订单数据、公开的纽约出租车行程数据、还有我自己爬下来的房价历史数据。只要数据量超过一两千万行DuckDB和SQLite的差距就会从“有点差别”变成“肉眼可见的碾压”。SQLite在最典型的大表聚合场景里慢的根源不是某个SQL写得不合适而是它整套执行引擎的设计目标就不是干这个的。DuckDB则从一开始就瞄准了“分析型SQL体验”列式存储、向量化执行、多核并行这三板斧下来同样一条聚合SQLDuckDB往往比SQLite快出一个数量级甚至更多。1.2 为什么“全面挑战”这个说法不准确但“全面挑战”这个说法有个问题它把数据库拉到了同一个擂台好像两者必须在所有维度上分个高下。实际上这两兄弟一个为事务处理而生一个为数据分析而生本质上是两类物种。SQLite在移动端、桌面应用、嵌入式系统里是事实标准你手机里的每一个App几乎都在用它浏览器、微信、各种硬件设备数不清的场景都靠它做本地持久化。DuckDB再好也很难在短期内替换掉这些存量场景。反过来说如果你让SQLite去处理几十GB的分析查询那确实是在为难它。所以我的结论是DuckDB在分析领域确实已经构成挑战但这场挑战是有边界的边界在哪里下面几个章节详细讲。2. 设计出身不同一个把“事务”刻进骨子里一个为“扫全表”而生2.1 SQLite行存储、B-tree和“小步快跑”的事务世界SQLite诞生于2000年作者是D. Richard Hipp它设计的初衷是做一个零配置、单文件、嵌入式的关系型数据库。它的核心存储结构是B-tree数据按照行的方式连续存放一行记录里的所有字段都靠在一起。这种布局最适合什么场景最典型的例子是你要根据某个主键查出一条订单记录数据库只需要沿着B-tree索引一路定位到叶子节点把那一行数据读出来就行。事务是SQLite的另一个核心价值。它有完整的ACID事务能力通过数据库文件锁机制实现并发控制允许多个进程同时读取但同一时刻只允许一个进程写入。这种“单写多读”模型用在业务系统里刚刚好用户提交一个订单、插入一条日志、更新一个状态每个操作都很轻量数据量不大但要求可靠、不出错。不要小看这种设计。几十年来全世界无数开发者就是靠这几招解决了真实的存储问题Windows桌面软件需要本地缓存数据Android和iOS应用需要离线存储嵌入式设备需要掉电不丢数据。SQLite在这些场景里是被精品打磨过的稳定性和兼容性都经过数亿设备验证。2.2 DuckDB列式存储、向量化执行和“大口吃肉”的分析世界DuckDB来自荷兰的CWICentrum Wiskunde Informatica这个研究机构在数据库领域有很深的积累之前著名的MonetDB就是他们的作品。DuckDB的目标特别直接做一个嵌入式版本的OLAP数据库也就是“分析型SQLite”。它的存储方式是列式的。同一列的所有值连续存放在一起。为什么要这么做因为分析型SQL通常只会读取表里的少数几列。举个例子你要统计“每个月的销售总额”其实只需要读month和amount这两列其他几十列字段完全不需要碰。列式存储在这种情况下能减少巨量的无谓I/O性能自然就上来了。和列式存储配合的是向量化执行。SQLite执行查询是一行一行地迭代处理完一行再处理下一行。DuckDB则是一次性从列数据里取出一批数据通常是一整个向量比如2048行然后对整批数据做运算再配合CPU的SIMD指令集做并行计算。这种方式把CPU的吞吐量利用到了极致。再加上DuckDB天生支持多线程并行扫描和分析操作一条GROUP BY查询它可以拆成多个任务交给不同CPU核心同时跑然后合并结果。SQLite的单查询执行是单线程的这一点在数据量上来之后形成了决定性的差距。2.3 并发与写入模型单写多读和批量加载的取舍并发模型也不能只看表面。SQLite的单写多读是经过实战考验的它适合高频的小型写入操作比如一个App每秒写几条日志哪怕多个进程来写也能保证数据一致。DuckDB同样支持事务但它的最佳使用姿势是“批量加载然后分析”频繁的小事务写入不是它的主场多进程高并发写入更不是它的设计目标。所以如果拿DuckDB去处理一个典型业务后端的数据持久化比如在线商城的订单写入这不是它的菜。反过来如果拿SQLite去跑数亿行数据的分析报表它也不舒服。搞清楚这一点很多选型困惑其实就解开了一大半。3. 同样一块CPU为什么DuckDB处理大数据时能快几十倍3.1 测试环境与数据集避免只在PPT上谈性能评判一个工具不能光看官网宣传我自己习惯在相同的硬件和数据集上跑一遍再下结论。下面提到的测试结果来自我的个人台式机8核16线程的CPU、32GB内存、SSD硬盘操作系统是Windows 11。数据集主要用的是我手头一份真实脱敏过的交易流水总共约2600万行3.7GB左右的CSV文件包含订单编号、用户ID、商品分类、金额、下单时间等字段。我不想把话说死因为具体数据量和查询复杂度不同最终结果会有波动。但我要强调的是在类似的数据规模下我在不同环境、不同数据集上多次复现两个数据库之间的性能差距方向是非常稳定的。3.2 CSV直接查询与导入第一个让我放下SQLite的场景先看CSV处理。传统SQLite的思路是先用.import命令把CSV导入到表中的临时表里之后才能写SQL。这几百万行数据导入通常要花上大约1分钟到几分钟不等。在导入阶段SQLite是单线程的CPU利用率上不去你就只能干等着。DuckDB有两种玩法。第一种是直接用read_csv函数SQL直接作用在CSV文件上完全不需要导入-- SQLite 需要先导入 .mode csv .import orders.csv orders -- DuckDB 直接查 CSV 文件 SELECT strftime(%Y-%m, order_date) AS month, category, SUM(amount) AS total FROM read_csv(orders.csv) GROUP BY 1, 2;不用导入这个特点太重要了。它意味着数据文件原地不动你随时可以像查数据库一样去查询一个CSV分析完改完数据CSV文件还是那个CSV文件。如果数据量特别大还可以先用COPY INTO把CSV转成DuckDB自己的列式存储格式进一步压缩体积、提升后续查询速度。实测下来这两步的耗时都比SQLite导入快出好几倍。3.3 聚合与连表报表型SQL的差距最刺眼接下来是聚合查询。还是那张2600万行的表做一个按月份和品类分组的销售金额统计。SQLite在我机器上跑了大约140到180秒DuckDB首跑大约8到12秒后续因为缓存和统计信息优化还能更快一点。这个差距主要来自前面说的列式存储和并行执行模型。连表查询也是类似。SQLite的查询优化器相对简单遇到多个大表JOIN的时候执行计划的选择有时候会让你怀疑人生。DuckDB的优化器更现代能做谓词下推、列裁剪、子查询去关联、join reordering等操作数据量大时查询计划的优劣会直接反映在耗时上。3.4 点查询与高频小写入SQLite仍然扳回一城不过DuckDB也不是所有场景都赢。如果你给SQLite的一列建好索引然后执行SELECT * FROM orders WHERE order_id A10086这种精准点查SQLite通常能在毫秒级返回结果DuckDB在类似场景下并不会显出什么优势有时候因为列式存储需要把数据按行重组出来反而会慢一些。再比如批量插入单条记录、ORM框架的日常CRUD、频繁的连接打开关闭这类OLTP负载SQLite的成熟度和低延迟表现仍然非常可靠。DuckDB在OLTP场景里既没有明显性能优势生态支持也远远比不上SQLite。所以点查询和小事务写入的场景我认为SQLite依旧是更稳的选择。4. 日常开发中容易忽略的能力分界线SQL语法、类型与插件4.1 SQL标准覆盖度窗口函数、PIVOT与GROUPING SETS性能只是其中一个维度SQL能力本身的完整度同样重要。SQLite对SQL标准的支持是可以满足日常小需求的但一旦你开始写复杂的分析SQL就会踩到各种能力边界。举一个我经常遇到的实际例子月度销售数据要横向展开成每月一列。SQLite没有原生PIVOT语法只能靠一串CASE WHEN去手动实现写一次倒还行但数据维度一多、月份一长SQL就变得又臭又长。DuckDB原生支持PIVOT和UNPIVOT一把梭就能搞定同样的事-- SQLite 需要手动 CASE WHEN 展开 SELECT category, SUM(CASE WHEN month 2024-01 THEN amount ELSE 0 END) AS jan, SUM(CASE WHEN month 2024-02 THEN amount ELSE 0 END) AS feb FROM orders GROUP BY category; -- DuckDB 原生 PIVOT PIVOT orders ON month USING SUM(amount) GROUP BY category;类似的差异还体现在GROUPING SETS、CUBE、ROLLUP这些多维分析子句上DuckDB是完整支持的SQLite则没有或支持得很有限。窗口函数方面SQLite从3.25版本开始支持了一些基础用法但DuckDB对窗口框架、QUALIFY子句、FILTER子句等的支持更全面写复杂分析SQL时体验完全不同。4.2 类型系统5种存储类型和完整类型体系的对决做过数据处理的人都知道类型系统对数据分析有多重要。SQLite有一套出了名的“轻量类型系统”——它只有NULL、INTEGER、REAL、TEXT、BLOB这5种存储类型列上写的类型更多是“亲和性”。这带来一些便利比如往任何列塞任何类型它都不太会报错但代价是严格的类型校验和复杂类型支持基本不存在。DuckDB则提供了完整的类型体系布尔、TINYINT、SMALLINT、INTEGER、BIGINT、HUGEINT、FLOAT、DOUBLE、DECIMAL还有精确到纳秒的TIMESTAMP、时区时间戳类型、UUID、JSON以及ARRAY、MAP、STRUCT等嵌套类型。做数据分析时日期加减、时区转换、JSON字段解析在DuckDB里是一等公民操作SQLite则需要自己写不少人肉处理逻辑。4.3 扩展生态FTS5、JSON、空间索引和DuckDB的autoloadSQLite的扩展生态经过二十多年积累数量非常可观。FTS5全文检索、JSON1扩展、R*Tree空间索引、各种加密扩展这些东西在桌面软件和嵌入式领域有大量真实应用。你要是做网络爬虫的本地去重存储或者做笔记软件的全文搜索SQLite配合FTS5几乎是教科书级别的方案。DuckDB的扩展机制也很优雅用的是autoload模式你用到一个扩展功能它能自动加载对应插件。官方扩展里有Parquet、HTTPFS、PostgreSQL、SQLite、Iceberg、Delta Lake等等尤其是直接读Parquet和连PostgreSQL源库这两个能力在数据分析工作流里特别有价值。我经常用DuckDB直接去读一个PostgreSQL库里的表做分析Query完再把结果写回另一个表全程不用把数据导出来。4.4 配套工具DB Browser for SQLite、SQLiteStudio、DBeaver与DuckDB CLI工具链这块SQLite确实老练。DB Browser for SQLite是很多入门用户装的第一款SQLite管理工具界面简洁能浏览表结构、执行SQL、编辑数据SQLiteStudio是另一个轻量选择便携免安装适合快速看库DBeaver则是通用数据库管理工具也把SQLite支持做得不错。这些工具让SQLite的学习成本非常低。DuckDB的配套工具有点不一样。官方CLI做得相当顺手有点像psql支持语法高亮、自动补全、多行编辑还内置了不少分析函数可以直接用。对于可视化浏览DBeaver新版已经支持连接DuckDB另外还有一些Web版工具比如shell.duckdb.org浏览器里就能开一个环境跑查询。整体来说DuckDB的配套没有SQLite那种“遍地都是”的感觉但它和Jupyter、VS Code这类开发环境的集成更紧密更符合数据分析师的习惯。5. 生态和工具链从Windows安装到Python数据分析的实际体验5.1 Python分析流水线DuckDB与pandas/Arrow的无缝衔接我自己的主力分析栈是Python。这里DuckDB有一个让我回不去SQLite的点它可以直接把pandas DataFrame当作一个虚拟表来查询。import duckdb import pandas as pd df pd.read_csv(orders.csv) result duckdb.sql( SELECT category, SUM(amount) AS total FROM df GROUP BY category ORDER BY total DESC ).df()注意这里DuckDB不会先把DataFrame复制一份到磁盘再查而是直接在内存里扫描这个DataFrame性能比自己写pandas的groupby还要稳尤其是在内存占用方面比反复创建中间DataFrame节省得多。SQLite当然也能查DataFrame但需要先把数据逐行写入数据库再执行SQL这个转换过程的成本和内存开销都不容小觑。如果你在用Arrow格式做数据交换DuckDB支持得更是原生级别的。它的外部扫描可以直接读取Arrow Table和Arrow Stream跨引擎的数据流转非常自然这一点在数据团队里尤其好用。5.2 Windows下安装与接入SQLite的DLL传统和DuckDB的Python包Windows一直有大量SQLite用户因为很多桌面软件跑在Windows上。早期SQLite在Windows下的常规操作是下载sqlite3.dll和sqlite3.exe放到项目目录里就能用这已经形成了一种传统。直到现在还有不少教程在讲“SQLite数据在Windows系统里面如何安装”本质上就是“下载两个文件完事”。这个简单粗暴的接入方式恰恰是SQLite深入人心的原因之一。DuckDB的Windows接入路径不一样。如果你想在Python里用直接pip install duckdb它会带好对应Windows平台的二进制包。如果你想要独立版本的数据库文件可以下载官方发布的duckdb.exe命令行工具也是免安装的双击就能进SQL交互环境。两条路都走通过体验都很顺只是和传统Windows开发者的使用习惯不太一样。5.3 移动端与嵌入式SQLite的护城河SQLite真正的护城河在移动端和嵌入式领域。它在iOS和Android上是系统级内置的数据库你写的任何一个手机App里只要用到本地持久化底层大概率就是SQLite。这种深度绑定意味着SQLite的内存占用极小默认编译配置下全库只有几百KB、运行稳定、事务可靠并且有数十年如一日稳定的文件格式。DuckDB虽然也能嵌入到移动设备上近来的版本优化了不少内存占用但它的定位本来就是分析型的体积和运行时要求天然比SQLite大一圈。在移动端内存寸土寸金的场景下DuckDB很难取代SQLite成为系统默认级的存在。嵌入式设备的固件存储、物联网网关的本地数据日志短期内更不用想。5.4 一个相似点单文件数据库但用法不同两者都有一个招牌特性整个数据库就是一个文件。这意味着备份、迁移、复制都非常方便拷走文件就等于拷走整个库。我自己一直很喜欢这个特性分析完一批结果直接把这个.duckdb文件发给同事他那边打开就能继续查完全不需要部署数据库服务。但有一点要注意SQLite的单文件格式在移动端和桌面端经过了二十年极端环境考验文件兼容性做得极其严格。DuckDB作为新项目也在努力保持向后兼容但它还处在快速迭代期跨大版本的数据库文件格式可能会有升级需求所以重要分析结果我一般会保留一份CSV或Parquet作为原始数据存档而不是只依赖DuckDB文件本身。6. 选型判断哪些项目继续用SQLite哪些项目该换DuckDB6.1 继续用SQLite的场景清单如果你的项目属于下面这几类我不建议跟风换DuckDB第一移动端或桌面端App的本地存储需要长时间稳定运行、文件格式严谨、系统级集成SQLite的地位没有对手。第二高频的小事务写入比如IoT网关、客户端埋点、采集Agent把消息一条条写进本地缓冲SQLite的单写者模型在这种场景下非常可靠。第三作为系统文件格式而不是数据库比如某些软件内部用SQLite保存配置、草稿和历史记录这种用途重要的是零配置和长期兼容性。第四项目中已经有成熟SQLite库和团队经验改动成本高除非有明确的性能瓶颈否则没必要折腾。还有一个实际存在的场景Windows桌面系统里用.NET 4.8之类的老框架接SQLite的情况在海量存量系统里非常常见系统内置驱动、社区资料丰富这种老项目稳定压倒一切千万不用为了追新去换存储层。6.2 换成DuckDB收益很大的场景清单反过来说下面这些情况里DuckDB能带来立竿见影的收益第一你经常处理几个GB到几十GB的CSV、Parquet、JSON文件主要工作就是做筛选、聚合、报表和探索性分析DuckDB的免导入查询和多核并行会帮你节省大量等待时间。第二你的SQL复杂度已经超过了SQLite的舒适区比如要用到PIVOT、GROUPING SETS、复杂窗口函数、递归CTE这些能力DuckDB的支持更完整。第三你在Python或R里做数据分析希望能把DataFrame、Arrow这些生态工具和SQL无缝串联起来DuckDB是当前最顺滑的中间层。第四你需要在本地快速抽测线上数据仓库的一部分数据或者直接用DuckDB的Postgres扩展直连线上库拉数据分析不用先落盘。换个直观说法如果你是数据分析师、数据工程师、量化研究员、算法工程师每天和大量表格数据打交道DuckDB几乎可以成为你工具箱里的一把主力军刀。如果你在做业务后端的CRUD接口或App本地存储SQLite才是那个更懂你的老朋友。6.3 一个可以直接抄的混合工作流其实两者不是非此即彼。我现在的标准做法是应用系统内部的所有状态存储、日志落盘、用户数据缓存一律用SQLite它在该在的地方继续发光。如果这些SQLite数据需要做报表分析我直接让DuckDB通过sqlite扩展去读SQLite文件拿到DuckDB里跑复杂分析再把结果输出成Parquet或者导出到目标表。-- 在 DuckDB 里直接读 SQLite 文件做分析 INSTALL sqlite; LOAD sqlite; SELECT category, strftime(%Y-%m, order_date) AS month, SUM(amount) AS total FROM sqlite_scan(shop.db, orders) GROUP BY category, month;这个搭配的好处是应用侧保持SQLite的稳定和轻量分析侧享受到DuckDB的性能和SQL能力数据不用搬来搬去文件就在那里两边按需使用。在真实的项目里我见过太多人因为“听说DuckDB很快”就把SQLite从某个稳定服务里换掉结果在写入并发上踩了坑又灰溜溜迁回去。也见过一些人因为拿到了几个GB的数据还死守SQLite每天等查询等到怀疑人生。我希望你看完这篇文章后能少走这些弯路。数据库选型从来不是选“最好”的而是选“最合适”的。下次再有人问“DuckDB能不能干掉SQLite”你可以告诉他DuckDB在分析桌上赢得很漂亮但SQLite在它的主场依然是那个无法撼动的家伙。我个人现在的习惯是凡是拿不准的场景先让两边的查询各自跑一遍看耗时再评估一下生态和运维成本答案基本就出来了。数据不会骗人但前提是你得知道自己手里的数据是什么类型、工作负载长什么样。
返回列表