最早的活跃查询(也称为运行时间最长的查询)是数据库中活跃的查询列表,按查询运行的时长排序。深入了解这些查询有助于确定出现系统延迟和 CPU 使用率较高的原因。
Spanner 提供了一个内置表 SPANNER_SYS.OLDEST_ACTIVE_QUERIES,该表按照开始时间以升序列出正在运行的查询,其中包括包含 DML 语句的查询。但不包括变更数据流查询。
如果正在运行大量查询,则由于系统对此类数据的收集实施内存限制,结果可能会仅限于总查询的一部分。因此,Spanner 提供了一个附加表 SPANNER_SYS.ACTIVE_QUERIES_SUMMARY,其中显示所有活跃查询(变更数据流查询除外)的摘要统计信息。您可以使用 SQL 语句从这两个内置表中检索信息。
在本文中,我们将介绍两个表,并介绍一些使用这些表的示例查询,最后演示如何使用它们来帮助缓解由活跃查询引发的问题。
访问最早的活跃查询统计信息
SPANNER_SYS 数据只能通过 SQL 接口获得;例如:
Google Cloud 控制台中的数据库 Spanner Studio 页面
Spanner 不支持将 SPANNER_SYS 与以下单次读取方法搭配使用:
- 对表中的单行或多行执行强读。
- 从表中的单行或多行执行过时数据读取。
- 从二级索引中的单行或多行读取。
OLDEST_ACTIVE_QUERIES统计信息
SPANNER_SYS.OLDEST_ACTIVE_QUERIES 返回按开始时间排序的活跃查询列表。如果正在运行大量查询,则由于 Spanner 对此类数据的收集实施内存限制,结果可能会仅限于总查询的一部分。如需查看所有活跃查询的摘要统计信息,请参阅 ACTIVE_QUERIES_SUMMARY。
所有最早的活跃查询统计信息表的架构
| 列名 | 类型 | 说明 |
|---|---|---|
START_TIME |
TIMESTAMP |
查询的开始时间。 |
TEXT_FINGERPRINT |
INT64 |
指纹是请求标记的哈希值;如果不存在标记,则为查询文本的哈希值。 |
TEXT |
STRING |
查询语句文本。 |
TEXT_TRUNCATED |
BOOL |
如果 TEXT 字段中的查询文本被截断,则此值为 TRUE。如果查询文本未被截断,则此值为 FALSE。
|
SESSION_ID |
STRING |
执行查询的会话的 ID。 |
QUERY_ID |
STRING |
查询的 ID。 您可以将此 ID 与
CALL cancel_query(query_id) 搭配使用来取消查询。 |
CLIENT_IP_ADDRESS |
STRING |
请求查询的客户端的 IP 地址。有时,客户端 IP 地址可能会被隐去。此处显示的 IP 地址与审核日志一致,并遵循相同的隐去指南。 如需了解详情,请参阅 审核日志中的调用方 IP 地址。我们建议仅在需要客户端 IP 地址时才请求,因为请求客户端 IP 地址可能会产生额外的延迟时间。 |
API_CLIENT_HEADER |
STRING |
客户端的 api_client 标头。
|
USER_AGENT_HEADER |
STRING |
Spanner 从客户端收到的 user_agent 标头。
|
SERVER_REGION |
STRING |
Spanner 根服务器处理查询的区域。如需了解详情,请参阅查询的生命周期。 |
PRIORITY |
STRING |
查询的优先级。如需查看可用的优先级,请参阅 RequestOptions。 |
TRANSACTION_TYPE |
STRING |
查询的事务类型。可能的值包括 READ_ONLY、READ_WRITE 和 NONE。 |
示例查询
您可以使用客户端库、Google Cloud CLI 或Google Cloud 控制台运行以下示例 SQL 语句。
列出最早运行的活跃查询
以下查询将返回最早运行的查询列表,按查询的开始时间排序。
SELECT start_time,
text_fingerprint,
text,
text_truncated,
session_id,
query_id,
api_client_header,
server_region,
priority,
transaction_type
FROM spanner_sys.oldest_active_queries
ORDER BY start_time ASC;
查询输出
下表显示了运行上述查询的输出:
| start_time | text_fingerprint | text | text_truncated | session_id | query_id | api_client_header | server_region | 优先级 | transaction_type |
|---|---|---|---|---|---|---|---|---|---|
| 2025-05-20T03:29:54.287255Z | -3426560921851907385 | SELECT a.SingerId, a.AlbumId, a.TrackId, b.SingerId as b_id, b.AlbumId as b_albumid, b.TrackId as b_trackId FROM Songs as a CROSS JOIN Songs as b; | FALSE | AG46FS6K3adF | 9023439241169932454 | gl-go/1.25.0-20250216-RC00 gccl/1.73.0 gapic/1.73.0 gax/2.14.1 grpc/1.69.2 | us-central1 | PRIORITY_HIGH | READ_ONLY |
| 2025-05-20T03:31:52.40808Z | 1688332608621812214 | SELECT a.SingerId, a.AlbumId, a.TrackId, a.SongName, s.FirstName, s.LastName FROM Songs as a JOIN Singers as s ON s.SingerId = a.SingerId WHERE STARTS_WITH(s.FirstName, 'FirstName') LIMIT 1000000; | FALSE | AG46FS6paJPKDOb | 2729381896189388167 | gl-go/1.25.0-20250216-RC00 gccl/1.73.0 gapic/1.73.0 gax/2.14.1 grpc/1.69.2 | us-central1 | PRIORITY_HIGH | READ_WRITE |
| 2025-05-20T03:31:52.591212Z | 6561582859583559006 | SELECT a.SingerId, a.AlbumId, a.TrackId, a.SongName, s.FirstName, s.LastName FROM Songs as a JOIN Singers as s ON s.SingerId = a.SingerId WHERE a.SingerId > 10 LIMIT 1000000; | FALSE | AG46FS7Pb_9H6J6p | 9125776389780080794 | gl-go/1.25.0-20250216-RC00 gccl/1.73.0 gapic/1.73.0 gax/2.14.1 grpc/1.69.2 | us-central1 | PRIORITY_LOW | READ_ONLY |
列出最热门的 2 个最早运行的查询
以上查询略有不同,本示例会返回前 2 个最早的运行中查询(按查询开始时间排序)。
SELECT start_time,
text_fingerprint,
text,
text_truncated,
session_id
FROM spanner_sys.oldest_active_queries
ORDER BY start_time ASC LIMIT 2;
查询输出
下表显示了运行上述查询的输出:
| start_time | text_fingerprint | text | text_truncated | session_id |
|---|---|---|---|---|
| 2039-07-18T07:52:28.225877Z | -3426560921851907385 | SELECT a.SingerId, a.AlbumId, a.TrackId, b.SingerId as b_id, b.AlbumId as b_albumid, b.TrackId as b_trackId FROM Songs as a CROSS JOIN Songs as b; | False | ACjbPvYsuRt |
| 2039-07-18T07:54:08.622081Z | -9206690983832919848 | SELECT a.SingerId, a.AlbumId, a.TrackId, a.SongName, s.FirstName, s.LastName FROM Songs as a JOIN Singers as s ON s.SingerId = a.SingerId WHERE STARTS_WITH(s.FirstName, 'FirstName') LIMIT 1000000; | False | ACjbPvaF3yK |
ACTIVE_QUERIES_SUMMARY
SPANNER_SYS.ACTIVE_QUERIES_SUMMARY 统计信息表显示所有活跃查询的摘要统计信息。查询分为以下几类:
- 超过 1 秒
- 超过 10 秒
- 超过 100 秒
ACTIVE_QUERIES_SUMMARY 的表格架构
| 列名 | 类型 | 说明 |
|---|---|---|
ACTIVE_COUNT |
INT64 |
正在运行的查询总数。 |
OLDEST_START_TIME |
TIMESTAMP |
最早运行的查询的开始时间上限。 |
COUNT_OLDER_THAN_1S |
INT64 |
超过 1 秒的查询数。 |
COUNT_OLDER_THAN_10S |
INT64 |
超过 10 秒的查询数。 |
COUNT_OLDER_THAN_100S |
INT64 |
超过 100 秒的查询数。 |
一个查询可以计入多个存储分区中。例如,如果查询已运行 12 秒,则它将被计入 COUNT_OLDER_THAN_1S 和 COUNT_OLDER_THAN_10S 中,因为它同时满足两个条件。
示例查询
您可以使用客户端库、gcloud spanner 或Google Cloud 控制台运行以下示例 SQL 语句。
检索活跃查询的摘要
以下查询将返回有关运行中查询的摘要统计信息。
SELECT active_count,
oldest_start_time,
count_older_than_1s,
count_older_than_10s,
count_older_than_100s
FROM spanner_sys.active_queries_summary;
查询输出
| active_count | oldest_start_time | count_older_than_1s | count_older_than_10s | count_older_than_100s |
|---|---|---|---|---|
| 22 | 2039-07-18T07:52:28.225877Z | 21 | 21 | 1 |
限制
虽然目标是为您提供最全面的数据分析,但在某些情况下,查询不会包含在这些表中返回的数据中。
如果 DML 查询(
UPDATE、INSERT、DELETE)处于应用 Mutation 阶段,则不会包含这些查询。如果查询由于暂时性错误而正在重启,则不会包含该查询。
不会包含来自过载服务器或无响应服务器的查询。
无法在读写事务中从
OLDEST_ACTIVE_QUERIES表读取或查询数据。即使在只读事务中,它也会忽略事务时间戳,并在执行过程中始终返回当前数据。在极少数情况下,它可能会返回ABORTED错误和部分结果;在这种情况下,请舍弃部分结果并再次尝试查询。如果
CLIENT_IP_ADDRESS列返回<error>字符串,则表示存在暂时性问题,不应影响查询的其余部分。重试查询以检索客户端 IP 地址。
使用活跃查询数据排查高 CPU 利用率的问题
在排查 Spanner 数据库中的延迟问题时,查询统计信息和事务统计信息会提供一些有用的信息。这些工具会提供有关已完成查询的信息。但是,有时候需要知道系统中正在运行的内容。例如,在 CPU 利用率非常高的情况下,您需要回答以下问题。
- 目前正在运行多少个查询?
- 这些查询是什么?
- 有多少个查询在长时间运行(即时长超过 100 秒)?
- 哪个会话正在运行查询?
通过回答上述问题,您可以决定执行以下操作。
- 删除执行查询的会话可立即解决问题。
- 通过添加索引来提高查询性能。
- 降低与周期性后台任务关联的查询的频率。
- 找出发出查询但可能无权执行查询的用户或组件。
在以下演示中,我们将研究活跃的查询,并确定要采取的行动(如果有的话)。
检索活跃查询的摘要
在示例场景中,我们发现高于正常 CPU 使用率的情况,因此决定运行以下查询返回活跃查询摘要。
SELECT active_count,
oldest_start_time,
count_older_than_1s,
count_older_than_10s,
count_older_than_100s
FROM spanner_sys.active_queries_summary;
该查询会产生以下结果。
| active_count | oldest_start_time | count_older_than_1s | count_older_than_10s | count_older_than_100s |
|---|---|---|---|---|
22 |
2039-07-18T07:52:28.225877Z |
21 |
21 |
1 |
这表明我们有一个查询的运行时间超过 100 秒。这对于我们的数据库而言并不常见,因此我们需要进一步调查。
检索活跃查询列表
我们在上一步已确定有一个查询的运行时间超过了 100 秒。如需进一步调查,请运行以下查询以返回有关前 5 个最早运行的查询的更多信息。
SELECT start_time,
text_fingerprint,
text,
text_truncated,
session_id,
query_id
FROM spanner_sys.oldest_active_queries
ORDER BY start_time ASC LIMIT 5;
在此示例中,我们在 2024 年 3 月 28 日下午 16:44:09(北美东部夏令时间/EDT)左右运行了查询,它返回了以下结果。(您可能需要水平滚动才能看到整个输出。)
| start_time | text_fingerprint | text | text_truncated | session_id | query_id |
|---|---|---|---|---|---|
| 2024-03-28 16:44:09.356939+00:00 | -2833175298673875968 | select * from spanner_sys.oldest_active_queries | false | ACjbPvYsucrtcffHrRK6aObeIjZf12tSUwOsim-g1WC3IhqF4epzICCQR3GCHw | 37190103859320827 |
| 2039-07-18T07:52:28.225877Z | -3426560921851907385 | SELECT a.SingerId, a.AlbumId, a.TrackId, b.SingerId as b_id, b.AlbumId as b_albumid, b.TrackId as b_trackId FROM Songs as a CROSS JOIN Songs as b; | false | ACjbPvaF3yKiNfxXFod2LPoFaXjKR759Bw1o34206vv0t7eOrD3wxZhu8U6ohQ | 48946620525959556 |
最早的查询 (fingerprint = -2833175298673875968) 在表格中突出显示。它是费用高昂的 CROSS JOIN。 我们决定采取行动。
取消成本高昂的查询
在此示例中,我们发现了一个运行成本高昂的 CROSS JOIN 查询,因此决定取消该查询。我们在上一步中收到的查询结果包含一个 query_id。我们可以运行以下 CALL cancel_query(query_id) 命令(适用于 GoogleSQL)和 spanner.cancel_query(query_id) 命令(适用于 PostgreSQL)来取消查询。
GoogleSQL
CALL cancel_query(query_id)
PostgreSQL
CALL spanner.cancel_query(query_id)
例如,在以下示例中,CALL 语句会取消 ID 为 37190103859320827 的查询:
CALL cancel_query('37190103859320827')
您需要查询 spanner_sys.oldest_active_queries 表,以验证查询是否已取消。
下面将演示如何使用 SPANNER_SYS.OLDEST_ACTIVE_QUERIES 和 SPANNER_SYS.ACTIVE_QUERIES_SUMMARY 来分析正在运行的查询,并在必要时对任何导致高 CPU 使用率的查询采取行动。当然,避免执行费用高昂的操作并为您的使用场景设计合适的架构始终更便宜。如需详细了解如何构建高效运行的 SQL 语句,请参阅 SQL 最佳做法。