管理查询优化器

Spanner 查询优化器会确定执行 SQL 查询的最有效方式。但是,当查询优化器本身演变或更新数据库统计信息时,由优化器确定的查询计划可能会略有变化。为了最大限度地减少查询优化器或统计信息更改时性能下降的风险,Spanner 提供以下查询选项。

  • optimize_version:对查询优化器进行捆绑并作为优化器版本发布。Cloud Spanner 会在最新版本发布至少 30 天后开始使用该优化器作为默认优化器。您可以使用查询优化器版本选项来针对旧版优化器运行查询。

  • optimize_statistics_package:Spanner 定期更新优化器统计信息。新统计信息以软件包的形式提供。此查询选项会指定一个统计信息软件包,供查询程序在编译 SQL 查询时使用。指定的软件包必须停用垃圾回收:

    ALTER STATISTICS <package_name> SET OPTIONS (allow_gc=false)

本指南介绍了如何在 Spanner 的不同范围中设置这些单独的选项。

列出查询优化器选项

Spanner 会存储有关您可以选择的优化器版本和统计信息软件包的信息。

优化器版本

查询优化器版本是一个整数值,每次更新时递增 1。最新版本的查询优化器为 5

执行以下 SQL 语句以返回所有受支持的优化器版本的列表及其相应的发布日期,以及该版本是否为默认版本。返回的最大版本号是支持的优化器最新版本。

SELECT * FROM SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS;

默认版本

默认情况下,Spanner 会在最新版本发布后的至少 30 天内开始使用最新版本的优化器。从新版本到默认版本为期 30 天的宽限期内,建议您根据新版本测试查询,以检测是否有任何回归问题。

如需查找默认版本,请执行以下 SQL 语句:

SELECT * FROM SPANNER_SYS.SUPPORTED_OPTIMIZER_VERSIONS;

查询将返回所有受支持的优化器版本的列表。IS_DEFAULT 列指定的是当前默认版本。

如需详细了解每个版本,请参阅查询优化器版本版本记录

优化器统计信息软件包

Spanner 创建的每个新的优化器统计信息软件包都会分配一个软件包名称,该名称在给定数据库中必须是唯一的。

执行以下 SQL 语句,以返回所有可用的优化器统计信息软件包的列表。自动构造的软件包名称为 auto_{PACKAGETIMESTAMP}UTCANALYZE 语句触发的统计信息软件包名称为 analyze{PACKAGE_TIMESTAMP}UTC,其中 {PACKAGE_TIMESTAMP} 为统计信息开始构建的时间戳,以世界协调时间 (UTC) 时区表示。

SELECT * FROM INFORMATION_SCHEMA.SPANNER_STATISTICS;

默认情况下,除非数据库或查询使用本页介绍的方法之一固定到旧版软件包,否则 Spanner 会使用最新的优化器统计信息软件包。

选项替换优先级

如果您使用 Google 标准 SQL 方言数据库,Spanner 提供了多种更改优化器选项的方法。例如,您可以针对特定查询设置选项,或者在进程或查询级别在客户端库中配置该选项。以多种方式设置选项时,应遵循以下优先级顺序。(点击链接可跳转到本文档中的相应部分)。

Spanner 默认 ← 数据库选项客户端应用环境变量客户端查询语句提示

例如,下面说明了在设置查询优化器版本时如何解读优先级顺序:

创建数据库时,该数据库使用 Cloud Spanner 的默认优化器版本。使用上面列出的方法之一设置优化器版本会优先考虑其左侧的所有内容。例如,使用环境变量为应用设置优化器优先于使用数据库选项为数据库设置的任何值。对于指定查询,通过语句提示设置优化器版本的优先级最高,高于使用任何其他方法设置的值。

现在,让我们来更详细地了解每种方法。

在数据库层级设置优化器选项

您可以使用以下 ALTER DATABASE DDL 命令在数据库上设置默认优化器版本。

ALTER DATABASE MyDatabase
SET OPTIONS (optimizer_version = 5);

您可以以类似方式设置统计信息软件包,如以下示例所示。

ALTER DATABASE MyDatabase
SET OPTIONS (optimizer_statistics_package = "auto_20191128_14_47_22UTC");

您还可以同时设置多个选项,如以下 DDL 命令所示。

ALTER DATABASE MyDatabase
SET OPTIONS (optimizer_version = 5,
             optimizer_statistics_package = "auto_20191128_14_47_22UTC");

您可以使用 gcloud CLI databases ddl update 命令在 gcloud CLI 中运行 ALTER DATABASE,如下所示。

gcloud spanner databases ddl update MyDatabase --instance=test-instance \
    --ddl='ALTER DATABASE MyDatabase SET OPTIONS ( optimizer_version = 5 )'

将数据库选项设置为 NULL 可清除该选项,以便使用默认值。

如需查看数据库这些选项的当前值,请查询 INFORMATION_SCHEMA.DATABASE_OPTIONS 视图,如下所示。

SELECT
  s.OPTION_NAME,
  s.OPTION_VALUE
FROM
  INFORMATION_SCHEMA.DATABASE_OPTIONS s
WHERE
  s.SCHEMA_NAME=""
  AND s.OPTION_NAME IN ('optimizer_version', 'optimizer_statistics_package')

使用 Cloud Spanner 客户端库设置优化器选项

通过客户端库以编程方式与 Spanner 交互时,有多种方法可以更改客户端应用的查询选项。

您必须使用最新版客户端库来设置优化器选项。

为数据库客户端设置优化器选项

通过配置查询选项属性,应用可以在客户端库中全局设置优化器选项,如以下代码段所示。优化器设置存储在客户端实例中,并应用于在客户端的整个生命周期内运行的所有查询。即使这些选项在后端的数据库级别应用,当这些选项在客户端级别设置时,它们也会应用于连接到该客户端的所有数据库。

C++

namespace spanner = ::google::cloud::spanner;
spanner::Client client(
    spanner::MakeConnection(db),
    google::cloud::Options{}
        .set<spanner::QueryOptimizerVersionOption>("1")
        .set<spanner::QueryOptimizerStatisticsPackageOption>(
            "auto_20191128_14_47_22UTC"));

C#


using Google.Cloud.Spanner.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

public class CreateConnectionWithQueryOptionsAsyncSample
{
    public class Album
    {
        public int AlbumId { get; set; }
        public int SingerId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task<List<Album>> CreateConnectionWithQueryOptionsAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString)
        {
            // Set query options on the connection.
            QueryOptions = QueryOptions.Empty
                .WithOptimizerVersion("1")
                // The list of available statistics packages for the database can
                // be found by querying the "INFORMATION_SCHEMA.SPANNER_STATISTICS"
                // table.
                .WithOptimizerStatisticsPackage("latest")
        };

        var albums = new List<Album>();
        var cmd = connection.CreateSelectCommand("SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album
            {
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
            });
        }
        return albums;
    }
}

Go


import (
	"context"
	"fmt"
	"io"
	"time"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
	sppb "google.golang.org/genproto/googleapis/spanner/v1"
)

func createClientWithQueryOptions(w io.Writer, database string) error {
	ctx := context.Background()
	queryOptions := spanner.QueryOptions{
		Options: &sppb.ExecuteSqlRequest_QueryOptions{
			OptimizerVersion: "1",
			// The list of available statistics packages can be found by
			// querying the "INFORMATION_SCHEMA.SPANNER_STATISTICS" table.
			OptimizerStatisticsPackage: "latest",
		},
	}
	client, err := spanner.NewClientWithConfig(
		ctx, database, spanner.ClientConfig{QueryOptions: queryOptions},
	)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: `SELECT VenueId, VenueName, LastUpdateTime FROM Venues`}
	iter := client.Single().Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var venueID int64
		var venueName string
		var lastUpdateTime time.Time
		if err := row.Columns(&venueID, &venueName, &lastUpdateTime); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %s %s\n", venueID, venueName, lastUpdateTime)
	}
}

Java

static void clientWithQueryOptions(DatabaseId db) {
  SpannerOptions options =
      SpannerOptions.newBuilder()
          .setDefaultQueryOptions(
              db, QueryOptions
                  .newBuilder()
                  .setOptimizerVersion("1")
                  // The list of available statistics packages can be found by querying the
                  // "INFORMATION_SCHEMA.SPANNER_STATISTICS" table.
                  .setOptimizerStatisticsPackage("latest")
                  .build())
          .build();
  Spanner spanner = options.getService();
  DatabaseClient dbClient = spanner.getDatabaseClient(db);
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Node.js

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(
  databaseId,
  {},
  {
    optimizerVersion: '1',
    // The list of available statistics packages can be found by querying the
    // "INFORMATION_SCHEMA.SPANNER_STATISTICS" table.
    optimizerStatisticsPackage: 'latest',
  }
);

const query = {
  sql: `SELECT AlbumId, AlbumTitle, MarketingBudget
        FROM Albums
        ORDER BY AlbumTitle`,
};

// Queries rows from the Albums table
try {
  const [rows] = await database.run(query);

  rows.forEach(row => {
    const json = row.toJSON();
    const marketingBudget = json.MarketingBudget
      ? json.MarketingBudget
      : null; // This value is nullable
    console.log(
      `AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`
    );
  });
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished.
  database.close();
}

PHP

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Database;

/**
 * Create a client with query options.
 * Example:
 * ```
 * create_client_with_query_options($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function create_client_with_query_options($instanceId, $databaseId)
{
    $spanner = new SpannerClient([
        'queryOptions' => [
            'optimizerVersion' => '1',
            // Pin the statistics package used for this client instance to the
            // latest version. The list of available statistics packages can be
            // found by querying the "INFORMATION_SCHEMA.SPANNER_STATISTICS"
            // table.
            'optimizerStatisticsPackage' => 'latest'
        ]
    ]);
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $results = $database->execute(
        'SELECT VenueId, VenueName, LastUpdateTime FROM Venues'
    );

    foreach ($results as $row) {
        printf('VenueId: %s, VenueName: %s, LastUpdateTime: %s' . PHP_EOL,
            $row['VenueId'], $row['VenueName'], $row['LastUpdateTime']);
    }
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client(
    query_options={
        "optimizer_version": "1",
        "optimizer_statistics_package": "latest",
    }
)
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

with database.snapshot() as snapshot:
    results = snapshot.execute_sql(
        "SELECT VenueId, VenueName, LastUpdateTime FROM Venues"
    )

    for row in results:
        print(u"VenueId: {}, VenueName: {}, LastUpdateTime: {}".format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

query_options = {
  optimizer_version: "1",
  # The list of available statistics packages can be
  # found by querying the "INFORMATION_SCHEMA.SPANNER_STATISTICS"
  # table.
  optimizer_statistics_package: "latest"
}

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id, query_options: query_options

sql_query = "SELECT VenueId, VenueName, LastUpdateTime FROM Venues"

client.execute(sql_query).rows.each do |row|
  puts "#{row[:VenueId]} #{row[:VenueName]} #{row[:LastUpdateTime]}"
end

使用环境变量设置优化器选项

如需更轻松地尝试不同的优化器设置,而无需重新编译应用,您可以设置 SPANNER_OPTIMIZER_VERSIONSPANNER_OPTIMIZER_STATISTICS_PACKAGE 环境变量并运行应用,如以下代码段所示。

Linux/macOS

export SPANNER_OPTIMIZER_VERSION="5"
export SPANNER_OPTIMIZER_STATISTICS_PACKAGE="auto_20191128_14_47_22UTC"

Windows

set SPANNER_OPTIMIZER_VERSION="5"
  set SPANNER_OPTIMIZER_STATISTICS_PACKAGE="auto_20191128_14_47_22UTC"

指定的查询优化器选项值会在客户端初始化时读取并存储在客户端实例中,并适用于在整个客户端生命周期内运行的所有查询。

为客户端查询设置优化器选项

通过在构建查询时指定查询选项属性,您可以在客户端应用的查询级层为优化器版本或统计信息软件包版本指定值。

C++

void QueryWithQueryOptions(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto sql = spanner::SqlStatement("SELECT SingerId, FirstName FROM Singers");
  auto opts =
      google::cloud::Options{}
          .set<spanner::QueryOptimizerVersionOption>("1")
          .set<spanner::QueryOptimizerStatisticsPackageOption>("latest");
  auto rows = client.ExecuteQuery(std::move(sql), std::move(opts));

  using RowType = std::tuple<std::int64_t, std::string>;
  for (auto const& row : spanner::StreamOf<RowType>(rows)) {
    if (!row) throw std::runtime_error(row.status().message());
    std::cout << "SingerId: " << std::get<0>(*row) << "\t";
    std::cout << "FirstName: " << std::get<1>(*row) << "\n";
  }
  std::cout << "Read completed for [spanner_query_with_query_options]\n";
}

C#


using Google.Cloud.Spanner.Data;
using System.Collections.Generic;
using System.Threading.Tasks;

public class RunCommandWithQueryOptionsAsyncSample
{
    public class Album
    {
        public int SingerId { get; set; }
        public int AlbumId { get; set; }
        public string AlbumTitle { get; set; }
    }

    public async Task<List<Album>> RunCommandWithQueryOptionsAsync(string projectId, string instanceId, string databaseId)
    {
        var connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";
        using var connection = new SpannerConnection(connectionString);
        using var cmd = connection.CreateSelectCommand("SELECT SingerId, AlbumId, AlbumTitle FROM Albums");

        cmd.QueryOptions = QueryOptions.Empty
            .WithOptimizerVersion("1")
            // The list of available statistics packages for the database can
            // be found by querying the "INFORMATION_SCHEMA.SPANNER_STATISTICS"
            // table.
            .WithOptimizerStatisticsPackage("latest");
        var albums = new List<Album>();
        using var reader = await cmd.ExecuteReaderAsync();
        while (await reader.ReadAsync())
        {
            albums.Add(new Album()
            {
                AlbumId = reader.GetFieldValue<int>("AlbumId"),
                SingerId = reader.GetFieldValue<int>("SingerId"),
                AlbumTitle = reader.GetFieldValue<string>("AlbumTitle")
            });
        }
        return albums;
    }
}

Go


import (
	"context"
	"fmt"
	"io"
	"time"

	"cloud.google.com/go/spanner"
	"google.golang.org/api/iterator"
	sppb "google.golang.org/genproto/googleapis/spanner/v1"
)

func queryWithQueryOptions(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: `SELECT VenueId, VenueName, LastUpdateTime FROM Venues`}
	queryOptions := spanner.QueryOptions{
		Options: &sppb.ExecuteSqlRequest_QueryOptions{
			OptimizerVersion: "1",
			// The list of available statistics packages can be found by
			// querying the "INFORMATION_SCHEMA.SPANNER_STATISTICS" table.
			OptimizerStatisticsPackage: "latest",
		},
	}
	iter := client.Single().QueryWithOptions(ctx, stmt, queryOptions)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var venueID int64
		var venueName string
		var lastUpdateTime time.Time
		if err := row.Columns(&venueID, &venueName, &lastUpdateTime); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %s %s\n", venueID, venueName, lastUpdateTime)
	}
}

Java

static void queryWithQueryOptions(DatabaseClient dbClient) {
  try (ResultSet resultSet =
      dbClient
          .singleUse()
          .executeQuery(
              Statement
                  .newBuilder("SELECT SingerId, AlbumId, AlbumTitle FROM Albums")
                  .withQueryOptions(QueryOptions
                      .newBuilder()
                      .setOptimizerVersion("1")
                      // The list of available statistics packages can be found by querying the
                      // "INFORMATION_SCHEMA.SPANNER_STATISTICS" table.
                      .setOptimizerStatisticsPackage("latest")
                      .build())
                  .build())) {
    while (resultSet.next()) {
      System.out.printf(
          "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
    }
  }
}

Node.js

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

const query = {
  sql: `SELECT AlbumId, AlbumTitle, MarketingBudget
        FROM Albums
        ORDER BY AlbumTitle`,
  queryOptions: {
    optimizerVersion: 'latest',
    // The list of available statistics packages can be found by querying the
    // "INFORMATION_SCHEMA.SPANNER_STATISTICS" table.
    optimizerStatisticsPackage: 'latest',
  },
};

// Queries rows from the Albums table
try {
  const [rows] = await database.run(query);

  rows.forEach(row => {
    const json = row.toJSON();
    const marketingBudget = json.MarketingBudget
      ? json.MarketingBudget
      : null; // This value is nullable
    console.log(
      `AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}, MarketingBudget: ${marketingBudget}`
    );
  });
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished.
  database.close();
}

PHP

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Database;

/**
 * Queries sample data using SQL with query options.
 * Example:
 * ```
 * query_data_with_query_options($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function query_data_with_query_options($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $results = $database->execute(
        'SELECT VenueId, VenueName, LastUpdateTime FROM Venues',
        [
            'queryOptions' => [
                'optimizerVersion' => '1',
                // Pin the statistics package to the latest version just for
                // this query.
                'optimizerStatisticsPackage' => 'latest'
            ]
        ]
    );

    foreach ($results as $row) {
        printf('VenueId: %s, VenueName: %s, LastUpdateTime: %s' . PHP_EOL,
            $row['VenueId'], $row['VenueName'], $row['LastUpdateTime']);
    }
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

with database.snapshot() as snapshot:
    results = snapshot.execute_sql(
        "SELECT VenueId, VenueName, LastUpdateTime FROM Venues",
        query_options={
            "optimizer_version": "1",
            "optimizer_statistics_package": "latest",
        },
    )

    for row in results:
        print(u"VenueId: {}, VenueName: {}, LastUpdateTime: {}".format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

sql_query = "SELECT VenueId, VenueName, LastUpdateTime FROM Venues"
query_options = {
  optimizer_version: "1",
  # The list of available statistics packagebs can be
  # found by querying the "INFORMATION_SCHEMA.SPANNER_STATISTICS"
  # table.
  optimizer_statistics_package: "latest"
}

client.execute(sql_query, query_options: query_options).rows.each do |row|
  puts "#{row[:VenueId]} #{row[:VenueName]} #{row[:LastUpdateTime]}"
end

使用语句提示为查询设置优化器选项

语句提示是对查询语句的提示,会更改查询执行的默认行为。在语句上设置 OPTIMIZER_VERSION 提示会强制使用指定的查询优化器版本运行该查询。

OPTIMIZER_VERSION 提示具有最高的优化器版本优先级。如果指定了语句提示,则将使用该提示,而不考虑其他所有优化器版本设置。

@{OPTIMIZER_VERSION=5} SELECT * FROM MyTable;

您还可以使用 latest_version 字面量将查询的优化器版本设置为最新版本,如此处所示。

@{OPTIMIZER_VERSION=latest_version} SELECT * FROM MyTable;

对语句设置 OPTIMIZER_STATISTICS_PACKAGE 提示会强制使用指定的查询优化器统计信息软件包版本运行该查询。指定的软件包必须停用垃圾回收

`ALTER STATISTICS <package_name> SET OPTIONS (allow_gc=false)`

OPTIMIZER_STATISTICS_PACKAGE 提示具有优化器软件包设置的优先级。如果指定了语句提示,则无论其他所有优化器软件包版本设置为何,都将使用此语句提示。

@{OPTIMIZER_STATISTICS_PACKAGE=auto_20191128_14_47_22UTC} SELECT * FROM MyTable;

您还可以通过 latest 字面量来使用最新的统计信息软件包。

@{OPTIMIZER_STATISTICS_PACKAGE=latest} SELECT * FROM MyTable;

这两个提示都可以在单个语句中设置,如以下示例所示。

default_version 字面量会将查询的优化器版本设置为默认版本,此版本可能与最新版本不同。如需了解详情,请参阅默认版本

@{OPTIMIZER_VERSION=default_version, OPTIMIZER_STATISTICS_PACKAGE=auto_20191128_14_47_22UTC} SELECT * FROM myTable;

使用 JDBC 驱动程序时设置优化器选项

您可以通过在 JDBC 连接字符串中指定选项来替换优化器版本和统计信息软件包的默认值,如以下示例所示。

只有最新版本的开源 JDBC 驱动程序才支持这些选项。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

class ConnectionWithQueryOptionsExample {

  static void connectionWithQueryOptions() throws SQLException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    connectionWithQueryOptions(projectId, instanceId, databaseId);
  }

  static void connectionWithQueryOptions(String projectId, String instanceId, String databaseId)
      throws SQLException {
    String optimizerVersion = "1";
    String connectionUrl =
        String.format(
            "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s?optimizerVersion=%s",
            projectId, instanceId, databaseId, optimizerVersion);
    try (Connection connection = DriverManager.getConnection(connectionUrl);
        Statement statement = connection.createStatement()) {
      // Execute a query using the optimizer version '1'.
      try (ResultSet rs =
          statement.executeQuery(
              "SELECT SingerId, FirstName, LastName FROM Singers ORDER BY LastName")) {
        while (rs.next()) {
          System.out.printf("%d %s %s%n", rs.getLong(1), rs.getString(2), rs.getString(3));
        }
      }
      try (ResultSet rs = statement.executeQuery("SHOW VARIABLE OPTIMIZER_VERSION")) {
        while (rs.next()) {
          System.out.printf("Optimizer version: %s%n", rs.getString(1));
        }
      }
    }
  }
}

您还可以使用 SET OPTIMIZER_VERSION 语句设置查询优化器版本,如以下示例所示。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

class SetQueryOptionsExample {

  static void setQueryOptions() throws SQLException {
    // TODO(developer): Replace these variables before running the sample.
    String projectId = "my-project";
    String instanceId = "my-instance";
    String databaseId = "my-database";
    setQueryOptions(projectId, instanceId, databaseId);
  }

  static void setQueryOptions(String projectId, String instanceId, String databaseId)
      throws SQLException {
    String connectionUrl =
        String.format(
            "jdbc:cloudspanner:/projects/%s/instances/%s/databases/%s",
            projectId, instanceId, databaseId);
    try (Connection connection = DriverManager.getConnection(connectionUrl);
        Statement statement = connection.createStatement()) {
      // Instruct the JDBC connection to use version '1' of the query optimizer.
      statement.execute("SET OPTIMIZER_VERSION='1'");
      // Execute a query using the latest optimizer version.
      try (ResultSet rs =
          statement.executeQuery(
              "SELECT SingerId, FirstName, LastName FROM Singers ORDER BY LastName")) {
        while (rs.next()) {
          System.out.printf("%d %s %s%n", rs.getLong(1), rs.getString(2), rs.getString(3));
        }
      }
      try (ResultSet rs = statement.executeQuery("SHOW VARIABLE OPTIMIZER_VERSION")) {
        while (rs.next()) {
          System.out.printf("Optimizer version: %s%n", rs.getString(1));
        }
      }
    }
  }
}

如需详细了解如何使用开源驱动程序,请参阅使用开源 JDBC 驱动程序

无效优化器版本的处理方式

Spanner 支持优化器版本的范围。此范围会随着查询优化器的更新而变化。如果您指定的版本超出范围,查询就会失败。例如,如果您尝试运行包含语句提示 @{OPTIMIZER_VERSION=6} 的查询,但最新的优化器版本号只有 5,Spanner 将返回以下错误消息:

Query optimizer version: 6 is not supported

处理优化器统计信息软件包设置无效

您可以使用本页前面介绍的方法之一将数据库或查询固定到任何可用的统计信息软件包。如果提供的统计信息软件包名称无效,则查询会失败。查询指定的统计信息包必须是:

确定用于运行查询的查询优化器版本

查询使用的优化器版本可通过 Google Cloud Console 和 Google Cloud CLI 查看。

控制台

如需查看查询使用的优化器版本,请在控制台的查询视图中运行查询,然后选择说明标签页。您应该会看到如下所示的消息:

查询优化器版本:5

gcloud CLI

如需查看在 gcloud CLI 中运行查询时使用的版本,请将 --query-mode 标志设置为 PROFILE,如以下代码段所示。

gcloud spanner databases execute-sql MyDatabase --instance=test-instance \
    --query-mode=PROFILE --sql='SELECT * FROM MyTable'

在 Metrics Explorer 中直观呈现查询优化器版本

Cloud Monitoring 会收计量结果,以帮助您了解应用和系统服务的性能。为 Spanner 收集的指标之一是查询数,用于测量实例中的查询数量随时间变化的抽样。此指标可帮助我们查看按错误代码分组的查询,此外,我们还可以使用它来查看运行每个查询的优化器版本。

您可以使用控制台中的 Metrics Explorer 直观呈现数据库实例的查询次数。图 1 显示了三个数据库的查询计数。您可以查看每个数据库中正在使用的优化器版本。

该图表下方的表格显示,my-db-1 尝试运行具有无效优化器版本的查询,返回状态 Bad usage,并导致查询计数为 0。其他数据库分别使用优化器版本 1 和版本 2 运行查询。

Metrics Explorer 中按查询优化器版本分组的查询计数

图 1:Metrics Explorer 中显示的按优化器版本分组的查询的查询计数

要为您的实例设置类似的图表,请执行以下操作:

  1. 导航到控制台中的 Metrics Explorer
  2. 资源类型字段中,选择 Cloud Spanner Instance
  3. 指标字段中,选择 Count of queries
  4. 分组依据字段中,选择 databaseoptimizer_versionstatus

本示例中未显示同一数据库的不同查询使用不同优化器版本的情况。在这种情况下,图表会针对数据库和优化器的每个组合显示条形图。

如需了解如何使用 Cloud Monitoring 监控 Spanner 实例,请参阅使用 Cloud Monitoring 进行监控