为什么用关系型数据库?最常见的理由是别人在用,所以我也得用,但是这个并不是理由,而是借口。

1 数据分类

选择数据存储类型前,先分析数据特点,才能针对性选择存储方案。

通常按数据与数据之间关系的复杂度对数据分类。最简单的是数据之间没关系,如常见的市场数据。复杂一点的是数据之间有单向关系,这些关系形成一个树状结构。最复杂的是网状结构的数据,也叫图数据类型。

虽然这些数据在金融系统里都有,但重要性和频率都不一样,所以在做存储选型的时候也有不同的考量标准。

按照数据出现的频率,数据大体可以分成这样三类:图数据类型、没有关系的数据类型和树状数据类型,它们分别对应了图数据库、时序数据库和关系型数据库。接下来我们就分别看看。

2 图数据库

存的是图。除了提供数据的存储以外,还支持图查询,如常见的相邻关系查询,或者连通关系查询。

但金融行业里很少有图这种类型的数据结构。主要是因为图是一种非结构化数据,而金融业务里处理的数据都要有清晰结构,金融数据本质就不是非结构化数据类型。

一般出现那些跟数据分析相关部门。如和客户进行业务往来之前,先要:

  • 对客户进行背调(KYC,Know Your Customer)
  • 或查看用户存在洗钱行为(AML,Anti-Money Laundering)

这就需要分析客户的社会关系和财务状况,用图表示这些彼此关联的信息。

3 时序数据库

金融市场数据一般都有时间?平时在新闻里听到的和金融市场相关的数据,如大盘、汇率、指数等都指某特定时间点数据。这些带有时间的数据有特殊的存储方式,叫时序数据库。

关系型数据库也可用来存储时间序列数据,但慢一些。为什么时序数据库更快?

3.1 行存储和列存储

普遍采用列存储,关系型数据库一般都用行存储

存储外汇信息,至少处理时间、汇率币种、买价格、卖价格和平均价格。下面这幅图展示了连续3个时间点的外汇信息:

金融业务的数据存储选型_数据

从数学上说是个矩阵,有两个维度。但存储设备只有一维的地址,不是二维的,所以要把这个矩阵从二维变为一维,才能存储到磁盘。

**关系型数据库采用降维方法是将矩阵横向切割。**每行作为一个整体存储,行与行之间挨着。

就像下面这幅图展示的存储方式一样,外汇信息被分为3个单位存储,每一行是一个单位:

金融业务的数据存储选型_关系型数据库_02

这样存储似乎看起来也可以。问题在于在进行数据查询的时候,需要将每行作为一个整体从文件上加载到内存,这样会拖慢速度。如想算这3个时间点对应买入价格的平均值。你要将这3个时间点所有数据都加载到内存,才能完成计算。

列数据库降维方式是将矩阵纵向切割。同样的外汇信息,被分为5个单位存储,每列是个单位:

金融业务的数据存储选型_关系型数据库_03

计算这3个时间点对应的买入价格的平均值,只需加载上面这幅图粉红色部分。大部分数据都不需加载内存,节省大量读取时间。对于金融市场数据,时序数据库是一种更有效存取方式。

为什么会出现这种情况?

数据的业务性。金融市场数据和金融业务数据不一样。**市场数据一般是业务处理结果。**比如你看到的股票价格信息是股票交易所进行买卖撮合后的结果,外汇信息是外汇交易之后的结果,利率、指数等等也都是这样生成的。既然市场数据是业务处理的结果,那它就不是业务问题。

关系型数据库最开始为解决业务问题。业务共同的特点是需对单业务数据进行完整读写。在关系型数据库里,一个业务一般用一行,因此数据库在进行存储优化的时候,选择优化行的整体读取能力。

而金融市场数据不是业务数据,并不太适合用关系型数据库处理,所以我们在选择存储金融市场数据的时候,会优先选择基于列存储的时序数据库。

3.2 KDB

不仅是个数据库,它还有自己的编程语言Q和K。其中K源自于一个编程语言叫作A+。A+是KDB作者在摩根士丹利的时候发明的一种编程语言。A+又来源于一门数学编程语言叫作A。和Lisp一样,都属于函数式编程语言,所以你在使用KDB的时候会看到很多Lisp的身影。Q是函数式编程语言,所以它里面的数据都不允许修改,修改会返回新的结果。它也假设函数没有随机性。在数据不允许修改和函数没有随机性的情况下,每个函数就可以当作是一个Map。这个Map的键是函数的参数,Map的值是函数的返回值。这样就让函数和Map得到了统一。

Q的另一个设计是统一了Map和关系型表。表的列名是Map的键,表每一列的值是Map的值。表和Map之间的转化是通过 ​​flip​​ 操作来进行的。

KDB/Q也是个列存储的数据库。KDB确是按照列数据库设计的,磁盘操作非常快。KDB不仅数据存储快,它的数据操作也快。

比如在前面讲到的例子中,3个时间点价格平均值的计算。如果是你用编程语言实现,可能会用一个循环来求和,然后求平均值。

由于KDB知道每一列的数据类型都是完全一样的,它在计算的时候会用到CPU的向量指令,用一个指令来完成多个数据的同时处理。这一点使得KDB在处理金融数据时有极高的处理速度,而这种效果正是KDB通过实时编译Q语言来实现的。

为了处理的速度更快,KDB采用单线程运行模式,避免线程切换和同步锁开销。由于KDB在IO和CPU的速度都很快,在金融行业里对计算速度要求高的领域有广泛的应用。

何时选择KDB

主要数据量问题。**KDB适用的数据量范围是GB~TB间。**比如你的金融市场数据在几十G左右的话是完全没有问题的。同时,KDB会大量使用内存,因此内存尽量大一点好。

缺点

学习门槛高。KDB的Q和Lisp一样是函数式编程语言,市面上会的人不多,教材和文档也比较缺乏。

太贵,只有顶级的金融公司才能承担得起。而且需要整个团队进行周边工具的开发,这就是一笔很高运营成本。

不过,我们一直强调在金融行业要讲究投资回报比,而不只是价格。虽然KDB成本这么高,但是一旦学会了就能有很快的开发速度和运行速度,在每秒几千万上下的金融市场往往能有奇效。

KDB一直以来都在很专业的领域内发展,比如金融和医药等。这些年来互联网行业的列数据库也越来越成熟,比如现在风头正盛的ClickHouse,里面的技术和KDB大同小异。

行业技术的出圈和彼此融合值得我们高兴,在这里我也希望当不同行业的解决方案在进行碰撞的时候,你能够独立思考特殊的方案是如何解决行业的特殊问题,这样你才能形成自己的架构。

双时序数据库

实现和时序数据库完全不同,适用场景也不同。由于多了一个时间维度,就不能按列存储。

其实我在第6节课的思考题里,已经给你提示了双时序数据库的存储空间复杂度和时间复杂度,这些复杂度并不低。而且,当你把内容加载到内存之后,会发现无法使用CPU的向量指令来加速运算。

这都导致双时序数据库不适合吞吐量特别高的业务,如股票和外汇业务这些高频交易类业务。适合交易量稍小一些的场外交易类业务,像债券、期货、资产证券化等。

核心组件代表了公司的核心竞争力,需要自己研发。双时序数据库对于大型金融公司来说就是核心竞争力,所以外界很少知道。实现双时序数据库的挑战主要在时间索引的生成和查询。

4 关系型数据库

4.1 对象关系阻抗不匹配

关系型数据库争议点主要在和OOP冲突。学术界甚至有个专业名词来形容这种冲突:对象关系阻抗不匹配(Object relational impedance mismatch)。

OOP的所有对象之间的关系形成一个图,因此研究需要用图论。而关系型数据库的模式(schema)基于关系代数(Relational Algebra),是一系列同构(Homomorphic)的列表组成的集合(Set),因此用集合论。

其实你将对象存储到关系型数据库的过程,就是一个将图论翻译到集合论过程。因为这是两个关系不大的数学理论,所以你在翻译的时候会觉得很不自然。因此,这两者不匹配的原因是图论和集合论区别。

数据封装

也有区别。oop隐藏类实现细节,只向外界暴露行为或接口,类与类之间通过接口来进行交互。但是关系型数据库会暴露所有内部细节,你在数据库里看到的是所有数据最原始的表现形式。数据库的表与表之间交互是原始数据的直接交互,没有任何抽象出来的行为或者接口。

所以面向对象编程里有对象和行为,而关系型数据库里只有数据,这两者有本质的区别。

虽然面向对象编程和关系型数据库里都有数据,但是它们的数据并不一样。面向对象编程里的对象本身也是数据,这是一个更高级和复杂的数据。而数据库里存储的是基本数据格式。这两者的数据抽象程度不一样

面向对象编程有公有和私有属性,有访问权限,还有一致性校验和继承。所有这些都不能直接反映到关系型数据库里。

所以在日常开发中我们不得不使用一些奇技淫巧来强行将业务对象存储到关系型数据库里。时间久了大家也会试着解决这个对象关系阻抗不匹配的问题,所以就有NewSql(以前叫NoSql)。

树状数据存储

大多业务数据之间不是图关系,而是树状结构。这颗树的根节点是业务交易,交易的对象和细节作为子节点一步步向下展开,这种结构为雪花(snowflake)。

NewSql诞生解决两个问题:

  • 高并发和高流量
  • 树状数据的存储问题。最开始学术界主推XML存储格式,但没流行起来,被后来工业界推行的JSON取代

NewSql里,OOP的对象可作为一个原子单元存储,解决大多数对象关系阻抗不匹配问题。

虽然NewSql解决对象存储问题,但没完美解决对象查询问题。NewSql普遍采用分布式架构设计,最终一致性甚至分布式事务在解决二级索引一致性上有非常大时间开销,因此二级索引一般会采用最终一致性的实现方式,这样导致查询不准。这也是金融行业对于NewSql一直采取观望态度原因。

查询不准有问题吗?

如你继续沿用现在关系型数据库的同步处理思路,肯定有问题。但如你按异步架构思路解决业务问题,在一些特定领域也存在应对办法。

异步处理会增加架构难度,而关系型数据库之所以成为金融行业万金油,主要因为事务极大简化架构难度。ROI角度,只有在业务量大到逼迫金融公司使用分布式数据存储方案,才会升级到异步处理架构。

总结

选择存储类型前先要对数据类型分类。按数据之间关系的复杂度,金融数据分为图数据类型、没有关系的数据类型和树状数据类型,它们分别对应了图数据库、时序数据库和关系型数据库。

因为金融业务需要准确地定义数据,所以很少用到图的数据结构。一般会在风控和反洗钱领域用到图相关的工具。

金融市场数据一般使用时序数据库。相比关系型数据库常用的行存储方式,时序数据库用了列存储的方式,这个方式在存储、读取和计算上都有很大的速度优势。KDB是金融行业的专用列存储数据库,它具有更高的执行效率。双时序数据库适合交易量稍小的场外市场业务,一般是金融公司自研。

关系型数据库和面向对象编程之间有天然的矛盾。现在的NewSql在解决对象存储方面有更多优势,但是由于NewSql普遍采用了分布式架构,在使用的时候我们需要小心处理异步处理和最终一致性等关系型数据库不存在的问题。