使用 GoogleSQL for Bigtable 开发应用

您可以使用 GoogleSQL for Bigtable 从应用运行查询,以将计算函数卸载到 Bigtable。 这样可以减少对客户端处理的需求。GoogleSQL 是一种 符合 ANSI 的结构化查询语言,适用于 Bigtable 和其他 Google Cloud 服务。如需详细了解 GoogleSQL for Bigtable 的其他用途,请参阅 GoogleSQL for Bigtable 概览

准备工作

确保您具有以下各项:

示例情境

本指南中的示例使用示例 weather-data 表,您可以在控制台中通过 免费试用 实例访问该表。Google Cloud 假设您的表存储来自不同气象站的信息,这些气象站由 station_id 标识。每个气象站都会在名为 temp_f 的列中记录华氏温度。您无需在应用中计算温度,而是可以使用 SQL 查询直接在 Bigtable 中将这些温度转换为摄氏温度,然后再将结果发送回应用。如需详细了解如何查询天气数据表,请参阅查询示例 数据

最佳做法

如需优化性能和资源用量,请在开发应用时考虑以下最佳实践:

  • 重复使用客户端: 为应用创建一个 BigtableDataClient。该客户端负责处理 gRPC 渠道管理,专为高并发而设计。我们建议您在应用启动后创建此客户端一次,然后将其重复用于每个查询。
  • 使用参数化查询: 使用 PreparedStatement 将查询逻辑与数据值分开。这样一来,Bigtable 就可以缓存执行计划并防止 SQL 注入。
  • 重复使用预处理语句: 每个 SQL 字符串仅调用 PreparedStatement 一次。跨多个请求存储和重复使用该语句。

创建 Bigtable 客户端

如需处理应用与 Bigtable 之间的连接,请创建 BigtableDataClient。以下示例展示了如何在应用中共享单个客户端实例,以高效管理连接:

BigtableDataSettings settings = BigtableDataSettings.newBuilder()
    .setProject(PROJECT_ID)
    .setInstance(INSTANCE_ID)
    .build();

// The client is thread-safe and should be reused.
try (BigtableDataClient dataClient = BigtableDataClient.create(settings)) {
    System.out.println("Connected to: " + INSTANCE_ID);
}

准备查询

我们建议您使用参数化查询,而不是将数据直接放入 SQL 字符串中。 在 GoogleSQL for Bigtable 中,参数化查询相当于 预处理语句。这样一来,您就可以将查询逻辑与数据值分开。

以下示例展示了如何使用参数占位符(例如 @sid)定义 GoogleSQL 查询。然后,通过指定 SQL 字符串和每个参数的类型来创建 PreparedStatement,以便下次查询可以更快运行:

Map<String, SqlType<?>> paramTypes = new HashMap<>();
paramTypes.put("sid", SqlType.string());

String sql = "SELECT " +
             "ROUND((CAST(CAST(weather['temp_f'] AS STRING) AS INT64) - 32) * 5 / 9, 2) AS temp_celsius " +
             "FROM " + TABLE_NAME + " WHERE station_id = @sid LIMIT 1";

// Create and reuse the PreparedStatement.
PreparedStatement preparedStatement = dataClient.prepareStatement(sql, paramTypes);

运行查询

提供参数值并运行查询。以下示例展示了如何使用 PreparedStatement 将值绑定到参数以创建 BoundStatement。然后,它会运行查询并遍历结果以输出 ResultSet,即摄氏温度:

BoundStatement boundStatement = preparedStatement.bind()
    .setStringParam("sid", stationId)
    .build();

try (ResultSet resultSet = dataClient.executeQuery(boundStatement)) {
    while (resultSet.next()) {
        System.out.println("Temp: " + resultSet.getDouble("temp_celsius"));
    }
}

检查数据结构

在开发或探索 SQL 时,我们建议您探索与表关联的元数据,以了解数据结构。由于 Bigtable 具有灵活的架构,因此如果您使用 SELECT * 查询,查询结果可能会根据数据而变化。因此,我们建议您不要在生产应用中使用 SELECT * 查询。

以下示例查看了结果集的元数据(例如列名称和类型),然后将这些元数据独立于数据发送回客户端。这样一来,您就可以先获取列信息,然后再创建处理应用中数据所需的数据结构。

ResultSetMetadata metadata = resultSet.getMetadata();
System.out.print("Columns: ");
metadata.getColumnsList().forEach(col -> System.out.print(col.getName() + " "));
System.out.println();

使用列族

Bigtable 将数据存储在列族中。GoogleSQL 会将这些列族作为名称和值的列表返回,这些列表称为 列限定符和值的映射 。读取列族时,Bigtable 会返回 SqlType.Map 类型。从具有历史记录的表(例如 SELECT column FROM my_table(with_history=>true))读取列族时,Bigtable 会返回 SqlType.historicalMap() 类型,该类型是列限定符的映射以及单元格时间戳和值的数组。如需查看更多 SqlType 类型示例,请参阅 SqlType

以下示例展示了如何一次获取整个列组,而不是一次获取一列。该代码将列族存储在映射中,以便您可以遍历所有名称和值:

Map<String, SqlType<?>> paramTypes = new HashMap<>();
paramTypes.put("keyPrefix", SqlType.bytes());
String sql = String.format("SELECT weather FROM %s WHERE STARTS_WITH(_key, @keyPrefix)", TABLE_NAME);
PreparedStatement preparedStatement = dataClient.prepareStatement(sql, paramTypes);
BoundStatement boundStatement = preparedStatement.bind()
   .setBytesParam("keyPrefix", ByteString.copyFromUtf8(key))
   .build();


try (ResultSet resultSet = dataClient.executeQuery(boundStatement)) {
   while (resultSet.next()) {
     SqlType.Map<ByteString, ByteString> mapType = SqlType.mapOf(SqlType.bytes(), SqlType.bytes());
     Map<ByteString, ByteString> weatherValues = resultSet.getMap("weather", mapType);
     for (Map.Entry<ByteString, ByteString> entry : weatherValues.entrySet()) {
       System.out.printf("%s = %s\n",
         entry.getKey().toStringUtf8(),
         entry.getValue().toStringUtf8());
   }
 }
}

后续步骤