数据存储使用文件还是数据库,哪个更合适?
使用数据库存储数据很常见,数据库不仅提供了多种存储策略,还可以满足数据一致性的要求,基于数据库还能很方便完成数据计算,但使用数据库存储数据也有一些缺点,在一些场景下并非最佳选择。与数据库相对的是使用文件存储数据,文件系统更加开放,使用起来也很灵活,但文件本身没有计算能力,也无法保证数据一致性,不过,一些场景使用文件存储数据却更加适合。
我们从以下方面来对比一下数据库与文件系统,看看在哪些场景下使用何种技术更合适?
关注点
读写性能
数据存储的目的是使用并创造价值,这里的读写是指应用在使用数据时需要通过文件系统或数据库将数据取出或写入的过程。我们知道,数据库数据出库要通过数据库访问接口,通常是 JDBC 和 ODBC,数据出库的效率与数据库接口密切相关。实测表明,一个 3000 万行(物理大小约为 4.9G)的表 MySQL 首次读取需要 518 秒,而同样的文本读取则只需要 42 秒,相差 12 倍多。数据写入也同样存在差异,数据库写入时间要比文本慢 50 倍。
因此,单纯从读写性能的角度来看,文件要比数据库快得多。
灵活性和易管理性
数据库的结构是扁平的,表建立以后都在同一级(线性结构),最多有个模式勉强算做两层,当表数量较多时就比较难以管理。要区分这些表就要使用严格的命名规则,比如按项目、模块、年代、版本命名,这样的命名规范需要强管理机制,但实际情况是往往由于项目急需上线,表命名规范就顾不上那么多了,时间久了数据库管理非常混乱,有时想删除都不容易,数据表建完经常永久存在(因为不知道那个程序还在用也不敢删)。
而文件可以借助文件系统的树状目录进行管理,不同项目、模块按照树形结构存储,管理和使用都很方便,如果项目下线可以放心大胆地删除对应目录,不用担心跟其他程序紧耦合。
另外,数据库有约束,不仅需要先建表再存储数据,数据类型也很死板,不满足条件的数据就存不进去,灵活性很差,有时一些临时性计算要折腾到数据库再使用就很不方便了;而文件不存在这样的问题,什么样的数据都能存储,灵活性很强,使用也很方便。
可计算性
文件不管是读写还是管理都占优,但是在计算方面就不一样了。前面说过,数据是要使用才能产生价值,从广义上来讲就是计算。数据库不仅能存储,还能计算,不仅能算还挺方便,使用 SQL 这种专门的数据库计算语言处理数据很方便,分组汇总、关联计算简单一句就能搞定。 而文件却不具备这样的能力,要计算文件数据还要借助其他编程语言完成,不同语言的实现难度不同,但大部分都要比 SQL 复杂一些。
数据修改能力
有些业务场景还会涉及数据修改,修改数据库数据相对简单,使用 SQL 语句就可以轻松完成,但文件修改要麻烦得多,无论是查找要修改的记录还是更新数据都不轻松,大多时候还不如重写文件的效率高。不过,像数据分析型(OLAP)场景面对的大多是不再变化的历史数据,不涉及数据修改删除(更多只是追加)使用文件是可行的。
另外,诸如交易系统还会要求数据一致性,数据库有事务管理机制可以满足一致性要求,但文件却不行。不过像 OLAP 等一些场景并没有数据一致性要求,在这类场景下使用文件也是可以的。
总体来看,如果对数据有频繁的修改需求,特别还对一致性有要求的场景(如交易系统),使用数据库更合适;而对于没有数据一致性要求的场景(如分析系统)就可以使用文件存储数据,只是文件没有计算能力,使用传统硬编码方式会比较复杂,如果在计算能力方面有所增强,再借助文件的高效读写能力和灵活易管理特性,那么文件存储将更有优势。
那么有没有办法增强文件的计算能力呢?
使用开源集算器 SPL 可以实现这个目标。
开源数据计算引擎 SPL
SPL 是一款专业的开源数据计算引擎,提供了独立的计算语法,计算能力不依赖数据库。可以基于文件进行计算,让文件拥有计算能力,这样就可以有效弥补文件计算能力不足的问题,同时支持多种数据源进行混合计算。
文件计算能力
SPL 提供了独立的计算语法和丰富的计算类库,可以快速完成结构化数据计算。下面是一些常规运算:
A | B | |
1 | =T("/data/scores.txt") | |
2 | =A1.select(CLASS==10) | 过滤 |
3 | =A1.groups(CLASS;min(English),max(Chinese),sum(Math)) | 分组汇总 |
4 | =A1.sort(CLASS:-1) | 排序 |
5 | =T("/data/students.txt").keys(SID) | |
6 | =A1.join(STUID,A5,SNAME) | 关联 |
7 | =A6.derive(English+ Chinese+ Math:TOTLE) | 追加列 |
除了原生 SPL 语法,SPL 还提供了相当 SQL92 标准的 SQL 支持,对于熟悉使用 SQL 的人员可以直接使用 SQL 查询文件。
$select * from d:/Orders.csv where Client in ('TAS','KBRO','PNS')
复杂些的 with 都支持:
$with t as (select Client ,sum(amount) s from d:/Orders.csv group by Client) select t.Client, t.s, ct.Name, ct.address from t left join ClientTable ct on t.Client=ct.Client
SPL 在处理 JSON/XML 等多层数据(文件)方面也很有优势,如:根据员工订单信息(json)完成计算。
A | ||
1 | =json(file("/data/EO.json").read()) | |
2 | =A1.conj(Orders) | |
3 | =A2.select(Amount>1000 && Amount<=3000 && like@c(Client,"*s*")) | 条件过滤 |
4 | =A2.groups(year(OrderDate);sum(Amount)) | 分组汇总 |
5 | =A1.new(Name,Gender,Dept,Orders.OrderID,Orders.Client,Orders.Client,Orders.SellerId,Orders.Amount,Orders.OrderDate) | 关联计算 |
可以看到,相对其他JSON 库(如 JsonPath)SPL 的实现更简洁。
同样,使用 SQL 也可以查询 JSON 数据:
$select * from {json(file("/data/EO.json").read())} where Amount>=100 and Client like 'bro' or OrderDate is null
SPL 的敏捷语法和过程计算还非常适合完成复杂计算,比如基于股票记录(txt)计算某只股票最长连涨天数 可以这样写:
A | |
1 | =T("/data/stock.txt") |
2 | =A1.group@i(price<price[-1]).max(~.len())-1 |
再比如,根据用户登录记录(csv)列出每个用户最近一次登录间隔:
A | ||
1 | =T(“/data/ulogin.csv”) | |
2 | =A1.groups(uid;top(2,-logtime)) | 最后2个登录记录 |
3 | =A2.new(uid,#2(1).logtime-#2(2).logtime:interval) | 计算间隔 |
这类计算即使基于数据库使用SQL 也很难写,SPL 实现却很方便。
高性能文件格式
文本是很常见的数据存储形式,它具有通用性易读性等优点而被广泛使用。但是,文本的性能却非常差!
文本字符不能直接运算,需要转换成整数、实数、日期、字符串等内存数据类型才可以进一步处理,而文本的解析是个非常复杂的任务,CPU 耗时很严重。一般来讲,外存数据访问的主要时间是在硬盘本身的读取上,而文本文件的性能瓶颈却经常发生在 CPU 环节。因为解析的复杂性,CPU 耗时很可能超过硬盘耗时(特别是采用高性能固态硬盘时)。需要高性能处理较大数据量时通常不会使用文本。
SPL 提供了两种高性能数据存储格式,集文件和组表。集文件是 SPL 提供的二进制数据格式,不仅采用了压缩技术(占用空间更小读取更快),存储了数据类型(无需解析数据类型读取更快),还支持可追加数据的倍增分段机制,利用分段策略很容易实现并行计算,进一步提升计算性能。
组表是 SPL 提供列存、索引机制的文件存储格式,在参与计算的列数(字段)较少时列存会有巨大优势。组表除了支持列存,实现了 minmax 索引外,还支持倍增分段机制,这样不仅能享受到列存的优势,也更容易并行提升计算性能。
SPL 存储的使用也很方便,与文本使用基本一致,比如读取集文件并计算:
A | B | |
1 | =T("/data/scores.btx") | 读入集文件 |
2 | =A1.select(CLASS==10) | 过滤 |
3 | =A1.groups(CLASS;min(English),max(Chinese),sum(Math)) | 分组汇总 |
如果数据量较大,还支持游标分批读取以及多 CPU 并行计算:
=file("/data/scores.btx").cursor@bm()
在使用文件作为数据存储方式时,无论原始数据是何种格式,最后都至少要转存成二进制(如集文件)格式,这样无论在空间占用还是计算性能上都会更有优势。
SPL 完备的计算能力加高性能存储就提供了与数据仓库相当(或超越)的能力,在很多场景下甚至可以替代数据仓库。
多源混合计算
SPL 不仅支持文件计算,还支持其他多种数据源,想到想不到的数据源都能支持。
这样,SPL 就可以进行多源混合计算,特别适合完成 T+0 查询。前面我们提过,文件适合存储不再修改的大量历史数据,而少量实时可能修改的热数据仍然存储在数据库中,如果要全量查询就要跨文件和数据库查询,这时就可以利用 SPL 的跨源计算能力和数据路由功能,根据计算需求选择对应的数据源以及跨数据源混合计算。如冷热数据分离后,基于 SPL 进行冷热数据混查,同时对上层应用透明,实现 T+0 查询。
A | ||
1 | =cold=file(“/data/orders.ctx”).open().cursor(area,customer,amount) | / 冷数据从文件系统(SPL 高性能存储)中取,昨天及以前的数据 |
2 | =hot=db.cursor(“select area,customer,amount from orders where odate>=?”,date(now())) | / 热数据从生产库中取,今天的数据 |
3 | =[cold,hot].conjx() | |
4 | =A3.groups(area,customer;sum(amout):amout) | / 混合计算实现 T+0 |
易集成
SPL 提供了标准 JDBC 和 ODBC 接口供应用调用。特别地,对于 Java 应用可以将 SPL 作为嵌入引擎集成到应用中,使得应用本身就具备基于文件的强计算能力。
JDBC 调用 SPL 代码示例:
... Class.forName("com.esproc.jdbc.InternalDriver"); Connection conn =DriverManager.getConnection("jdbc:esproc:local://"); CallableStatement st = conn.prepareCall("{call splscript(?, ?)}"); st.setObject(1, 3000); st.setObject(2, 5000); ResultSet result=st.execute(); ...
SPL 是解释执行的,天然支持热切换,这对 Java 体系下的应用是重大利好。基于 SPL 的数据计算逻辑编写、修改和运维都不需要重启,实时生效,开发运维更加便捷
易集成与热切换特性还很方便与主流的微服务框架结合,实现服务内的数据处理工作,这样还可以有效减轻原来 Java 实施数据计算的负担。
除了以上优点,SPL 提供的简洁易用的开发环境也可圈可点,单步执行、设置断点,所见即所得的结果预览窗口…,开发效率也更高。
在 SPL 文件计算、高开发效率和高计算性能的支持下,使用文件存储数据可以获得更优的体验(不需要数据一致性时),同时还能结合其他数据源完成混合计算,是后数据库时代很好的选择。