最近读到一篇文章SQL 必须被淘汰的 9 个理由-CSDN博客。这篇文章有误导性,其说法显然是错误的。我将讨论原作者提出的观点,以及为什么我认为它们不切实际。
我住在加拿大。加拿大银行估计该国每天约有 3000 万笔金融交易。我们拥有所谓的“Big 5”。银行。如果我们假设他们在这些交易中首当其冲(比方说三分之二),那么这将是 2000 万笔交易。如果我们假设分布均匀,这意味着它们每人每天处理约 400 万笔交易。多年来,银行每天都在处理这种负载,并使用 SQL。
银行已经找到了如何扩展 SQL 使用的方法。我从未在银行工作过,所以我不知道他们如何扩大规模。我怀疑解决方案的一部分是分区,最有可能按日期范围进行分区 - 对给定日期范围(例如日历月)内的事务使用单独的磁盘空间。
如果一家银行平均每天处理 400 万笔交易,在最坏的情况下,一个分区将包含 31 天 * 400 万笔交易 = 1.24 亿行。我认为从一个账户提款和存入另一个账户需要单独的记录。
假设每个事务处理两行,每月产生 2.48 亿行。从长远来看,普通的企业蹩脚设备(其性能比实际服务器差得多)可以轻松地通过主键在 2 毫秒内从一百万行中找到一个随机行。
我确信事情没有那么简单,而且可能有比这更多的数据 - 银行拥有非常可靠的系统,我的猜测是这种可靠性是以更多数据为代价的。即使我们再次将这个数量翻倍,达到每月约 5 亿行,这也是一个可管理的数据量。如果数据量太慢,则分区可以更细粒度,例如每月的上半月和下半月,以将每个分区的行数大约减少一半。
加拿大的银行需要将数据存储 7 年,因此该过程可以通过在每个月底运行一个作业来进行,这会做两件事:
为下个月的日期范围创建新分区。该分区必须在新月份之前存在,以确保在下个月的第一笔交易发生之前有单独的分区来存储下个月的数据。最安全的选择是在 28 日运行该作业,因为每个月至少有 28 天。
删除任何超过 7 年的分区。这可以节省不需要存储的数据的空间。
作者一直将功能称为“螺栓连接”。借用一个英语表达——“bollocks”。SQL 供应商并不愚蠢,他们与时俱进。就像银行推出电子邮件转账让我们的生活更轻松一样,SQL 供应商也添加了所需的功能,如 XML、JSON、窗口函数、分区等。
这些功能中的每一个都经过深思熟虑,这就是它们如此可靠的原因。以 JSON 为例:虽然不同的供应商对 JSON 的实现不同,但他们有共同的思路:
名为 JSON 的单一类型可以在内部表示对象、数组、字符串、数字、布尔值或 Null 值
用于访问特定对象键或数组元素的运算符
将 JSON 数组转换为 JSON 值元素行的函数,反之亦然
将 JSON 对象转换为字符串键和 JSON 值行的函数,反之亦然
使用路径表达式挖掘 JSON 值的运算符/函数
用于确定特定 JSON 值包含六种有效 JSON 值类型中哪一种的函数
可以对单个 JSON 属性或所有属性创建索引
实际上,SQL 行并不多,您可以执行以下操作:
将对象转换为键和值列的行
使用 where 子句消除与某些模式匹配的任何键,这些模式表示不应向应用程序公开的内部存储详细信息
从剩余行重建一个新对象
在上述过程中,重要的部分是键和值表具有类型为 JSON 的值列:SQL 表的每一列必须具有单一类型,但 JSON 键的值可以是六种不同的有效值中的任何一种JSON 值。这就是为什么单类型 JSON 可以在内部存储六种值类型中的任何一种,这与应用层处理 JSON 的方式不同。
实际上,SQL 对 JSON 的支持与 SQL 哲学和模式相当一致。有很好的文档解释它,不需要是天才就能使用它。在常见网站(如供应商文档页面)上有大量示例可供参考,但愿上帝保佑。
SQL 供应商提供的每个新功能都同样细致地以有意义的方式实现它,并考虑该功能可能需要什么样的运算符、函数、聚合函数、窗口函数等。每个新功能的工作方式都像是经过精心策划的,并且与其他功能相当相似。当然,由于用户请求,有时会考虑额外的用例,并且随着时间的推移会添加额外的运算符等。
没有什么是事后才添加的。
不,不是。任何具有反射和 SQL 支持的语言都必然具有一个或多个现成的 ORM 解决方案,用于将行转换为对象/结构/无论该语言如何称呼它,反之亦然。Java JPA 之类的一些解决方案非常复杂,但在我看来,这是 Java 社区小题大做的症状,几乎所有事情都如此。
一般来说,语言具有如下所示的低级数据库支持,这些 ORM 是建立在这些基础上的:
打开连接
使用参数化查询创建语句
设置参数值
通过以下两种方式之一执行查询:
对于写查询,是成功还是失败
对于读取查询,将结果收集到一个或多个添加到某种列表/数组/无论其名称的对象中
与任何事情一样,如果您看到的 ORM 太复杂,您可以制作一个简单的。您至少需要弄清楚将 SQL 类型转换为应用程序类型的反射细节,反之亦然。这并不像听起来那么糟糕,因为大多数 SQL 驱动程序实现都允许一定的灵活性,例如您可以要求任何类型的字符串。ORM 不一定是为您生成查询、缓存结果等的野兽。
您还可以编写一个采用某种规范的代码生成器 - 如果我今天这样做,我会使用 TOML - 并生成数据传输对象 (DTO) 表示表的列,以及具有如下操作方法的数据访问对象 (DAO):
更新插入一个DTO
更新插入许多 DTO
按 id 选择一行
按 ID 选择多行
按id删除一行
按 ID 删除多行
辅助方法
填充来自 DTO 的语句,反之亦然
将查询结果收集到一个 DTO 或一组 DTO 中
任何自定义查询都可以编写为不同目录中生成的 DAO 子类的方法,并利用辅助方法来转换 SQL 行<-> DTO。当代码生成器稍后重新运行时,它可以首先擦除生成的类的目录,以确保:
任何不再相关的先前生成的类都将被删除
自定义子类保持不变
可以说,制定解决方案听起来像是浪费时间,但如果您有需要这样做的情况,为什么不呢?EG,在 Java 中,有比 JPA 更不流行、更简单的解决方案,正是因为像我一样,他们认为 JPA 是一个过度的内存消耗者。必须有人编写那些更简单的解决方案。
在这种情况下,作者可能有一个观点——但这并不意味着应该放弃 SQL。仅仅因为你遇到了 SQL 不擅长的情况,并不意味着完全抛弃它。相反,它意味着基于 SQL 数据库中的数据添加另一个解决方案。
我没有使用过实时数据库,但我确信有一些方法可以在添加 SQL 数据时通过某种复制从 SQL 数据填充实时数据库。SQL 数据库甚至可能内置或可以添加此类复制 - 例如,Postgres 具有外部数据包装器,在这种情况下可能会有所帮助。在某些情况下,您应该能够通过相应地调整 SQL 来使用它。
您不必拥有一个数据库来统治所有这些。通常,您可以使用 SQL 完成您需要的所有操作,但并非总是如此 - 就像其他操作一样。
不,他们不是。您只需要真正了解一些关键思想即可将它们区分开来:
表 A 到表 B 的所有连接有效地将表 A 和表 B 的所有列收集到一个平面列列表中
仅使用逗号意味着笛卡尔叉积 - 将表 a 的每一行乘以表 b 的每一行。当从表 a 和/或 b 中选择的数据是单行时,最常使用此方法,这样您只需乘以一。可以使用关键字 CROSS APPLY 代替逗号。
表 a 到表 b 的左连接是可选的 - b 中的相应行可能不存在,在这种情况下,b 的所有列对于该特定行均为空。这可以通过检查 b 的主键列是否为空来检测。
a表与b表的右连接要求b中存在相应的行,否则,a中的行将被过滤掉。它有效地充当 where 标准,并且可以重写为左连接,其中 b 的主键不为空。
表 A 到表 B 的完全连接提供了三种类型的行:
表A和表B中均存在一行并且所有列都有数据
仅表 A 中存在一行,表 B 的所有列均为空
仅表 B 中存在一行,表 A 的所有列均为空
由于您可以拥有 JSON 类型的列,因此您可以使用 JSON 来存储常见 SQL 表定义的各种反例的字段,例如:
很少使用的字段,仅存在于极小比例的行中
用户定义字段
其类型可能因某些非 JSON 列和/或 JSON 属性的其他值而异的字段
您不必仅使用非 JSON 或仅使用 JSON 列。您可以出于充分的理由混合搭配。
这并不完全正确。是的,某些查询可能无法很好地扩展,但这就是正确的应用程序设计发挥作用的地方。您应该将应用程序设计为具有单独的模型,其中只有模型代码知道您正在使用 SQL,并包含所有 SQL 查询。
如果给定的查询无法以某种方式扩展,那么模型可以执行任何必要的操作来加速该查询,例如:
使用 EXPLAIN 命令找出查询速度慢的原因,并采取措施,例如:
添加另一个索引
开始使用分区
添加物化视图 - 由表支持的视图,用于存储结果,您必须定期刷新视图
编写更高效的模型代码,例如,可以使用每个查询更少数据的多个查询,并为优化器提供更好的索引选择。
使用Redis缓存数据
我知道有些人会说“但是我们正在使用微服务,而且他们每个人都拥有自己的表”。
由于各种原因,将每项服务都放在桌面上并不是很好,例如:
如果需要像行级授权这样的功能,则必须在每个服务中实现它
如果某些表需要额外的解决方案(例如缓存),则很难知道哪些表使用这些额外的解决方案
更好的想法是拥有一个微服务,其唯一职责是执行所有查询
它充当阻塞点,是实现行级授权等功能的单一位置
这是单一职责原则的一种形式,其中一个服务只是数据,而每个其他服务只是处理细节,例如一种数据类型的验证
仅仅因为将每项服务都放在其桌面上很受欢迎,并不意味着它就是一个好主意
我确信它有用例,但我个人从来没有在我从事过的任何项目中需要它。您不必选择规范化或非规范化 - 您可以混合使用两者,其中一个视图可用于生成多个规范化表的非规范化视图。
这样的视图可以具体化为充当缓存以加速结果,并且具有不必在多个查询中不断复制相同连接条件的额外好处。这就是为什么纯粹主义者建议始终使用视图 - 它允许随着时间的推移以任何需要的方式操作视图,而不必总是调整应用程序代码。
您的 SQL 供应商可能支持“INSTEAD OF”触发器,仅适用于视图。可以将此类触发器添加到非规范化视图中,以将非规范化数据转换为规范化表。
原始文章读起来就像一个对深入学习 SQL 不感兴趣的人。在我的职业生涯中,我不断遇到这种情况,但近年来更是如此。在我看来,过去 15 年里发生了以下事件:
当我刚开始时,软件公司有 DBA,他们帮助开发人员提供有关提高性能以及如何进行某些类型查询的建议
公司停止招聘 DBA,留下了一个对数据库没有深入了解的人的缺口
公司开始使用云,其中大部分数据库管理都转移给云公司
像 JPA 这样的 ORM 突然出现,变得非常复杂,并成为事实上的标准工具
开发人员已经习惯了不必编写 SQL
由于这一进展,如今许多开发人员:
可能从未与 DBA 合作过
不习惯编写任何真正复杂的 SQL 查询
不知道有哪些功能可用于加速查询或扩展性能
不知道他们的SQL数据库可以做以下任何事情:全文检索,图查询,分层查询
这种知识差距并不完全是由于开发人员造成的,公司需要确保他们拥有一些数据库开发人员。也许并不是每个团队都需要数据库开发人员,但公司需要为有需要的人提供一些数据库开发人员。一般开发人员需要养成这样的习惯:假设他们的 SQL 数据库能够满足他们的需求,直到更精明的数据库开发人员另有说法。
一点常识大有帮助:
如果没有人理解某些查询的工作原理,那与没有人理解某些 Java 代码的工作原理有何不同?可以使用相同的解决方案来解决这两个问题:适当的注释和文档
如果您在数据库中使用多种不同的策略,请记录每种策略,包括正在解决的问题以及如何通过具体示例解决该问题
CTE 是一种让 SQL 以自上而下的方式可读的好方法,更像应用程序代码的过程
评论查询就像评论应用程序代码一样
我不是一个白胡子老甘道夫 DBA。我是一名对数据库非常感兴趣的开发人员,我们的团队正在将 ETL 从 MSSQL Server 迁移到 BigQuery。之所以有很多 BQ 函数和过程,是因为原始的 MSSQL 解决方案就是这样编写的,而且这是翻译代码的最简单的方法。其他不是我并且没有参与原始实现的人可以理解所使用的方法,并且可以跟上我们正在编写的查询类型。
SQL 应该保留很长一段时间,因为它是操作数据的优秀语言。就像任何其他语言一样,那些投入学习它的人会找到理由说它是一个好的解决方案,而那些不投资学习它的人会找到理由说它不是一个好的解决方案。
作者:Greg Hall
更多技术干货请关注公号【云原生数据库】
squids.cn,云数据库RDS,迁移工具DBMotion,云备份DBTwin等数据库生态工具。
irds.cn,多数据库管理平台(私有云)。