基于主键的高并发点查询

SinceVersion 2.0.0

背景

Doris 基于列存格式引擎构建,在高并发服务场景中,用户总是希望从系统中获取整行数据。但是,当表宽时,列存格式将大大放大随机读取 IO。Doris 查询引擎和计划对于某些简单的查询(如点查询)来说太重了。需要一个在FE的查询规划中规划短路径来处理这样的查询。FE 是 SQL 查询的访问层服务,使用 Java 编写,分析和解析 SQL 也会导致高并发查询的高 CPU 开销。为了解决上诉问题,我们在Doris中引入了行存、短查询路径、PreparedStatment来解决上诉问题, 下面是开启这些优化的指南。

行存

用户可以在Olap表中开启行存模式,但是需要额外的空建来存储行存。目前的行存实现是将行存编码后存在单独的一列中,这样做是用于简化行存的实现。行存模式默认是关闭的,如果您想开启则可以在建表语句的property中指定如下属性

  1. "store_row_column" = "true"

在merge-on-write模型下的点查优化

上诉的行存用于在merge-on-write模型下减少点查时的IO开销。当enable_unique_key_merge_on_write在建表开启时,对于主键的点查会走短路径来对SQL执行进行优化,仅需要执行一次RPC即可执行完成查询。下面是点查结合行存在merge-on-write下的一个例子:

  1. CREATE TABLE `tbl_point_query` (
  2. `key` int(11) NULL,
  3. `v1` decimal(27, 9) NULL,
  4. `v2` varchar(30) NULL,
  5. `v3` varchar(30) NULL,
  6. `v4` date NULL,
  7. `v5` datetime NULL,
  8. `v6` float NULL,
  9. `v7` datev2 NULL
  10. ) ENGINE=OLAP
  11. UNIQUE KEY(`key`)
  12. COMMENT 'OLAP'
  13. DISTRIBUTED BY HASH(`key`) BUCKETS 1
  14. PROPERTIES (
  15. "replication_allocation" = "tag.location.default: 1",
  16. "enable_unique_key_merge_on_write" = "true",
  17. "light_schema_change" = "true",
  18. "store_row_column" = "true"
  19. );

[NOTE]

  1. enable_unique_key_merge_on_write应该被开启, 存储引擎需要根据主键来快速点查
  2. 当条件只包含主键时,如 select * from tbl_point_query where key = 123,类似的查询会走短路径来优化查询
  3. light_schema_change 应该被开启, 因为主键点查的优化依赖了轻量级schema change中的column unique id来定位列

使用 PreparedStatement

为了减少SQL解析和表达式计算的开销, 我们在FE端提供了与mysql协议完全兼容的PreparedStatement特性(目前只支持主键点查)。当PreparedStatement在FE开启,SQL和其表达式将被提前计算并缓存到session级别的内存缓存中,后续的查询直接使用缓存对象即可。当CPU成为主键点查的瓶颈, 在开启PreparedStatement后,将会有4倍+的性能提升。下面是在JDBC中使用PreparedStatement的例子

  1. 设置JDB url并在server端开启prepared statement
  1. url = jdbc:mysql://127.0.0.1:9030/ycsb?useServerPrepStmts=true
  2. ``
  3. 2. 使用 `PreparedStatement`
  4. ```java
  5. // use `?` for placement holders, readStatement should be reused
  6. PreparedStatement readStatement = conn.prepareStatement("select * from tbl_point_query where key = ?");
  7. ...
  8. readStatement.setInt(1234);
  9. ResultSet resultSet = readStatement.executeQuery();
  10. ...
  11. readStatement.setInt(1235);
  12. resultSet = readStatement.executeQuery();
  13. ...