一些初步理论
要了解如何针对繁重的插入工作量进行优化,我建议了解在数据库中插入数据所涉及的主要开销。一旦了解了各种开销,所有优化之王自然会出现。好处是你们都会对解决方案更有信心,对数据库有更多的了解,并且可以将这些优化应用到多个引擎(MySQL、PostgreSQl、Oracle 等)。
我首先列出了插入开销的非详尽列表,然后展示了避免此类开销的简单解决方案。
1. SQL 查询开销: 为了与数据库通信,您首先需要创建与服务器的网络连接、传递凭据、验证凭据、序列化数据并通过网络发送,等等。
并且一旦查询被接受,就需要对其进行解析,对其语法进行验证,必须对数据类型进行解析和验证,查询所搜索到的对象(表、索引等)以及访问权限进行检查等等。这些步骤(我确信我在这里忘记了很多事情)在插入单个值时代表了显着的开销。开销如此之大,以至于某些数据库,例如Oracle,有一个 SQL 缓存来避免这些开销。
解决方案:重用数据库连接,使用准备好的语句,并在每个 SQL 查询中插入许多值(1000 到 100000)。
2.确保强 ACID 保证: 数据库的 ACID 属性是以提前记录对数据库的所有逻辑和物理修改为代价的,并且需要复杂的同步技术(细粒度锁定和/或快照隔离)。处理 ACID 保证所需的实际时间可能比在数据库页面中实际复制 200B 行所需的时间高几个数量级。
解决方案: 在表中导入数据时禁用撤消/重做日志记录。或者,您也可以 (1) 降低隔离级别以权衡较弱的 ACID 保证以降低开销或 (2) 使用异步提交(允许数据库引擎在重做日志正确硬化到磁盘之前完成插入的功能) .
3.更新物理设计/数据库约束: 在表中插入值通常需要更新多个索引、物化视图和/或执行各种触发器。这些开销很容易再次支配插入时间。
解决方案:您可以考虑在插入/导入期间删除所有辅助数据结构(索引、物化视图、触发器)。完成大部分插入后,您可以重新创建它们。例如,从头开始创建索引比通过单独插入填充索引要快得多。
在实践中
现在让我们看看我们如何将这些概念应用到您的特定设计中。我在您的案例中看到的主要问题是插入请求是由许多分布式客户端发送的,因此对插入进行批量处理的机会很小。
您可以考虑在您最终拥有的任何数据库引擎前面添加一个缓存层。我认为 memcached 不适合实现这样的缓存层——memcached 通常用于缓存查询结果而不是新插入。我有使用 VoltDB 的个人经验,我绝对推荐它(我与该公司没有任何联系)。 VoltDB 是一个内存中、横向扩展的关系数据库,针对事务工作负载进行了优化,它应该为您提供比 MongoDB 或 MySQL 高几个数量级的插入性能。它是开源的,但并非所有功能都是免费的,所以我不确定您是否需要为许可证付费。如果您不能使用 VoltDB,您可以查看 MySQL 的内存引擎或其他类似的内存引擎。
您可以考虑的另一个优化是使用不同的数据库进行分析。最有可能的是,具有高数据摄取量的数据库在执行 OLAP 样式查询方面非常糟糕,反之亦然。回到我的建议,VoltDB 也不例外,在执行长分析查询方面也不是最佳的。这个想法是创建一个后台进程来读取前端数据库中的所有新数据(即,这将是一个 VoltDB 集群)并将其批量移动到后端数据库以进行分析(MongoDB 或更高效的东西)。然后,您可以对批量数据移动应用上述所有优化,创建一组丰富的附加索引结构以加快数据访问速度,然后运行您喜欢的分析查询并将结果保存为一组新的表/物化以供以后访问。导入/分析过程可以在后台不断重复。