5.4 SQL 数据库的反击
有一种说法称:云计算不再是 SQL 的时代,而是 NoSQL 的时代。因此,不依赖 SQL 且结构简单的 NoSQL 数据库受到了广泛的关注。那么,SQL 数据库真的已经不再那么重要了吗?SQL 数据库真的不支持云计算吗?
“云”的定义
“云”这个词有很多种用法,不过它的定义却非常模糊,导致我们的讨论容易过度发散。为了让论点更加明确,我们在这里将“云”定义为“大规模分布式环境”这个概念。
当然,“云”并非总是代表“大规模分布式环境”,但在数据库系统的语境中,用“云”这个词一般是代表现有数据库系统很难应对的情况。也就是说,数据量和访问量这两者的其中一个,甚至是全部两个,其规模已经超过单独一台数据库服务器所能够应对的程度,必须要依靠由多台服务器协同工作所构成的分布式环境来进行应对。
在这样的环境中,不使用 SQL 的 NoSQL 数据库很受欢迎。像 SQL 这样复杂的查询访问是受限的,取而代之的则是多个 NoSQL 数据库自动分布到多台服务器上的架构。这种方式对大规模分布式环境拥有更强的亲和力。
SQL 数据库的极限
那么,SQL 数据库真的不适合大规模分布式环境吗?在云计算环境中,它真的就不如 NoSQL 数据库吗?事实上并不一定。在云计算环境下最大限度利用现有 SQL 数据库的技术,目前已经在实用化方面取得了一定的进展。
其中一个基本的思路是对数据库进行分割。在专业领域,这种数据库分割被称为 Sharding 或者 Partitioning。这种手法的目的,是通过将数据库中大量的记录分别存放到多台服务器中,从而避免数据库服务器的瓶颈。以 mixi 这样的社交网站(SNS)为例,可以理解为将用户编号为偶数的用户和编号为奇数的用户分别存放到不同的数据库中。这样就避免了对单独一台数据库服务器的集中访问,从而提高了处理速度。第一步的分割可以在应用程序级别上完成,在上述例子中,对编号为偶数和奇数的记录分别访问不同的数据库,而这样的逻辑可以编写在应用程序中。
不过,仔细想想就会发现,其实数据库的分割和应用程序逻辑的本质毫无关系,是需要在数据库层面上解决的问题。将这样的逻辑混入应用程序中的话,说实话是很“拙劣”的。数据库的问题,就应该在数据库中解决,不是吗?像这样能够实现自动分割的方法有很多种,这里我们介绍一下为 MySQL 提供分割功能的 Spider。
存储引擎 Spider
Spider 是由 ST Global 公司的斯波健德(Kentoku Shiba) 先生开发的一种存储引擎。在 MySQL 中,用于查询处理的数据库引擎和实际负责存储数据的存储引擎是相互独立的,对于每张数据表都可以采用不同的存储引擎。可能大家都听说过 InnoDB、MyISAM 之类的名字,这些都是 MySQL 的存储引擎。
Spider 和它们一样,也是在 MySQL 上工作的存储引擎的一种。不过,Spider 自身并不执行实际的数据存储操作,而是将这些操作交给其他的 MySQL 服务器来完成。也就是说,在使用 Spider 的时候,表面上看起来是一个数据库,实际上却可以将数据自动分割保存在多台数据库中(sharding),而且只要对一台数据库保存数据,也会同时在其他服务器的数据库中保存(replication)。比起在应用程序端实现分割来说,用 Spider 来实现有下列这些优点:
· 逻辑和数据库相分离:使用 Spider,就意味着从应用程序端看起来,对数据库的访问和通常的 MySQL 访问是完全一样的。因此,在应用程序端不需要进行任何特殊的应对。
· 可维护性高:和分割相关的信息都只维护在表定义中,而且,数据库分割策略也可以在表定义中进行设置。关于数据库的设置都集中在一个地方,这一点从可维护性的角度来说,是非常重要的。
刚才我们介绍了利用 MySQL 的存储引擎 Spider 进行自动分割的手法。其实实现自动分割的软件不仅只有这一种,单在 MySQL 数据库中,除了 Spider 之外,还有像 MySQL Cluster、SpockProxy 等其他方案。
SQL 数据库之父的反驳
尽管通过 Sharding 技术将数据库进行分割,就能够在分布式环境中运用 SQL 数据库,但却无法做到像一部分 NoSQL 数据库那样,能够根据需要自动增加节点来实现性能的扩充。此外,如果用 SQL 数据库来实现 NoSQL 中这种简单的查询处理,大多数情况下在性能上(如每秒查询数)都不及 NoSQL。
虽说 SQL 和 NoSQL 各自所擅长的领域不同,但很多人曾经认为在大规模分布式环境中使用 NoSQL 是板上钉钉的事。在这个时候,迈克尔·斯通布雷克(Michael Stonebraker,1943— )站了出来。斯通布雷克是最早的 RDB 系统 Ingres 的开发者,在 Ingres 商用化之后,他开发了 Ingres 的后续版本 Postgres,后者演变为现在的 PostgreSQL。斯通布雷克应该被称为 PostgreSQL 之父,但他的贡献并非仅仅如此。由于 Sybase 以及 Microsoft SQL Server 中都继承了他所开发的 Ingres 的代码,因此毫无疑问,他是一个对于 SQL 数据库整体都产生了巨大影响的人物。现在,斯通布雷克担任 MIT(麻省理工学院)客座教授,同时还在几家数据库相关的企业中担任董事。
斯通布雷克在计算机协会 ACM 1的学术期刊《Communications of the ACM》(ACM 通信)2010 年 4 月号中刊登了一篇题为“SQL Databases vs NoSQL Databases”的专栏2。在该专栏中,斯通布雷克以“所有的技术都有其擅长的领域,没有一种数据库是万能的”为前提,提出了以下观点:
1 计算机协会(Association of Computer Machinary,ACM)是一个世界性计算机从业者的专业组织,被誉为计算机领域的诺贝尔奖的图灵奖就是由 ACM 主办的。
2 斯通布雷克的专栏可参见:http://cacm.acm.org/blogs/blog-cacm/50678-the-nosql-discussion-has-nothing-to-do-with-sql/fulltext。(原书注)
· NoSQL 的优势在于性能和灵活性。
· NoSQL 的性能优于 SQL 这一说法,并非在所有情况下都成立。
· 通常认为 NoSQL 是通过牺牲 SQL 和 ACID 特性3 来实现其性能的,然而性能问题与 SQL 和 ACID 是无关的。
3 数据库所应具备的 4 种特性的首字母缩写,即一系列处理不会残留中间状态的 Atomicity(原子性)、不会产生数据不完整的 Consistency(一致性)、对处理中间状态进行隐藏的 Isolation(隔离性)以及对完成的处理进行保存的 Durability(持久性)。(原书注)
说实话,看了这些内容,我的第一反应就是:“唉?真的吗?”。作为像我这样写了很多文章,给别人灌输了“云计算时代非 NoSQL 莫属”观点的人来说,实在是百思不得其解。
那么我们就来看个究竟吧。根据这篇文章,决定 SQL 数据库性能的,是客户端与服务器之间的通信开销,以及服务器上的事务处理开销。而通信开销可以通过将大部分处理放在服务器上的存储过程(Stored Procedure)在一定程度上得以解决。
而对于服务器上的处理,大致进行分类的话,主要有 4 个瓶颈,而对于这些瓶颈的应对就是决定性能的关键。这 4 个瓶颈具体如下。
日志(Logging):为了防止磁盘崩溃等故障的发生,大多数关系型数据库都会执行两次写入。即向数据库执行一次写入,再向日志执行一次写入。而且,为了防止日志信息丢失(为了实现 ACID 中的 D),必须保证这些数据确实写入了磁盘中。这样,即便由于一些问题导致数据库崩溃,也可以根据日志的内容恢复到故障前的状态。然而,考虑到向磁盘写入的速度是非常慢的,因此向日志执行确定的写入操作是非常“昂贵”的。
事务锁(Locking):在对记录进行操作之前,为了防止其他线程对记录进行修改,需要对事务加锁。这也形成了一项巨大的开销。
内存锁(Latching):Latch 是门闩的意思,这里是指对锁和 B 树等共享数据结构进行访问时所需要的一种排他处理方式,斯通布雷克管这种方式叫做 Latching。这也是造成开销的原因之一。
缓存管理(Buffer Management):一般来说,数据库的数据是写入到固定长度的磁盘页面中的。对于哪个数据写入哪个页面,或者是哪个页面的数据缓存在内存中,都需要由数据库进行管理。这也是一项开销很大的处理。
斯通布雷克认为,要实现高速的数据库系统,必须要消除上述所有 4 个瓶颈,而且上述瓶颈并非 SQL 数据库所固有的。听他这么一说,好像还真是这么回事。
NoSQL 之所以被认为速度很快,是因为它在设计之初就考虑了分布式环境,通过多个节点将处理分摊了。然而,SQL 数据库也是可以将处理分摊到多个节点上的。此外,即便是 NoSQL 数据库,只要涉及到磁盘写入操作,以及多线程架构下的缓存管理,也难以回避上述瓶颈中的一个或几个。
通过上面的分析,斯通布雷克的结论是,无论是 SQL 还是 ACID 特性,都不是影响云计算环境下数据库性能的本质性障碍。唔,原来如此。
随后,斯通布雷克还写了一篇博客4,对于与 CAP 原理5 相对应的 NoSQL 数据库策略 BASE(Basically Available, Soft-state, Eventually consistent)阐述了反对意见。这还真是让人大开眼界。
5 在“一致性”、“可用性”和“分裂容忍性”三者中,只能同时满足其中两者。据说这一原理已经通过数学方法得到了证明。而 BASE 是以满足“可用性”和“分裂容忍性”为目标的。(原书注)
SQL 数据库 VoltDB
之前讲的这些,还只是停留在“SQL 数据库在理论上还存在着可能性”这个阶段,但作为技术大牛的斯通布雷克可不会仅仅满足于这样的结论,他已经在数据库业界的最前线活跃了 40 多年,绝对不是一个简单的人物。斯通布雷克在美国一家叫做 VoltDB 的创业型公司任 CTO,该公司将他的上述观点进行了体现,开发出了 VoltDB 数据库系统,并以开源形式发布。这是何等惊人的行动力。
VoltDB 有两个版本,一个是以 GPL 协议发布的开源社区版本(Community Edition),另一个是以订阅形式提供的收费版本。开源版本可以直接从 VoltDB 公司的官方网站获取。
VoltDB 并不是一个像 PostgreSQL 或 MySQL 那样的通用数据库,而是一个面向特定领域(OLTP)进行了大幅度调优的数据库系统。在 VoltDB 的主页上是这样介绍它的:“VoltDB 是面向大规模事务处理应用程序的 SQL 数据库系统。”其特征包括以下几点:
· 比传统 RDBMS 高出几十倍的性能
· 线性可扩展性
· 以 SQL 作为 DBMS 接口
· ACID 特性
· 可 365 天 24 小时全天候工作的高可用性看起来很有吸引力对吧?
不过,到底是用了怎样的手段才实现了这样的特性呢?尤其是斯通布雷克在博客中指出的那四个瓶颈,到底是用什么办法来解决的呢?
首先,VoltDB 最大的特征在于,它是一个内存数据库系统。也就是说,数据基本上是储存在内存中的。由于数据存储在内存中,缓存管理的问题就得以解决,而且由于不存在磁盘崩溃的情况,也就不需要日志了。这样一来,四个瓶颈中一下子就解决了两个。
等等,先别高兴太早。电源一关,内存中的数据就消失了,这样的数据库岂不是无法提供 ACID 中的持久性这一特性吗?其实,在 VoltDB 中,持久性是通过复制(replication)的方式来维持的。VoltDB 数据库是在由多台服务器组成的集群上工作的,在集群中的多台服务器上都保存有重复的数据副本,因此即便失去一台服务器,也不必担心数据会丢失。此外,VoltDB 也提供了定期将数据写入文件的快照功能。
那么,剩下的两个瓶颈,即事务锁和内存锁又是如何解决的呢?在 VoltDB 中,数据库是分割成多个分区(partition)来管理的,对于每个分区都分配了一个独立的管理线程。也就是说,对分区的操作是单线程的,因此也就从根本上不需要用于实现排他处理的事务锁和内存锁机制了。
VoltDB 通过这样的构造回避了瓶颈,其每秒事务数(TPS)可以跑出比传统 RDBMS 高 50 倍的成绩(根据 VoltDB 公司的测试数据)。怎么说呢,这是拿一般的磁盘写入型数据库和内存型数据库来比较,有这种程度的差距也许并不意外。此外,由于上述比较是在一台服务器上进行的,而 VoltDB 可以通过增加节点来使性能呈线性提升,也就是说,如果将服务器数量翻倍,则性能也几乎可以翻倍,因此还是非常值得期待的。
VoltDB 的架构
VoltDB 采用以内存型集群运用为前提的架构,光这一点就和传统的 RDBMS 架构大相径庭,但它们之间的差异还远远不仅限于此。没有人知道,依靠打破 RDBMS 常识的想象力,向超高速的实现发起挑战的斯通布雷克,在这条路上到底能走多远。
大家应该还记得,在上述“4 个瓶颈”之前,曾经讲过“通信开销过大的问题,通过存储过程可以在一定程度上得到解决”。在传统的数据库中,有专门负责数据存储的数据库服务器,而应用程序是通过 SQL 来进行查询的(图 1)。而对于需要重复进行的操作,可以通过某种手段,调用服务器上事先写好的存储过程来实现,从而在一定程度上减少通信量。存储过程的实现方式在各种 RDBMS 上都有所不同,有的是用 C 等语言来编写并载入到服务器上,也有的是将 SQL 进行扩展来编写存储过程。
图 1 传统的 RDBMS 架构
不过,“走极端”的 VoltDB 可不是光有存储过程就满足了的。“既然存储过程可以改善性能,那么把所有的事务都用存储过程来实现不就好了吗?”于是,在 VoltDB 中,对服务器的调用只能运行事先编写好的过程。也就是说,VoltDB 虽然是一个 SQL 数据库,但却无法从客户端来执行 SQL 查询(实际上貌似是可以的,只是不推荐而已)。这是何等的大刀阔斧。从结果来看,对 VoltDB 的访问不能使用现在主流的 ODBC 和 JDBC 方式,因为这些方式都是通过 SQL 来调用 RDBMS 功能的。要对 VoltDB 进行访问,需要用 Java 来编写程序。
也就是说,在 MySQL 等现有 RDBMS 的结构中包括通用的数据库服务器,客户端用 SQL 对该服务器进行访问,而在 VoltDB 中,访问数据库的过程本身是作为存储过程保存在数据库服务器中的,而客户端采用一种类似远程调用的方式对该存储过程进行调用(图 2)。换句话说,数据库服务器与客户端的界限,在位置上有所不同。
图 2 VoltDB 架构
VoltDB 中的编程
接下来,我们来看看在这一极端设计原则下,VoltDB 的编程到底是如何进行的。
VoltDB 的应用程序,基本上是采用图 3 这样的结构。要构筑一个应用程序,需要准备下列部件。
图 3 VoltDB 应用程序结构
1. 数据库结构定义:用于定义数据库的 SQL 文件,内容基本上是 SQL 的 CREATE TABLE 语句。此外,CREATE INDEX 和 CREATE VIEW 也是可以使用的。
2. 存储过程:用 Java 编写的存储过程。关于存储过程的内容我们稍后会详细讲解,基本上是负责构造 SQL 语句并执行它。当然,由于存储过程采用 Java 来编写,自然可以在服务器端执行更加复杂的逻辑。
3. 工程定义:一个名为 project.xml 的 XML 文件。在这个文件中定义了数据库定义文件名、存储过程名、数据库分割基准字段等信息。
4. 客户端代码:客户端的源代码,用 Java 编写。对 VoltDB 的访问可以使用 org.voltdb 包所提供的功能。
将上述 1 至 3 的信息提交给一个叫做 VoltCompiler 的程序,就可以生成叫做“目录”(catalog)的服务器端程序(jar 文件)。此外,VoltCompiler 中还需要指定下列内容:
(a) 构成集群的节点数量
(b) 每个节点的分区数量
(c) 集群“首领”的主机名
上述信息需要在编译时指定,这意味着在 NoSQL 中十分常见的在运行时根据需要添加节点的动态可扩展性,这在 VoltDB 中是无法实现的。
在文档中,关于改变集群节点数量的步骤是这样写的:
1. 将数据库输出至文件
2. 修改节点数并重新生成目录
3. 重新启动数据库
4. 从文件向数据库载入数据
如果要对数据库结构定义进行修改,也需要按照上面的步骤来进行操作。
如果你习惯了 MongoDB 等 NoSQL 数据库的灵活性,就会觉得仅仅是为了修改结构定义和集群节点数就要这样操作实在是太麻烦了。不过与此同时,文档中也写道:“VoltDB 在 2 到 12 个节点的环境下能够发挥最大效率,用 10 个节点就可以实现 100 万个 QPS(每秒查询数量),大多数情况下这样的性能已经可以满足需要”。也就是说,用少量的节点数量就能实现压倒性的性能,因此灵活性也就显得不那么重要了。从某种意义上说,这样的思路和 NoSQL 正好是相互对立的两个极端。
Hello VoltDB!
下面我们来看看实际的 VoltDB 程序。图 4 到图 8 就是 VoltDB 的 Hello World 程序。具体来说,就是用 Insert 存储过程将语言名称和该语言对应的 HelloWorld 写入数据库,然后用 Select 存储过程读出语言名称对应的 HelloWorld。
图 4 的数据库结构定义是一个简单的 SQL 中的 CREATE TABLE(创建表)语句,没有什么难点吧。图 5 和图 6 是用 Java 编写的存储过程定义。
CREATE TABLE HELLOWORLD ( HELLO CHAR(15), WORLD CHAR(15), DIALECT CHAR(15) NOT NULL, PRIMARY KEY (DIALECT) );图 4 数据库结构定义(helloworld.ddl)
import org.voltdb.*; @ProcInfo( partitionInfo = "HELLOWORLD.DIALECT: 0", singlePartition = true ) public class Insert extends VoltProcedure { public final SQLStmt sql = new SQLStmt( "INSERT INTO HELLOWORLD VALUES (?, ?, ?);" ); public VoltTable[] run(String hello, String world, String language) throws VoltAbortException { voltQueueSQL(sql, hello, world, language); voltExecuteSQL(); return null; } }图 5 Insert 存储过程(Insert.java)
import org.voltdb.*; @ProcInfo( partitionInfo = "HELLOWORLD.DIALECT: 0", singlePartition = true ) public class Select extends VoltProcedure { public final SQLStmt sql = new SQLStmt( "SELECT HELLO, WORLD FROM HELLOWORLD " + " WHERE DIALECT = ?;" ); public VoltTable[] run(String language) throws VoltAbortException { voltQueueSQL(sql, language); return voltExecuteSQL(); } }图 6 Select 存储过程(Select.java)
VoltDB 的存储过程定义包括下列要素:
· 对 org.voltdb 包的 import
· 用 @Procinfo 指定分区
· 定义一个继承 VoltProcedure 的类
· 定义存储过程本体的 run 方法
其中,有必要对 @ProcInfo 部分做一些说明。VoltDB 是将数据库分割成分区,并分布式地配置在集群中的节点上的。当存储过程所进行的操作仅限于对单一分区进行访问时,VoltDB 可以发挥最佳性能。因此,如果要在编译时指定某个存储过程是否仅访问单一分区,以及该分区的分割基准是哪张表的哪个字段,就需要使用 @ProcInfo 记法。
project.xml 中记载了关于数据库结构、存储过程、分区等信息。
<?xml version="1.0"?> <project> <database name='database'> <schemas> <schema path='helloworld.sql' /> </schemas> <procedures> <procedure class='Insert' /> <procedure class='Select' /> </procedures> <partitions> <partition table='HELLOWORLD' column='DIALECT' /> </partitions> </database> </project>图 7 工程定义(project.xml)
客户端代码则比较简单。基本上,对数据库的访问,只是使用 callProcedure() 方法,对 project.xml 中定义的存储过程进行调用而已。关于获取结果等操作,看一下图 8 中的示例代码,应该就能有个大致的理解了。
import org.voltdb.*; import org.voltdb.client.*; public class Client { public static void main(String[] args) throws Exception { /* * Instantiate a client and connect to the database. */ org.voltdb.client.Client myApp; myApp = ClientFactory.createClient(); myApp.createConnection("localhost", "program", "password"); /* * Load the database. */ myApp.callProcedure("Insert", "Hello", "World", "English"); myApp.callProcedure("Insert", "Bonjour", "Monde", "French"); myApp.callProcedure("Insert", "Hola", "Mundo", "Spanish"); myApp.callProcedure("Insert", "Ciao", "Mondo", "Italian"); myApp.callProcedure("Insert", "こんにちわ", "世界", "Japanese"); /* * Retrieve the message. */ final ClientResponse response = myApp.callProcedure("Select", "Spanish"); if (response.getStatus() != ClientResponse.SUCCESS){ System.err.println(response.getStatusString()); System.exit(-1); } final VoltTable results[] = response.getResults(); if (results.length != 1) { System.out.printf("I can't say Hello in that language."); System.exit(-1); } VoltTable resultTable = results[0]; VoltTableRow row = resultTable.fetchRow(0); System.out.printf("%s, %s!¥n", row.getString("hello"), row.getString("world")); } }图 8 客户端代码(Client.java)
性能测试
在 VoltDB 的官方网站(https://voltdb.com/blog/key-value-benchmarking)中,刊登了与 NoSQL 的代表 Cassandra 进行对比的性能测试结果。不过,需要注意的是,VoltDB 和 Cassandra 在擅长处理的对象方面是有所不同的。
例如,VoltDB 是内存型数据库,Cassandra 则不是。Cassandra 无需定义数据库结构,比较灵活,VoltDB 则不是。在对比中需要注意上述这几点。
VoltDB 和 Cassandra 在以下三种性能测试中进行了对比。
单纯键 - 值:VoltDB 具备用于实现键 - 值存储所需的充足性能,因此对用 VoltDB 实现的键 - 值存储与 Cassandra 进行了对比。测试内容为用 50B 的键和 12KB 的值进行 50 万对的访问和更新。在单节点、3 节点(无复制)和 3 节点(有复制)这三种条件下对比 5 分钟的处理总数。
多个整数列:用 50 个 32 位整数作为值来代替单纯键 - 值进行对比。对比条件依然是单节点、3 节点(无复制)和 3 节点(有复制)三种。
多个整数列(批处理):依然是用 50 个 32 位整数作为值,但不同的是对每个键进行 10 次访问和更新。对比条件依然是单节点、3 节点(无复制)和 3 节点(有复制)三种。
上述 3 次测试的结果如表 1 至表 3 所示。虽然集群的构成只有 3 个节点,规模比较小,但在对比的范围内,最好的情况下可以跑出相当于 Cassandra 性能 16 倍以上的成绩。不过,这个测试的目的,是为了证明使用 SQL 的数据库在云计算环境下也并不慢,而并不代表对大家接下来开发的应用程序数据库来说,VoltDB 就是最佳选择,这一点请大家注意。
表1 键-值性能测试(5分钟事务数量)
集群配置
VoltDB
Cassandra
性 能 比
1 节点
17000
7940
2.2 倍
3 节点(无复制)
19800
17400
1.1 倍
3 节点(有复制)
12600
4450
2.8 倍
表2 多个整数列性能测试(5分钟事务数量)
集群配置
VoltDB
Cassandra
性 能 比
1 节点
111000
24200
4.6 倍
3 节点(无复制)
293000
38900
7.5 倍
3 节点(有复制)
176000
24700
7.1 倍
表3 多个整数列(批处理)性能测试(5分钟事务数量)
集群配置
VoltDB
Cassandra
性 能 比
1 节点
102000
13300
7.7 倍
3 节点(无复制)
286000
17200
16 倍
3 节点(有复制)
172000
13000
13 倍
小结
VoltDB 是一种内存型数据库,且客户端无法直接调用 SQL,这与传统的 RDBMS 在设计思想上有很大差异。然而相对地,它却展现出了优秀的性能,并可以通过增加节点数量,实现与 NoSQL 相当的线性扩展。
不过,由于数据基本上都是保存在内存中的,因此其容纳的数据总量会受到服务器安装的内存容量的限制。此外,虽然它具备复制和快照功能,但由硬件故障造成数据丢失的危险性,感觉比传统的数据库要高一些。而且,在数据库结构、构成集群的节点数量等系统结构的灵活性方面,和 NoSQL 等相比依然稍逊一筹,因此必须从一开始就对数据库结构进行精确的设计。
虽然性能很高,但却很难掌控,从这个意义上来看,VoltDB 可以说是数据库中的“方程式赛车”了。VoltDB 才刚刚诞生不久,今后也有进行诸多改善的计划。据说在这里提出的数据库结构和集群结构缺乏灵活性这一点,也将得到改善,在操作接口上也考虑支持 JSON 等等。VoltDB 可以说是今后值得期待的一种数据库。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论