[[本文摘自即将出版的图书《Development&Deployment of Multiplayer Online Games》第 2 卷“Beta”版第 11(f) 章。目前这本书正处于 Beta 测试阶段,Beta 测试意在改善图书质量,并为协助改善本书的热心人提供免费的“可发行”电子版本,更多详情请参阅“图书的Beta 测试”。Beta 测试过程中发布的所有内容可能会在图书正式出版前有所改动。
若要完整浏览本书目录,请参阅 Development&Deployment of MOG:内容目录。]]
我们经常需要为自己的 OLTP(事务 / 运营)数据库选择适合的 RDBMS(关系型数据库管理系统)。虽然通过编写可移植的 SQL 可以暂时避免进行这样的选择,但迟早要做出这样的选择,至少需要进行这样的尝试(例如意识到具体的选择不够明确,因此决定选择跨 RDBMS 的 SQL)。
生产环境 OLTP 选择 RDBMS 的标准
在发起“到底哪个 RDBMS 最好”的圣战之前,也许需要首先明确一下对于 24x7 运行的生产级 OLTP RDBMS,到底需要具备哪些必不可少的功能。
基于锁,或是基于 MVCC
考虑到并发性,目前几乎所有 RDBMS 无外乎基于锁的(Lock-Based),或基于 MVCC(多版本并发控制)的。从写负载更重的 OLTP 处理角度来说,我曾经见到过:
- 对于读写混合型负载(例如 OLTP 事务和报表),基于 MVCC 的 RDBMS 表现会比基于锁的略好一些。
- 如果使用高于 Read Uncommitted 的隔离级别,效果还会更好,最适合用于读取 / 报表用途。
- 另一方面,OLTP 很少会遇到大量并发读取的情况,如果真的遇到这种情况,通常可通过副本(Replica)执行此类读取,因此不会造成太严重的问题。
- 对于大部分情况下以写入为主的 OLTP(没有太多报表需要创建),基于锁的 RDBMS 要比基于 MVCC 的表现略好一些。
- 然而如果能让 OLTP 工作负载使用 INSERT 代替 UPDATE,此时 MVCC 的效率更高。
另外有必要注意,如果使用了单一写入连接(Single-write-connection)数据库架构,基于锁和基于 MVCC 的 RDBMS 之间的逻辑差异将显得微乎其微(尽管性能略有差别,但其他方面几乎相同,基于锁的 RDBMS 通常略微领先一些)。
ACID 保障
对于 OLTP 数据库,我们需要为涉及多行和多表的事务提供 ACID 保障。
如上所述,对于 OLTP 数据库,我们需要为事务提供全面的 ACID 保障。更重要的是,需要保障涉及多行和多表的事务具备 ACID 特性。虽然这一规则也有例外,但这种例外情况实际上极为罕见。
这几乎已自动将 MySQL+MyISAM 用作 OLTP 数据库的可能性彻底排除在外。但是也要注意,MySQL+ISAM 可能是少数应用(例如作为快递追踪系统或系统监视工具的后端)的好选择,但并不适合涉及某类与金钱有关信息的常规 OLTP 处理。
此外 RDBMS 提供的 ACID 保障差不多等同于意味着需要使用数据库日志,同时也意味着一旦 RDBMS 崩溃,随后需要通过数据库日志进行自动恢复(并自动前卷(Rollforward))。
支持 24×7 运行
作为联机备份的备选方案,可使用异步主从复制(Replication)
我们需要的另一系列功能主要与 24×7 不间断运行有关(例如游戏服务器,总得全天候运行对吧)。这些功能包括:
- 联机备份。无论做什么都肯定需要备份,24x7 不间断运行更是少不了联机备份。
- 通常来说,联机备份意味着需要具备“日志前卷”能力。大部分时候是这样工作的:创建两个数据库,一个作为“主”,一个作为“从”,随后从“主”获取日志文件并发送给“从”,然后在从数据库上进行“前卷”。
- 此外有些数据库可以对处于“日志前卷”状态下的“从”数据库执行只读请求(实际上等同于创建了一个只读从副本)。然而其他一些 RDBMS 不能处理这样的请求(例如需要首先完成“日志前卷”操作才能让从 RDBMS 能够接受查询操作)。
- 作为联机备份的备选方案,也可以通过异步主从复制获得近乎完全同步的备份副本(这种做法对 MySQL+InnoDB 是一种尤为有趣的选项)。
- 需要注意,这样的副本也许可以或无法支持联机备份 + 前卷那样的“时点”恢复(具体情况请参阅文档)。虽然只在从一些非常糟糕的情况下恢复时需要“时点”恢复(实际生产环境中我从未遇到需要这种恢复的情况),不过真遇到这种糟糕的情况至少也能助我们一臂之力。
- “即时”的 ADD COLUMN 语句。我们可能需要对生产环境的数据库进行扩展,这一点是确定无疑的。大部分时候这是通过 ALTER TABLE… ADD COLUMN 语句实现的。面对 ADD COLUMN 语句,很多 RDBMS 会简单地将整个表重写为新格式的行。如果表包含 10 亿行,这一过程可能需要数小时??(在进行复制的过程中,整个表将完全无法访问,导致数据库在数小时内无法使用??)。让 ADD COLUMN 能够近乎即时(考虑到表的大小,可能需要几毫秒的时间)执行完成并不需要什么艰深的技术,有些 RDBMS 也确实能做到这一点,但是也不能忽视,这并不是一种普遍特性??。
- 预算不足时的备选方案是实现无锁 ADD COLUMN(以及常规的 ALTER TABLE),方法如下:
- 用新的结构创建“影子”表
- 通过触发器将对当前表的所有改动写入影子表
- 将数据从当前表复制到影子表(一定要忽略已存在的行,因为触发器已经在其中写入内容了)
- 用影子表取代当前表 - 这种“廉价”的 ADD COLUMN 方法相当繁琐(全过程会对性能产生极大影响),但如果没有其他更好的方法,这种做法至少可以起到一定的效果。
- “即时”ALTER COLUMN(字段拓宽,Widening field)也是个很好的功能,但因为字段拓宽可通过 ADD COLUMN 模拟,因此显得并不是太重要。
- 联机表优化。这个功能需要介绍一下。由于 RDBMS 会不断修改表内容,表的性能会逐渐退化(实际上取决于所用存储引擎,从“行溢出(Overflow row)”到“死行(Dead row)”,我们会遇到各种不希望出现的情况)。为此有必要进行一定的优化(例如 InnoDB 的 OPTIMIZE TABLE,DB/2 的 REORG TABLE,Postgres 的 VACUUM 等),并且我们会需要联机完成这些操作(无需让整个数据库彻底停摆,因为对包含数百万行内容的表进行优化通常需要花很长时间)。
- 大部分时候,此类优化需要创建“影子副本”(由数据库自行创建,这总好过需要我们手工创建),这也意味着需要额外的存储空间。不过至少有一个 RDBMS 提供了“原地”表优化功能。
- 预算不足时的备选方案是实现无锁 ADD COLUMN(以及常规的 ALTER TABLE),方法如下:
- 容器的重新平衡。虽然不像上文列出的其他问题那么重要,但我始终认为“容器的重新平衡”也是 RDBMS 需要考虑的一个重要问题。简单来说,这个问题主要出现在添加存储数据的新硬盘(这种情况时有发生),以及通过将数据分散在所有硬盘上实现提速时。此时可通过下列两种方法之一实现:(a) 使用 RAID-10(这样就无需考虑数据库存储数据的方式了),以及 (b) 通过多个 RAID-1 磁盘使用数据库容器(因为数据库本质上采用了类似 RAID-0 的工作原理)。只要无需添加新硬盘(实际上通常在添加时,为了实现冗余往往会成对增加硬盘),所有系统基本上是均等的,然而在添加了一对新硬盘后,我们需要对硬盘进行“重新平衡”,借此实现负载的重新平衡,这一“重新平衡”的工作分别是由 RAID 或数据库进行的。RAID 级别的重新平衡对服务器性能的影响远大于数据库级别的重新平衡(尤其是有些情况下系统甚至完全无力承担 RAID 级别重新平衡过程中产生的负荷)。因此我更乐于选择使用由数据库管理的容器(会在增加容器后重新平衡,整个过程会保持尽可能平缓)。
- 通常来说,联机备份意味着需要具备“日志前卷”能力。大部分时候是这样工作的:创建两个数据库,一个作为“主”,一个作为“从”,随后从“主”获取日志文件并发送给“从”,然后在从数据库上进行“前卷”。
随着 RDBMS 不断修改表内容,表的性能会逐渐退化
性能
不幸的是,缺乏具体用例情况下进行的数据库性能评测其实没有任何意义
当然,性能(尤其是写性能)对 OLTP 数据库至关重要。不幸的是,缺乏具体用例情况下进行的数据库性能评测其实没有任何意义??。因此我只能尽量介绍一些与性能有关的知名 RDBMS 架构功能及对某些功能的误解。
SQL 编译器的提示
人类从不以史为鉴,这本身就是最重要的“鉴”。
— 奥尔德斯·赫胥黎 —
在向 RDBMS 提交 SQL 语句时,语句会被编译为“执行计划”。而(无论数据库开发者怎么想或数据库产品的销售人员怎么说)这样的编译器时不时总会出错??。例如下面列出了一个常见的此类错误:
- 我们正在使用基于统计信息(即基于成本)的 SQL 编译器。
- 有一个很大的历史表,其中包含一个 TIMESTAMP 字段(很常见的情况)。
- 统计信息恰好有些陈旧,例如晚了几小时 / 几天(总是会这样)。
- 我们正在编译的 SQL 语句会获取“T= 上一小时”之后的数据。
- 此时 SQL 编译器查看统计信息发现我们所请求的 T 之后没有任何数据,并决定针对上一小时的数据进行索引扫描(并预期到将会读取回 0 行数据)。
- 其实还有其他(实际上更好的)执行计划(基于其他索引),但 SQL 优化器(预期会通过索引扫描取回 0 行数据)决定使用基于时间的索引。
- 然而在上一个小时里产生了几百万笔事务,导致这次索引扫描工作需要耗费极长的时间??。
- 此时 SQL 编译器查看统计信息发现我们所请求的 T 之后没有任何数据,并决定针对上一小时的数据进行索引扫描(并预期到将会读取回 0 行数据)。
为解决此类(以及其他类似)灾难,人们发明出一种所谓的“SQL 编译器提示(Hint)”功能。“提示”可供我们强制 RDBMS 使用我们选择的执行计划,(对于 99% 的 OLTP 语句实际上都可以实现确定最优化的执行计划)。
如果有人告诉你“嘿,数据库总是比你更了解实际情况”,你可千万别相信(通常确实如此,但总是这个词用的有些太绝对了)
另外,如果有人告诉你“嘿,数据库总是比你更了解实际情况”,你可千万别相信(通常确实如此,但总是这个词用的有些太绝对了)。在现实世界中,除了像上文提到的这种“壮烈”的优化器失败,还有另一个有趣的故事。在 RDBMS 的远古时期,IBM DB/2 团队曾坚定不移地严格禁止使用提示,就是这样(“我们比你更懂!我们不允许,这其实是我们的 Bug,马上就会修复。”)然而(与其他所有空话一样)事情并没有像他们说的那样发展。经过一段时间的发酵,DB/2 编译器选择错误执行计划的问题甚至催生了一种事实标准的实践:“如何欺骗 DB/2 优化器”,例如在 WHERE 子句中添加“OR 1=0”,借此对编译器产生影响(实际上“OR 1=0”就可以看作一种 SQL 编译器提示)。后来这个技巧被广泛采用(尽管 DB/2 团队依然在说“我们始终比你更懂”),以至于 DB/2 团队最终不得不承认“OR 1=0”的作用很重要,他们会保证该方法以后可以永远使用下去??。但是故事还没完,大概 10 年前(坚持认为“我们比你更懂”大约 20 年后),DB/2 开始支持提示(虽然面子上过不去,但至少好过“OR 1=0”这样的做法)。
目前 Postgres 团队正在坚守与 DB/2 团队 30 年前相同的立场,对此我唯一感到好奇的并不是 Postgres 以后是否会改变有关提示的立场,而是什么时候会改变。不幸的是,大家从历史教训中学到的唯一经验就是:人们从不会以史为鉴??。
OLTP 的性能问题
某些 RDBMS 会让人感觉它们在设计时从未考虑过主要以写操作为主的 OLTP(而是更专注于读取查询)。虽然这并不意味着此类 RDBMS 从本质上就很糟糕(毕竟大部分数据库确实主要以读取查询为主要任务),但在现实世界中,面对需要执行大量写操作的 OLTP 环境,这会成为一个不容忽视的问题??。一起看看这些相当著名的问题吧。
Postgres:甚至对非索引字段进行更新也会导致 Ctid 的变化(存在争议)
有报道称现实用例中对包含大量索引的数据库进行更新时,Postgres 的数据库会遇到严重的性能问题。相关问题的详细讨论可参考 StackOverflow.PostgresUpdates 和 Klitzke ,在我看来问题主要在于:
“由于索引需要通过 Ctid 引用行,一个简单的 UPDATE(哪怕针对非索引列执行)也会改变 Ctid,导致引用了被更改行的表中每个索引中的 Ctid 均需要重写。”
这就很糟了,对写操作负担重的 OLTP 数据库尤为如此??。另外从 Postgres 8.3 开始提供了一种所谓的 Heap-Only Tuples(HOT)功能,该功能至少在理论上应该能消除大部分相关问题(然而我没找到任何能确认这一点的现实用例),该功能的简要介绍可参阅 Postgres.HOT 。这个功能的大致思路是:在 HOT 正常工作的前提下,如果新行可以放入同一页,那么无论 Ctid 如何变化,索引依然会指向同一页,因此无需更新索引。当然这种想法有一定效果,但前提是新行可以放入同一页,为此似乎可以通过“机会型的(Opportunistic)Mini-vacuum”实现:尽管 Postgres 依然无法清理(Prune)需要更新的 Tuple(出于 MVCC 的考虑必须保持精简),但(据推测)可以对同一页中较旧的 Tuple 进行清理,这样也许可以在同一页中保存新行并避免更新索引。
底线是:虽然 Postgres 存在不必要的索引更新问题,但通过 HOT 功能大幅缓解了这个问题,但 HOT 能否完全解决这个问题还有待讨论(这种缓解过程可能需要额外的配置以便在页中为 HOT 提供所需的空间),但至少我们可以针对数据库实例监视 HOT 的运行效率(可参阅 Postgres.HOT )。
MemSQL:投票式日志写入
虽然内存中数据库为 OLTP 应用提供了巨大收益,但我并不会出于这种用途考虑选择 MemSQL,原因如下。
正如 Mituzas 所述,MemSQL 会使用耗时 50 毫秒的投票发起数据库日志写入操作。对任何类型的 OLTP 数据库来说这都是一种很糟糕的做法,但如果你考虑我的建议选择单一写入数据库连接架构,MemSQL 的这种所谓“功能”会造成极为严重的后果??。一定要反复核实该产品是否还在使用这个功能,如果在使用,至少应该尽量避免使用单一写入数据库连接的配置。
执行计划和 Profiling
为了对 SQL 语句进行调试和 Profiling,至少需要具备一个能展示 SQL 查询“执行计划”的工具。
面对生产数据库,我们需要对 SQL 语句进行调试和 Profiling。至少需要具备一个能展示 SQL 查询“执行计划”的工具。这样才能预测查询的执行方式(即,SQL 语句编译后的执行计划将用于生产数据库,或从生产数据库状态导入的数据库)。
另外执行计划只能告诉我们预测的执行成本(通过数据库状态计算而来),但可能与实际情况存在数量级的差异。
为此可以使用某些类型的实时 Profiling 技术。这类工具可能有一定的作用,但在我看来并非绝对必要:通常来说只要具备一些经验和常识,就可以很容易地发现这些与查询有关的性能问题(一般来说,迫使数据库使用我们指定的查询计划,这往往要比确定当前所用查询计划表现欠佳的原因更为困难)。
内存中处理
目前已经有不少 RDBMS 提供了内存中处理功能。这些功能主要可分为两类:
- 非持久内存中处理。例如通过 RAM 磁盘运行 RDBMS,以及为 MySQL 使用 MEMORY 存储引擎。但这种方式通常无法用于 OLTP 数据库。
- 持久内存中处理。这种方式类似于在应用和 RDBMS 之间添加了一层内存缓存(实际上 Oracle 的 TimesTen IMDB Cache 就是作为一种缓存进行宣传的)。下文将要介绍的所有大型商用 RDBMS 供应商都提供了类似技术,但每种技术都需要耗费血本??。
然而非持久内存中处理技术通常并不适合 OLTP 数据库
复制
面对极高的负载,我们迟早需要为数据库创建(只读的)副本(Replica)。如果所用 RDBMS 产品支持复制(并能正确使用),就可以自动创建所需副本??。
大部分时候我们最需要的是一种所谓的主从异步复制(这样从副本的延迟才不会影响主副本)。此外可能还会用到其他相关功能,例如副本合并(同样适用于简单的主从异步环境,但绝对不会产生任何冲突)。
然而从我个人的经验来看,RDBMS 提供的复制功能在高负载情况下通常表现都很糟??。有这样一个极端案例:在负载持续多天维持每天不超过 1 百万笔事务的情况下,复制功能总是因为一些难以理解的错误而失败,需要对副本进行完整的重新同步(这也是个非常头疼的问题??)。这种情况告诉我们:
在实际使用前一定要通过极为严苛的负载对复制机制进行测试
设备或机械由于无法正常工作或运转失败而显得无用
— 维基词典 —
好在如果你选择的复制技术恰好就是这样的“DUD”(并且使用了单一连接的方法),还可以用相对较为简单的方法自行进行复制。
分区
RDBMS 提供的分区(Partitioning)是一种实现可缩放性的工具,但往往会被过度吹捧。但是我本人也倾向于选择无需共享(Share-Nothing)的模式(并采用应用层面的分区),因为相比将一个数据库分区至多台服务器,这种方式可以上线更为线性的缩放能力。但有些情况下 RDBMS 提供的分区功能也会显得较为有用,因此如果提供有这样的功能,也可以将其看作一个“加分项”(尽管可能并不像 RDBMS 销售人员说的那么天花乱坠)。
对 OLTP 而言不是问题
在比较不同 RDBMS 时,你肯定会看到有关不同 RDBMS 对 JOIN 的支持情况,或对 SQL 标准不同解释的大量争议。然而有一件事必须注意:
虽然所有这些问题对“报表”和“分析”数据库非常重要,但从以往经验来说,对 OLTP 数据库并不重要
OLTP 数据库是一种很奇怪的东西,尤其是这一领域很少会使用 JOIN 语句。当然,你可能有时候会需要 JOIN(毕竟这是 SQL 的全部??),但大部分情况下 OLTP 数据库并不需要 JOIN(哪怕真的需要 JOIN,也可以非常简单地实现)。因此除非为“报表”和“分析”使用同一个数据库(下文将详细介绍),这个问题其实并不重要。
然而有些时候确实需要为“报表”和 OLTP 使用同一个 RDBMS
然而有些时候确实需要为“报表”和 OLTP 使用同一个 RDBMS,尤其是恰巧在这两类数据库之间进行了 RDBMS 级别的复制时,可以无需自行实现,直接获得所需副本(过程较为简单,但非常耗时)。
RDBMS 供应商(尤其是商用供应商)还过度鼓吹了另一个问题:容错。使用容错功能的数据库服务器并不能保证可以改善 MTBF(考虑到容错系统本身的 MTBF 并非无限的,很容易得出这样的结论)。更重要的是,现实情况告诉我们,容错功能的 MTBF 通常低于高质量服务器硬件自身的 MTBF,这意味着如果不使用容错机制,系统本身的 MTBF 反而更高(这也是现实世界中经验得出的结论)。换句话说,对于高质量服务器,硬件(例如 CPU 或主板)故障几率远低于容错系统出错(进而导致各种类型的麻烦,直到最棘手的“裂脑(Split brain)”)的几率。
当然,有些情况下确实需要容错(例如证交所或银行的系统),但这些系统很可能运行在 DB/2 / Oracle 产品之上,它们在这方面表现其实差不多,因此 RDBMS 的容错问题也就不那么重要了。
许可
一旦开始考虑商用 RDBMS 的许可问题,你会发现这些产品不仅昂贵,而且许可机制极为复杂,可能需要花费数天时间才能理解到底要花多少钱
最后同样重要的一个问题:还需要考虑许可的获取及相关成本。一旦开始考虑商用 RDBMS 的许可问题,你会发现这些产品不仅昂贵,而且许可机制极为复杂,可能需要花费数天时间才能理解到底要花多少钱。我对截止 2016 年底市面上三大商用 RDBMS 的许可情况分析如下。
首先是几个备注:
- 我会尽可能以“每内核”方式比较不同产品的价格。
- 我只关注面向生产环境的许可,“开发者版”、“学生版”,只能通过托管供应商获得的版本等特殊版本不予考虑。
- 此外我还会忽略“授权的客户端”这种许可模式(毕竟这种模式无法适用于用户数未知的部署)。
- 声明:有关许可的介绍信息基于“我本人尽最大努力后的理解”,但不提供任何保证。在制定任何决策,尤其是要购买昂贵的产品前,还行自行分析相关信息。
- 另外需要注意,虽然存在一些共同的趋势(例如免费提供较为基本的数据库产品),但商用数据库的许可经常会发生变化,尤其是内核数方面的限制,通常数量可能增加,但也有可能降低??。
- 最后同样重要的是:一定要与经销商好好砍价,有时候甚至能达到 50% 的折扣,对于更大金额的合约甚至折扣可以高达 80%。
Microsoft SQL Server 2016
在各大主要商用数据库中,MS SQL Server 是最便宜,许可模式最简单的产品之一(没错,按照商用 RDBMS 的标准来看真的是既便宜又简单)。注:下列数据来自 SQLServer.Editions 。
Microsoft SQL Server Express
成本:免费。
局限:最多 1 颗处理器(或 4 个内核,取较小值),1GB 内存,数据库体积上限 10GB。SQL Server 2016 中这些限制适用于每实例,主要限制了每个实例(而非每台数据库服务器)可用资源总量,通过一台物理服务器运行多个实例绕过这些每实例限制的做法是官方允许的,详情请参阅 SQLServer.CapacityLimits 。
考虑到每个数据库 10GB 容量的限制,就算为每个服务提供一个数据库,也会很快达到上限
因此该版本非常适合为每个服务提供一个数据库(无论是否使用单一数据库连接)的模式。然而考虑到每个数据库 10GB 容量的限制,就算为每个服务提供一个数据库,也会很快达到上限??。
功能:基本的 SQL 功能,支持 24×7 运行,可充当复制关系中的客户端。
缺乏的功能:分区,充当复制关系中的主副本。
当然,该版本可以由用户自行进行分区和复制,如果希望针对企业版之外 SQL Server 版本自行分区,可使用一些工具,例如 Clement 实现。
Microsoft SQL Server Standard
成本:约每内核 2 千 -4 千美元(可参阅 Ozar 和 SQLServer.Pricing )。
局限:最多 4 颗处理器(或 24 个内核,取较小值),128GB 内存,数据库大小几乎无上限。
功能:基本的 SQL,支持 24×7 运行、复制,及 SQL Profiler。
缺乏的功能:分区。
只要预算允许,SQL Server Standard 是一款完整功能的 RDBMS 产品,很适合用于 OLTP 负载。我通常更愿意选择该产品而非 MySQL(出于可靠性和功能丰富程度的考虑),然而如果预算极为充足,我会考虑用 DB/2 或 Oracle 产品代替(虽然通常更贵,但相比 MS SQL 而言,长远来看通常会更可靠 [1])。
Microsoft SQL Server Enterprise
成本:每内核 7 千 -1.4 万美元。
局限:无。
功能:需要的一切功能,外加内存中 OLTP 处理。
缺乏的功能:无。
SQL Server Enterprise 无疑很贵,老实说我没看到该版本有任何实际用例,除了内存中 OLTP(在这一领域 SQL Server 在价格方面远超其他所有商用 RDBMS)。其实如果你按照本书其他章节提供的思路来设计数据库架构,除非每天写入事务量上亿,否则通常并不需要内存中 OLTP,这样价格也就显得不是那么的高。
IBM DB/2 10.5
IBM DB/2 在一个领域是不容置疑的冠军:尽可能让价格高到离谱让人难以置信??[2]。然而 DB/2 也确实具备一些不错的技术特性(我本人就有良好的使用体验),因此成本分析中也包含了这个产品。
DB/2 Express-C
成本:免费。
局限:最多 2 个内核,16GB 内存,数据库体积上限 15TB。这一系列限制适用于每台服务器(而非每个实例),但也可适用于每个虚拟化会话,详情请参阅 RadaMelnyk 。
功能:基本的 SQL,支持 24×7 运行。
缺乏的功能:分区,复制。
总的来说,对于大部分单一写入数据库连接部署(尤其是使用副本作为报表用途时),2 内核的限制并不是太糟,通常可在无需面临太多局限的情况下满足要求。其他局限其实也显得不是太糟糕了。
另外还可参阅上文自行实现分区和复制的介绍(DB/2 的自行分区实现方式与 SQL Server 差不多)。
DB/2 Express
DB/2 Express 主要有两种值得我们考虑的定价模式。
成本:DB/2 Express 主要有两种值得我们考虑的定价模式。其中之一基于一种名为 PVU(暂不考虑其含义)的概念:约为每 PVU 70 美元 [3] 及每内核 100PVU,换算后价格为每内核 7 千美元。另一种定价模式基于所谓的 FTL 概念,可将其简单理解为包年订阅的全套方案(初始 TCO 较低,长期范围内较高)。然而我暂时没找到有关 DB/2 最新 FTL 定价的标准??。
局限:最多 8 内核,64GB 内存(每服务器),数据库大小上限 15TB。
功能:基本的 SQL,支持 24×7 运行和复制(一种名为 SQL Replication 的复制方式)。
缺乏的功能:分区,Q Replication。
DB/2 Express 相当贵,但功能也相当强大。对于 OLTP 应用,通常使用中不可能达到规格上限,但对报表(和分析)副本,这样的上限略微有些低。
DB/2 Workgroup
顾名思义,DB/2 Workgroup 与 DB/2 Express 基本类似,但存在下列差异:
- 上限提高至 16 个内核与 128GB 内存(每服务器)。
- 相比 DB/2 Express,成本提高约 1.5 倍(大约每内核 1 万美元左右)。
是否可以将 Workgroup 版用于 OLTP 应用这不太好说,但报表副本可以从该版本提高的上限中获益。
DB/2 Enterprise
相比 DB/2 Workgroup,上限有所提高,但价格也有了 5 倍 -6 倍的提升(约为每内核 5 万美元左右!)。此外 Enterprise Server 还提供了一些花哨的功能(例如 Q Replication),但老实说与其支付对这么贵的价格,不如自行开发复制机制 ;-)。
实际上我没看到任何将 DB/2 Enterprise 用于 OLTP 的用例(甚至银行 / 证交所也不会这样做)。
DB/2 Advanced * Server
为了让许可机制变得更复杂,DB/2 还提供了 Advanced Workgroup Server 和 Advanced Enterprise Server 版本。这些版本的价格高得离谱(Advanced Workgroup 的价格与非 Advanced Enterprise 版价格类似,但 Advanced Enterprise 的价格比非 Advanced Enterprise 版高了 1.5 倍)。另外他们还提供了每 TB 容量定价的模式(Advanced Workgroup 版每 TB 容量约 5 万美元,Advanced Enterprise 版每 TB10 万美元)。OLTP 数据库的容量通常不会超过 1TB(但他们没提供低于 1TB 容量的许可),因此可以将其理解为实际成本。
在 OLTP 处理方面,DB/2 Advanced Workgroup Server 的用例可能只有一个:那就是你真的非常“不差钱”,并且同时你还需要内存处理技术。
Oracle Database 12c
相比 BD/2,Oracle 的许可机制略微简单,但实际价格更加昂贵??。
Oracle DB Express (DB XE)
成本:免费
局限:11GB 用户数据,最高 1GB 内存,单一内核。
功能:基本的 SQL,支持 24×7 运行。
缺乏的功能:分区,复制。
在我看来,11GB 用户数据的限制使得该产品基本无法用于现实环境,哪怕为每个服务使用一个数据库也是如此。然而对于 OLTP,也不是完全不现实。
Oracle DB Standard Edition 2 (DB SE2)
成本:每内核 17500 美元 [4](永久许可),每内核 3500 美元(1 年期限),此外如果稍后需要使用其他功能,需要随时准备好为这些功能额外付费??。
局限:最多 16 个内核(或 2 个处理器,取较小值)。
(包含的)功能:基本的 SQL,支持 24×7 运行,复制。
缺乏的功能:分区。
老实说,SE2 应该已经可以满足所有 OLTP 负载的需求(内存中处理除外),但成本同样很高??。
Oracle DB Enterprise Edition (DB EE)
成本:每内核 47500 美元(永久许可),每 CPU 9500 美元(1 年期限),此外如果稍后需要使用其他功能,需要随时准备好为这些功能额外付费??。
局限:无。
(包含的)功能:基本的 SQL,支持 24×7 运行,复制,分区。
(额外付费的)可选功能:内存中处理(适用于 Oracle EE 的 TimesTen In-Memory Cache)。
我认为唯一 OLTP 环境中有必要使用 Oracle EE 的唯一原因是 TimesTen,而这技术真是贵得过分??。
与 OLTP 有关的 RDBMS 功能对比表格
上文内容可总结在下表中(但是要注意:下表列出的都是对 OLTP 生产环境较为重要的因素,与报表、分析等场景的关系不大。也就是说,下表并不能告诉你“总的来说最棒的 RDBMS 是哪个”,而是会告诉你“以写操作为主的 OLTP 应用最适合的数据库是哪个”):
注意:对 OLTP 最为重要的特性均使用了粗体字
注意:对 OLTP 最为重要的特性均使用了粗体字 MySQL + InnoDB [1][2] PostgreSQL MS SQL Server IBM DB/2 Oracle 类型 MVCC MVCC 基于锁或 MVCC 基于锁 MVCC ACID 保证 多行 ACID 多行 ACID 多行 ACID 多行 ACID 多行 ACID 24×7 运行 联机备份 第三方,可使用异步复制代替 支持 支持 支持 支持 ADD COLUMN 复制整个表,很慢;基于第三方触发器的方式 [1] “即时” “即时”[1] “即时” “即时” 表优化 联机 [1] 联机 联机 联机就地 [1] 联机 添加磁盘 依赖文件系统,添加磁盘会导致文件系统不平衡或需要 RAID 级别的平衡 依赖文件系统,添加磁盘会导致文件系统不平衡或需要 RAID 级别的平衡 虽然使用了容器,但似乎无法进行明确的重新平衡 [1] 容器重新平衡 容器重新平衡 OLTP 性能 提示 USE INDEX 不支持 [1] WITH(INDEX) 可行但效果一般 [1] INDEX/NO_INDEX 已知的 OLTP 性能问题 非索引更新重写整个行 [1] 执行计划 /Profiling EXPLAIN、Profiling[1] EXPLAIN SHOWPLAN_*、Profiler[1] EXPLAIN、Profiler EXPLAIN、Profiling 内存中处理(耐久) 不支持 [1] 不支持 支持(贵) 支持(贵) 支持(贵) 复制 / 分区 异步主从复制 支持 支持 支持 [1] 支持 [1] 支持 [1] 分区 支持 支持 支持 [1] 支持 [1] 支持 [1] 定价 定价选项 1 免费 [1] 免费 免费(最多 10GB 数据) 免费(最多 2 内核) 免费(最多 11GB 数据) 定价选项 2 每服务器每年 5 千美元 每内核 2 千 -4 千美元 每内核 7 千美元 每内核 1.75 万美元或每内核每年 3.5 千美元 定价选项 3(包含内存中处理) 每内核 7 千 -1.4 万美元 每内核 4 万美元或每 TB 5 万美元 每内核 7.05 万美元## 选择最适合自己的
从上表中可以发现,RDBMS 的选择并不像想象中那么容易。免费的数据库固然很有吸引力 ;-),但一些问题使其无法用于生产环境。老实说,如果能忽略价格,我肯定会选择上述一种商用 RDBMS 来使用(也许会选择 DB/2 或 Oracle)。然而就算“标准”、“Express”之类版本的商用 RDBMS,价格也显得非常高,对于企业级的版本,简直有些高得让人无法接受??,实际上通常我们并不需要这些产品??。
因此如果要开始一个全新的项目,我也许会考虑各种不同选项,并根据项目的具体需求权衡利弊。
On Cross-RDBMS SQL
对于“我们要使用哪个 RDBMS”这样的问题,最可行的回答也许就是:“哪个都行”??。这是个很有趣(并且很可行)的选项,为此我们需要确保自己的 SQL 语句可以跨越不同 RDBMS 使用。
* 所有 *RDBMS 供应商都在努力实现供应商锁定,并且通常都会成功
虽然可以编写与具体 RDBMS 无关的 SQL 语句,但通常来说这样做的难度会介于“相当难”和“十分难”之间??(在我看来,远比编写跨平台 C++ 更难,而 C++ 本身就够难着手了)。如此难的主要原因在于所有 RDBMS 供应商都在努力实现供应商锁定,并且通常都会成功??。如果你向负责数据库的同事询问这类夸 RDBMS 的方法,他们有 99% 的可能性会让你别犯傻了,为啥放着 < 他们自己所熟悉的任何 RDBMS 产品 > 那么棒的功能不用。
作为各类供应商锁定做法始终不渝的反对者,我非常提倡跨 RDBMS SQL(并且已经获得了不错的成绩)。然而这毕竟不是决定项目生死的问题,具体怎样做还要由你来决定。所有跨平台的做法面临着一个共同的情况:一开始很痛苦,但长远来看终究会让你获益。
如果你也打算朝着这个方向努力,需要注意一些重要的常见问题:
- 不要使用存储过程(Stored procedure) – 不同供应商的做法完全不同。你可以通过应用级别的“数据库服务器”将数据库与应用隔离,并且实际操作远比听上去简单很多(妥善规划的语句通常可实现极为类似的性能)。
- 不要使用触发器。大部分情况下并不需要它们(虽有例外,但很罕见也很少见)。
- 不要使用自动增量之类的措施 [5]
- 不要使用特定供应商专有的功能(最常见的例子是 CURRENT_TIMESTAMP/CURRENT TIMESTAMP/getdate()/Now())。
另外,无论负责数据库的那帮人怎么跟你说,具体供应商专有的扩展都不是获得更高性能所必需的。
最后同样重要的是,对 SQL 绑定(Binging)进行编译(下文将进行介绍,出于很多原因考虑这都是一种很好的做法)可以对跨平台 SQL 的实现提供巨大的帮助。
免费数据库也不错
跨平台 SQL 姑且不谈,看看都有哪些选项吧。最主要的方法是使用免费的 RDBMS。
如果为 OLTP 应用使用免费的 RDBMS,我更愿意选择 MySQL+InnoDB 而非 Postgres。在我看来,有关 Postgres 的设计决策更适合读取密集型工作负载而非写入密集型负载,有报道 Postgres 面对写入密集型 OLTP 环境存在严重的性能问题。另外根据第三方报道,很多非常大型的公司(例如 Skype)已经成功使用 PostgreSQL 运行了非常的规模的负载,因此在 OLTP 应用中使用 Postgres 是非常可行的。
另一方面我要说的是,如果将 MySQL 用作 OLTP 数据库,生产环境中的运行并不像你想象的那么简单,尤其是为表添加新字段是一种相当繁琐的操作(但也是可以实现的)。
关于 RDBMS 还有个问题需要注意:当同一时间有成千上万用户后,任何停机事件都会造成巨大的损失。这方面商用 RDBMS 通常比 MySQL 的社区版做得更好(社区版在这方面的改动比商用数据库更频繁,详情可参阅 Schwartz 的讨论),当然也比 Postgres 做得好(例如可以参阅 Klitzke 有关间歇性数据不一致问题的抱怨)。如果选择 MySQL 企业版,其实从价格方面考虑已经和另一个竞争者 Microsoft SQL Server 的价格相差无几了(如果要在不考虑价格的前提下从这两者中做选择,我更愿意选择至少为 OLTP 使用 SQL Server)。
Microsoft SQL Server,以及更多 Microsoft SQL Server 产品
Microsoft SQL Server 的定位恰巧位于两个极端(免费的数据库,极为昂贵的 DB/2 和 Oracle)之间。很多实际用例中,用户会出于稳定性(24x7 连续运行)和价格的折衷而选择更显合理的 SQL Server。
这样的搭配(总成本约为 4 万 -8 万美元)通常可以应对同时访问的成千上万个用户
如果选择 SQL Server,也许可以首先从免费的 SQL Server Express 着手(并在数据库容量超过不怎么大的“10GB 上限”之前继续使用),随后可以考虑为 OLTP 购买 SQL Server Standard 的 4 内核许可,并随着需求的增加为报表副本购买 2 个 8 内核许可。这样的搭配(共 20 个内核的许可,总价格约为 4 万 -8 万美元)通常可以应对同时访问的成千上万个用户。如果预算允许,这将是一种非常可行的选项。
但我想强调的是,从功能和可靠性的角度考虑(暂不考虑 SQL Server 过去 20 年来脱胎换骨的改进),我依然不认为 MS SQL Server 可以成为 DB/2 和 Oracle 势均力敌的对手。然而考虑到其价格(相比其他商用产品更便宜),选择 SQL Server 也是一个适度的妥协。
商用 RDBMS OLTP + 基于免费 RDBMS 的副本
我认为比较可行的第三种方法是,使用商用 RDBMS(我个人在使用 DB/2 时获得了不错的体验)作为 OLTP 数据库,从个人经验来说,此时最多只需要 4 个内核。同时依照个人经验,OLTP 数据库通常并不大(大部分空间都用来保存历史数据,这些数据其实可以转移到副本中),因此大部分情况下就算为 OLTP 系统使用免费版 RDBMS 也是可行的(考虑到各自的局限,免费版 DB/2 的可行性高于 Oracle 和 SQL Server,但超出免费版限制后 DB/2 的成本会更高。
如果 / 当免费版不够用时,通常使用“标准版”、“Express”版 OLTP 数据库(但并不用于报表副本)也是可以的(至少你已经考虑了成本的问题,而 1 万 -3 万美元的成本也并没有高到离谱)。
如果为报表副本使用商用 RDBMS(MS SQL Server 除外),很可能需要付出更高成本。为了避免这种问题,我们可以使用自行实现的副本,并通过首选 RDBMS 之外的其他产品运行这些副本(例如 Postgres,但有必要进行相应的测试以确保复制写操作负载的处理速度足够快)。
如果不差钱(证交所 / 赌场):DB/2 或 Oracle
最后同样重要的是:如果是证交所、银行、赌场的系统,如果不愿意承担任何风险,此时最佳做法是完全使用 DB/2 或 Oracle。这些行业通常很赚钱,DB/2 或 Oracle 的许可成本完全不是问题,并且这些 RDBMS 也是公认的高负荷 OLTP 环境最佳选择(并提供了这些环境必须的功能)。然而就算这种环境,大部分情况下 Workgroup/SE2 版的 OLTP 数据库也足够了(但也要具体问题绝体分析)。
[[待续……
本文摘自即将出版的图书《Development and Deployment of Multiplayer Online Games (from social games to MMOFPS, with social games in between)》Beta 测试版中的第 17(f)章。我们还将发布第 17(g)章的内容,其中将介绍 SQL 绑定的“编译”(很多情况下都需要使用该技术))]]
卡通插画作者:Sergey GordeevIRL,来自布拉格 Gordeev Animation Graphics 。
作者:“No Bugs” Hare ,阅读英文原文: Choosing RDMBS for OLTP DB
- 不考虑 MySQL+MyISAM 原因在于缺乏对多行 ACID 事务的支持。
- 我没有资格告诉你 MariaDB+XtraDB 与 MySQL+InnoDB 有多大差异,也许相差无几,但我也不敢保证。
- 至少有 Percona 提供的解决方案,自行实现是可行的。
- 从 SQL Server 2012 开始,以往的限制似乎已经取消了,参阅: Rusanu 。
- 从 MySQL 5.7.4 开始。
- Express-C 版似乎不支持就地重构。
- 参阅 Randal 。
- 虽然可以通过一些方式修改 Postgres 的优化器(例如可参阅 Postgres.QueryPlanning ),但无法针对每查询进行,因此虽然理论上可以实现,但具体过程的麻烦程度远非“太不方便了”那么简单??。
- 基于 XML 的“Profile”?省省吧。类似 DB/2 中经典的“OR 1=0”这种玩弄优化器的手段只会更糟。
- 虽然可通过 HOT 加以缓解,但效果是否全面尚不明确。
- 可视化功能需要额外付费。
- Profiler 要求至少使用 SQL Server Standard。
- MEMORY 存储引擎或通过 RAM 磁盘运行任何引擎的做法缺乏耐久性,不予考虑。
- 充当复制主副本的系统至少要运行 SQL Server Standard。
- 至少需要 DB/2 Express(老实说,对于 OLTP 我强烈推荐 Q Replication,该功能要求具备 DB/2 Enterprise,有些超范围了)。
- 至少需要 Oracle SE2。
- 至少需要 SQL Server Enterprise。
- 需要 DB/2 Enterprise。
- 需要 Oracle Enterprise。
- 有付费支持的选项。
- 15 年前 SQL Server 运行崩溃的情况远远高于已经较为成熟的 DB/2 和 Oracle,然而在这之后 MS SQL 逐渐迎头赶上。现在具体情况如何,大家各持己见。
- 我比较好奇的是,DB/2 营销团队需要用多长时间才能意识到因为价格问题,他们已经流失了大量新客户?
- 注意:DB/2 未提供公开的售价信息??,详情需要联系经销商,但具体金额是可以谈的。
- Oracle 的定价是“每处理器”的,但对于比较新的 x64 CPU,“处理器”等同于“内核”。
- 对单一写入数据库连接应用来说这完全不算问题,最新值可以很轻松地进行缓存。
感谢木环对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论