虽然 MySQL 在互联网行业中历史久远,应用广泛,有大量的各种应用,包括网络游戏也在使用,但是关系型数据库并不是诞生于互联网的软件模型。在互联网的大量应用场景下,关系型数据库作为一个功能齐全的工具,都能很快的满足功能需求。不过,在互联网业务运营到一定程度之后,往往又变成一个技术上的瓶颈。
问题的总结
我们可以总结出几个,互联网业务中,使用关系型数据库出现的典型问题:
- 错误或者没有使用索引。此问题常见于新手程序,不理解关系型数据库的搜索,必须要建立索引。又或者由于业务变更后,没有及时修改索引设置,或者遗留过多无用的索引而影响插入性能。由于数据库就算没有索引,在数据量极小的情况下,是察觉不出功能上的问题的,所以很多新手开发者,都会在开发期忽略索引这个问题,而等到业务上线,数据量变多,才导致运营事故。
- 返回过大的数据集。此问题有时候会以 select * 这种语句出现,也有时候以缺乏 limit 子句导致。此问题危害性最大的地方,对于 join 表记录数的设计,往往是不作限制的,如一个玩家可以携带的道具总数。这种情况会以偶发情况出现在各种环节,有时候会自我恢复,隐藏后续更大的事故。
- 早期的表锁和现在的行锁。SQL 数据在并发的情况下,为了维持数据一致性,往往会进行锁。早期的 MySQL 采用的 MyISAM 类型的表,为了提高性能,使用了“表锁”,但是在互联网应用下,很快就发现,这种“提高性能”的设计实际上是不符合实际的。因为互联网业务并发量非常大,导致了频繁的锁表,加上关系型数据表没有分布能力,导致请求进一步集中,更加恶化了这种情况。所以后期 MySQL 改为使用 InnoDB 表格式,付出更多的性能代价换取“行”锁,有效缓解了这一问题。但是“行”锁依然在大并发的情况下,有可能付出较高的延迟性代价,特别是碰到上面所说的错误 #1 和错误 #2,扩大了故障的损害蔓延。
- 主从同步失效导致的业务故障。作为典型的“读写分离”场景,使用主从同步是 MySQL 最常见的使用方法。但是主从同步本身,在网络出现偶发故障,以及其他一些故障(如磁盘满)的时候,缺乏自我恢复的能力。如果业务依赖于主从同步,很容易出现不可发现的数据错误。如果这些数据错误在业务逻辑上是无法纠正的,那么就导致了最严重的事故——数据损坏。而且主从同步从另外一个角度来看,也是破坏了关系型数据库关于强一致性的承诺,这就衍生出大量需要“经验”才能解决的业务逻辑设计问题。
- 单一表记录过多。对于互联网应用来说,数据就是钱。几乎很少互联网应用会删除积累的数据,所以数据表的记录,无可避免的越积越多。单表积累的数据过多,会导致性能持续下降。对此业界做了大量非标准的分库分表的工具和中间件。而这些技术往往都有一定约束性,破坏了 SQL 本身设计的功能,导致更多可能的误用,譬如设计的索引和分布字段有冲突等等。
- 用作海量统计工具。根据上面所述,数据量的持续增大是一个必然事件,为了统计表的性能,往往需要自己写一些脚本维持统计表的数据记录数在比较小的区间。但是这有可能又导致某些大跨度的统计语句失效。按照时间维度进行记录合并是一个常见的做法,但是这种做法和 Map Reduce 的操作已经很像了,而且还缺乏分布式处理的能力,为什么不用 MapReduce 的体系技术呢?
- 用作消息队列。这是一种典型的错误用法,常见于 web 开发中,为了解决部分服务器间的通信问题,直接使用数据库的写入表,读取表,删除表记录。这一系列的操作,其成本是单纯的网络通信的性能成本的几个数量级倍数。
原因分析
这些典型的问题,在数据库层面的设计根源,是有以下几个原因:
- 没有分布式的存储设计。这导致了单一表数据记录有限,以及后续出现的分库分表中间件的一系列问题。
- 强一致性保证。由于需要这个保证,导致了“锁”的必要性,以及在某些分布处理的情况下,如主从同步,对这个保证的误解和无法满足,从而出现故障。
- 缺乏有损服务和性能保护设计。这个原因实际上是“强一致性保证”的副产品。互联网应用往往都可以在“有损服务”的情况下维持运行,但由于关系型数据库垮了,导致全体功能全部不可用。
这些原因,在 CAP 理论上有清晰的定义。由于关系型数据库选择了强一致性和高可用性,就必然在分布式特性无法满足。而互联网应用的特点,就是对于分布式特性的强需求。这种设计上的需求分歧,是导致各种问题的总原因。
解决方案
以 Redis/MongoDB 这类数据库的出现,能比较好的解决上述的问题。
- 对于无限增加的记录,本身就是设计为分布式存储的数据库,可以自己完成集群的功能,而不需要业务关心
- 由于不保证强一致性,所以也可以使用类似乐观锁的机制,来保证“最终一致性”,从而大大提高的性能和可用性,这是 CAP 理论所论证的。
- 由于没有特别灵活的“索引”,所以需要使用者承担“设计存储结构”的责任,而不是简单的设计几个字段。但是这种付出得到的额外好处是,基本上不太可能出现“误用”无索引搜索的情况。和关系型数据库“易用性”带来的副作用相比,这个“副作用”可谓吃苦在前,收获在后,可能是一种更“好”一点的副作用。现在也有一些新型数据库试图弥补 nosql 的易用性问题,称为 NotOnlySQL,非常值得期待。
但是同样也需要付出成本:
- 学习成本。SQL 学习成本相对比较低,索引维护和表复制操作,并不影响功能开发,所以很多开发者在初期并不需要投入精力学习太深入。而 NOSQL 由于没有标准的 IDL (接口描述语言),导致每一种数据库都需要专门学习,所以学习成本较高。即便只使用一种 NOSQL,由于需要自己维护数据结构,如 MongoDB 就需要维护 Document 接口,所以在应用时需要学习的概念也比较多。
- 需要开发者设计存储结构,而不是简单的定义一个表。由于 SQL 使用二维表这种表达能力很丰富的抽象,所以几乎大部分逻辑都可以承载。但是以树状结构为基础的 NOSQL,对于多种查询条件的支持,实际上需要开发者自己定义多个树。SQL 的关联表的语义也比较清晰,而树的关联则复杂一些。对于 SQL 来说,多一种查询方式,只需要多建立一个索引即可,功能代码都是一条 SQL 语句;而 NOSQL 则需要自己写代码维护多个存储树,实际上等于手动维护索引。这些都是使用 NOSQL 需要额外付出的开发成本。