使用缓存的查询结果

本文档介绍如何在 BigQuery 中使用缓存结果。

概览

BigQuery 会将所有查询结果写入表中。该表可由用户明确标识(目标表),也可以是临时的缓存结果表。临时的缓存结果表按每个用户、每个项目进行维护。使用临时表不会产生任何存储费用,但如果您将查询结果写入永久表,则需要支付这些数据的存储费用。

所有查询结果(包括交互式查询和批量查询)都会在临时表中缓存大约 24 小时,但有一些例外情况

限制

使用查询缓存时,会受到以下限制:

  • 当您运行重复的查询时,BigQuery 会尝试重复使用缓存结果。要从缓存中检索数据,重复查询文本必须与原始查询完全相同。
  • 要使查询结果保留在缓存结果表中,结果集必须小于响应大小上限。如需详细了解如何管理大型结果集,请参阅返回大型查询结果
  • 在使用 DML 语句时,不能将缓存结果表指定为目标表。
  • 尽管当前语义允许,但强烈建议您不要将缓存结果用作从属作业的输入。例如,不应提交从缓存表中检索结果的查询作业,而是应该将结果写入已命名的目标表中。为方便清理,数据集级层 defaultTableExpirationMs 属性等功能可以在指定期限过后自动使数据过期。

价格和配额

如果查询结果是从缓存结果表中检索到的,则作业统计信息属性 statistics.query.cacheHit 将返回 true 值,您无需为该查询支付费用。虽然使用缓存结果的查询不会产生费用,但这些查询仍受 BigQuery 配额政策约束。由于 BigQuery 不需要计算结果集,使用缓存结果的查询不但可以降低费用,而且还可大幅加快执行速度。

查询缓存的例外情况

在以下情况下,查询结果将无法缓存:

  • 在作业配置、Cloud Console、经典版网页界面、bq 命令行工具或 API 中指定目标表时
  • 自结果上次缓存之后,其引用的表或逻辑视图有任何更改
  • 查询引用的任何表最近收到了流式插入内容(有流式缓冲区附加到此表),即使没有新增任何行
  • 查询使用非确定性函数;例如,日期和时间函数(如 CURRENT_TIMESTAMP()NOW())以及其他函数(如 CURRENT_USER())会根据查询执行时间返回不同的值
  • 使用通配符查询多个表
  • 缓存结果已过期;一般缓存有效期为 24 小时,但系统会尽量缓存查询结果,因此缓存有效期可能更短。
  • 查询是针对外部数据源运行

缓存结果的存储方式

您在运行查询时,系统会在一个特殊的数据集(称为“匿名数据集”)中创建临时缓存结果表。与从 IAM 资源层次结构模型(项目和组织权限)继承权限的常规数据集不同,只有数据集所有者能够访问匿名数据集。匿名数据集的所有者就是运行的查询生成了缓存结果的用户。

创建匿名数据集时,系统将为运行查询作业的用户明确授予对匿名数据集的 bigquery.dataOwner 访问权限。bigquery.dataOwner 访问权限仅可让运行查询作业的用户完全控制数据集,包括完全控制匿名数据集中的缓存结果表。如果您打算共享查询结果,请勿使用匿名数据集中存储的缓存结果。 正确的做法是将结果写入已命名的目标表中。

虽然运行该查询的用户具有对数据集和缓存结果表的完整访问权限,但强烈建议您不要将其用作从属作业的输入。

匿名数据集的名称以下划线开头。因此,Cloud Console 和经典版 BigQuery 网页界面会隐藏此类名称,而不会将其显示在数据集列表中。您可以使用 bq 命令行工具或 API 列出匿名数据集并审核匿名数据集的访问权限控制机制。

禁止检索缓存的结果

当您选中使用缓存的结果选项后,除非查询的表已更改,否则您可以重复利用之前运行的相同查询的结果。使用缓存的结果仅对重复查询有利。对于新查询,使用缓存的结果选项虽然默认为启用,但没有任何效果。

当您在已停用使用缓存的结果选项的情况下重复查询时,将覆盖现有的缓存结果。这会要求 BigQuery 来计算查询结果,因此将向您收取该查询的费用。这在基准化分析场景中尤其有用。

如果您要停用缓存结果检索功能并强制对查询作业进行实时评估,可以将查询作业的 configuration.query.useQueryCache 属性设置为 false

要停用使用缓存的结果选项,请执行以下操作:

控制台

  1. 打开 Cloud Console。
    转到 BigQuery 页面

  2. 点击编写新查询

  3. 查询编辑器文本区域中输入有效的 SQL 查询。

  4. 点击更多,然后选择查询设置

    查询设置

  5. 缓存偏好设置下,取消选中使用缓存的结果

    缓存的结果选项

经典版界面

  1. 转到经典版 BigQuery 网页界面。
    转到 BigQuery 网页界面

  2. 点击 Compose query 按钮。

  3. New Query 文本区域中输入有效的 SQL 查询。

  4. 点击 Show Options

  5. 取消选中 Use Cached Results

缓存的结果选项

bq

使用 nouse_cache 标志覆盖查询缓存。以下示例强制 BigQuery 在不使用现有缓存结果的情况下处理查询:

 bq query \
 --nouse_cache \
 --batch \
 'SELECT
    name,
    count
  FROM
    `my-project`.mydataset.names_2013
  WHERE
    gender = "M"
  ORDER BY
    count DESC
  LIMIT
    6'

API

要在不使用现有缓存结果的情况下处理查询,请在 query 作业配置中将 useQueryCache 属性设置为 false

Go

在尝试此示例之前,请按照《BigQuery 快速入门:使用客户端库》中的 Go 设置说明进行操作。如需了解详情,请参阅 BigQuery Go API 参考文档

import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/bigquery"
	"google.golang.org/api/iterator"
)

// queryDisableCache demonstrates issuing a query and requesting that the query cache is bypassed.
func queryDisableCache(w io.Writer, projectID string) error {
	// projectID := "my-project-id"
	ctx := context.Background()
	client, err := bigquery.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("bigquery.NewClient: %v", err)
	}
	defer client.Close()

	q := client.Query(
		"SELECT corpus FROM `bigquery-public-data.samples.shakespeare` GROUP BY corpus;")
	q.DisableQueryCache = true
	// Location must match that of the dataset(s) referenced in the query.
	q.Location = "US"

	// Run the query and print results when the query job is completed.
	job, err := q.Run(ctx)
	if err != nil {
		return err
	}
	status, err := job.Wait(ctx)
	if err != nil {
		return err
	}
	if err := status.Err(); err != nil {
		return err
	}
	it, err := job.Read(ctx)
	for {
		var row []bigquery.Value
		err := it.Next(&row)
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		fmt.Fprintln(w, row)
	}
	return nil
}

Java

要在不使用现有缓存结果的情况下处理查询,请在创建 QueryJobConfiguration 时,将 set use query cache 设为 false

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.QueryJobConfiguration;
import com.google.cloud.bigquery.TableResult;

// Sample to running a query with the cache disabled.
public class QueryDisableCache {

  public static void runQueryDisableCache() {
    String query = "SELECT corpus FROM `bigquery-public-data.samples.shakespeare` GROUP BY corpus;";
    queryDisableCache(query);
  }

  public static void queryDisableCache(String query) {
    try {
      // Initialize client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();

      QueryJobConfiguration queryConfig =
          QueryJobConfiguration.newBuilder(query)
              // Disable the query cache to force live query evaluation.
              .setUseQueryCache(false)
              .build();

      TableResult results = bigquery.query(queryConfig);

      results
          .iterateAll()
          .forEach(row -> row.forEach(val -> System.out.printf("%s,", val.toString())));

      System.out.println("Query disable cache performed successfully.");
    } catch (BigQueryException | InterruptedException e) {
      System.out.println("Query not performed \n" + e.toString());
    }
  }
}

Node.js

在尝试此示例之前,请按照《BigQuery 快速入门:使用客户端库》中的 Node.js 设置说明进行操作。如需了解详情,请参阅 BigQuery Node.js API 参考文档

// Import the Google Cloud client library
const {BigQuery} = require('@google-cloud/bigquery');

async function queryDisableCache() {
  // Queries the Shakespeare dataset with the cache disabled.

  // Create a client
  const bigquery = new BigQuery();

  const query = `SELECT corpus
    FROM \`bigquery-public-data.samples.shakespeare\`
    GROUP BY corpus`;
  const options = {
    query: query,
    // Location must match that of the dataset(s) referenced in the query.
    location: 'US',
    useQueryCache: false,
  };

  // Run the query as a job
  const [job] = await bigquery.createQueryJob(options);
  console.log(`Job ${job.id} started.`);

  // Wait for the query to finish
  const [rows] = await job.getQueryResults();

  // Print the results
  console.log('Rows:');
  rows.forEach(row => console.log(row));
}

Python

在尝试此示例之前,请按照《BigQuery 快速入门:使用客户端库》中的 Python 设置说明进行操作。如需了解详情,请参阅 BigQuery Python API 参考文档

from google.cloud import bigquery

# Construct a BigQuery client object.
client = bigquery.Client()

job_config = bigquery.QueryJobConfig(use_query_cache=False)
sql = """
    SELECT corpus
    FROM `bigquery-public-data.samples.shakespeare`
    GROUP BY corpus;
"""
query_job = client.query(sql, job_config=job_config)  # Make an API request.

for row in query_job:
    print(row)

确保使用缓存

如果您使用 jobs.insert 函数运行查询,则可以将 copy 作业配置的 createDisposition 属性设置为 CREATE_NEVER,借此强制查询作业在无法使用缓存结果的情况下失败。

如果缓存中不存在查询结果,系统会返回 NOT_FOUND 错误。

验证是否使用缓存

您可以通过以下两种方式确定 BigQuery 是否使用缓存返回结果:

  • 如果您使用的是 Cloud Console 或经典版 BigQuery 网页界面,则结果字符串不会包含有关已处理字节数的信息,但会显示“cached”字样。

    界面中的缓存指示符

  • 如果您使用的是 BigQuery API,则查询结果中的 cacheHit 属性会设置为 true

列级别安全性的影响

默认情况下,除上述例外情况以外,BigQuery 会将查询结果缓存 24 小时。24 小时缓存功能还适用于查询使用政策标记进行列级别安全性保护的数据。某些更改(例如将某个群组或用户从用于政策标记的 Data Catalog Fine Grained Reader 角色中移除)不会导致 24 小时缓存功能失效。对 Data Catalog Fine Grained Reader 访问权限控制组本身进行的更改会立即传播,但该更改不会使缓存失效。

由此产生的影响是,如果用户运行了查询,则该用户仍然可以在屏幕上看到查询结果。即使该用户在过去 24 小时内无法访问数据,也可以从缓存中检索这些结果。

在用户从用于政策标记的 Data Catalog Fine Grained Reader 角色中被移除后的 24 小时内,该用户只能访问其之前可查看的数据的缓存数据。如果表中添加了行,那么即使缓存了结果,用户也不会看到所添加的行。