鎖定統計資料

Spanner 提供鎖定統計資料,可協助您找出特定時間範圍內,資料庫中造成交易鎖定衝突的主要來源資料列鍵和資料表欄。您可以使用 SQL 陳述式,從 SPANNER_SYS.LOCK_STATS* 系統資料表擷取這些統計資料。

查看鎖定統計資料

Spanner 會在 SPANNER_SYS 結構定義中提供鎖定統計資料。您可以透過下列方式存取 SPANNER_SYS 資料:

  • 資料庫在 Google Cloud 控制台中的 Spanner Studio 頁面

  • gcloud spanner databases execute-sql 指令。

  • 「鎖定深入分析」資訊主頁。

  • executeSqlexecuteStreamingSql 方法。

    Spanner 提供的下列單一讀取方法不支援 SPANNER_SYS

    • 從資料表中的單一資料列或多個資料列執行強式讀取。
    • 從資料表中的單一資料列或多個資料列執行過時讀取。
    • 從次要索引中的單一資料列或多個資料列讀取。

依資料列索引鍵鎖定統計資料

下表會追蹤等待時間最長的資料列鍵:

  • SPANNER_SYS.LOCK_STATS_TOP_MINUTE:在 1 分鐘間隔內,鎖定等待時間最長的資料列鍵。

  • SPANNER_SYS.LOCK_STATS_TOP_10MINUTE:10 分鐘間隔期間內,鎖定等待時間最長的資料列鍵。

  • SPANNER_SYS.LOCK_STATS_TOP_HOUR:在 1 小時間隔內,鎖定等待時間最長的資料列鍵

這些資料表具備下列屬性:

  • 每個資料表都包含資料表名稱所指定的非重疊時間間隔長度的資料。

  • 間隔是基於時鐘時間。1 分鐘間隔的結束時間是目前這一分鐘、10 分鐘間隔的結束時間是目前這個小時的每 10 分鐘,而 1 小時間隔的結束時間是目前這個小時。每隔一段時間,Spanner 就會從所有伺服器收集資料,然後在不久後將資料提供給 SPANNER_SYS 資料表。

    舉例來說,在上午 11:59:30,SQL 查詢的最近可用間隔如下:

    • 1 分鐘:上午 11:58:00–11:58:59
    • 10 分鐘:上午 11:40:00–11:49:59
    • 1 小時:上午 10:00:00–10:59:59
  • Spanner 會依起始資料列鍵範圍將統計資料分組。

  • Spanner 會在指定間隔期間擷取特定起始資料列鍵範圍的統計資料,每個資料列都包含此範圍的總鎖定等待時間統計資料。

  • 如果 Spanner 無法儲存間隔期間鎖定等待的每個資料列鍵範圍資訊,系統會優先處理在指定間隔期間鎖定等待時間最長的資料列鍵範圍。

  • 資料表中的所有資料欄都可為空值。

資料表結構定義

資料欄名稱 類型 說明
INTERVAL_END TIMESTAMP 包含的鎖定衝突發生時的時間間隔結束時間。
ROW_RANGE_START_KEY BYTES(MAX) 發生鎖定衝突的資料列鍵。如果衝突涉及資料列範圍,這個值代表該範圍的起始鍵。加號「+」代表範圍。 詳情請參閱「什麼是資料列範圍起始索引鍵」。
LOCK_WAIT_SECONDS FLOAT64 以秒為單位,記錄列鍵範圍內所有資料欄的鎖定衝突累計鎖定等待時間。
SAMPLE_LOCK_REQUESTS ARRAY<STRUCT<
  column STRING,
  lock_mode STRING,
   transaction_tag STRING>>
這個陣列中的每個項目,都對應到一個導致鎖定衝突的樣本鎖定要求,因為該要求正在等待鎖定,或阻礙其他交易在指定資料列鍵 (範圍) 上取得鎖定。這個陣列中的樣本數上限為 20 個。
每個樣本都包含下列三個欄位:
  • lock_mode:所要求鎖定模式。詳情請參閱「鎖定模式 」。
  • column:發生鎖定衝突的資料欄。這個值的格式為 tablename.columnname
  • transaction_tag:發出要求的交易標記。如要進一步瞭解如何使用代碼,請參閱「排解交易代碼問題」。
系統會以隨機方式平均取樣所有導致鎖定衝突的鎖定要求,因此這個陣列中可能只會記錄衝突的一半 (持有者或等待者)。

鎖定模式

如果 Spanner 作業屬於讀寫交易,就會取得鎖定。唯讀交易不會取得鎖定。Spanner 會使用不同的鎖定模式,盡可能讓更多交易在特定時間存取特定資料格。不同鎖具有不同的特性。舉例來說,有些鎖定可供多筆交易共用,有些則無法。

當您嘗試在交易中取得下列其中一種鎖定模式時,可能會發生鎖定衝突。

  • ReaderShared 鎖定 - 鎖定可讓其他讀取作業繼續存取資料,直到交易準備好修訂為止。讀寫交易讀取資料時,會取得這個共用鎖定。

  • WriterShared 鎖定 - 讀寫交易嘗試提交寫入作業時,會取得這項鎖定。

  • Exclusive 鎖定 - 讀寫交易已取得 ReaderShared 鎖定,並在讀取完成後嘗試寫入資料時,會取得專屬鎖定。專屬鎖是 ReaderShared 鎖的升級版,交易同時持有 ReaderShared 鎖定和 WriterShared 鎖定時,即為獨占鎖定,這是交易的特殊案例。其他交易無法在同一儲存格上取得任何鎖定。

  • WriterSharedTimestamp 鎖定:特殊類型的 WriterShared 鎖定,當您將新資料列插入以修訂時間戳記做為主鍵的資料表時,系統會取得這類鎖定。這類鎖定可防止交易參與者建立完全相同的資料列,因此不會相互衝突。Spanner 會更新插入資料列的鍵,以符合執行插入作業的交易修訂時間戳記。

如要進一步瞭解交易類型和可用的鎖定類型,請參閱「交易」。

鎖定模式衝突

下表列出不同鎖定模式之間可能發生的衝突。

鎖定模式 ReaderShared WriterShared Exclusive WriterSharedTimestamp
ReaderShared
WriterShared 不適用
Exclusive 不適用
WriterSharedTimestamp 不適用 不適用

只有在插入新資料列時,且時間戳記是主鍵的一部分,才會使用 WriterSharedTimestamp 鎖定。寫入現有儲存格或插入沒有時間戳記的新列時,會使用 WriterSharedExclusive 鎖定。因此,WriterSharedTimestamp 不會與其他類型的鎖定發生衝突,而這些情況在上表中會顯示為「不適用」

唯一的例外是 ReaderShared,這個屬性可套用至不存在的資料列,因此可能會與 WriterSharedTimestamp 衝突。舉例來說,完整資料表掃描會鎖定整個資料表,即使是尚未建立的資料列也一樣,因此 ReaderShared 可能會與 WriterSharedTimestamp 發生衝突。

什麼是資料列範圍起始索引鍵?

ROW_RANGE_START_KEY 欄會識別有鎖定衝突的複合式主鍵,或資料列範圍的起始主鍵。以下結構定義用於說明範例。

CREATE TABLE Singers (
  SingerId   INT64 NOT NULL,
  FirstName  STRING(1024),
  LastName   STRING(1024),
  SingerInfo BYTES(MAX),
) PRIMARY KEY (SingerId);

CREATE TABLE Albums (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  AlbumTitle   STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

CREATE TABLE Songs (
  SingerId     INT64 NOT NULL,
  AlbumId      INT64 NOT NULL,
  TrackId      INT64 NOT NULL,
  SongName     STRING(MAX),
) PRIMARY KEY (SingerId, AlbumId, TrackId),
  INTERLEAVE IN PARENT Albums ON DELETE CASCADE;

CREATE TABLE Users (
  UserId     INT64 NOT NULL,
  LastAccess TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true),
  ...
) PRIMARY KEY (UserId, LastAccess);

如下表所示,資料列鍵和資料列鍵範圍會以鍵中的加號「+」表示範圍。在這些情況下,該鍵代表發生鎖定衝突的鍵範圍起始鍵。

ROW_RANGE_START_KEY 說明
歌手(2) 主鍵 SingerId=2 的 Singers 資料表
albums(2,1) Albums 資料表,主鍵為 SingerId=2、AlbumId=1
歌曲(2,1,5) 歌曲資料表,主鍵為 SingerId=2、AlbumId=1、TrackId=5
歌曲(2、1、5+) 歌曲資料表鍵範圍,從 SingerId=2、AlbumId=1、TrackId=5 開始
專輯(2 張以上) Albums 資料表鍵範圍,從 SingerId=2、AlbumId=1 開始
users(3, 2020-11-01 12:34:56.426426+00:00) 使用者資料表,其中鍵為 UserId=3,LastAccess 為 commit_timestamp

匯總統計資料

SPANNER_SYS 也包含資料表,可儲存 Spanner 在特定時間範圍內擷取的鎖定統計資料匯總資料:

  • SPANNER_SYS.LOCK_STATS_TOTAL_MINUTE:彙整 1 分鐘間隔內所有鎖定等待的統計資料。

  • SPANNER_SYS.LOCK_STATS_TOTAL_10MINUTE:彙整 10 分鐘間隔內所有鎖定等待的統計資料。

  • SPANNER_SYS.LOCK_STATS_TOTAL_HOUR:每 1 小時間隔內所有鎖定等待的匯總統計資料。

匯總統計資料表具有下列屬性:

  • 每個資料表都包含資料表名稱所指定的非重疊時間間隔長度的資料。

  • 間隔是基於時鐘時間。1 分鐘間隔的結束時間是目前這一分鐘、10 分鐘間隔的結束時間是目前這個小時的每 10 分鐘,而 1 小時間隔的結束時間是目前這個小時。

    舉例來說,在上午 11:59:30,SQL 查詢的最近可用間隔如下:

    • 1 分鐘:上午 11:58:00–11:58:59
    • 10 分鐘:上午 11:40:00–11:49:59
    • 1 小時:上午 10:00:00–10:59:59
  • 每個資料列都會包含指定間隔期間,資料庫上所有鎖定等待的統計資料的匯總。每個時間間隔只有一個資料列。

  • SPANNER_SYS.LOCK_STATS_TOTAL_* 資料表中擷取的統計資料包含 Spanner 未在 SPANNER_SYS.LOCK_STATS_TOP_* 資料表中擷取的鎖定等待。

  • 這些表格中的部分資料欄會以 Cloud Monitoring 指標的形式顯示。 公開的指標如下:

    • 鎖定等待時間

    詳情請參閱「Spanner 指標」。

資料表結構定義

資料欄名稱 類型 說明
INTERVAL_END TIMESTAMP 發生鎖定衝突的時間間隔結束時間。
TOTAL_LOCK_WAIT_SECONDS FLOAT64 整個資料庫記錄的鎖定衝突總等待時間 (以秒為單位)。

查詢範例

以下是可用於擷取鎖定統計資料的 SQL 陳述式範例。您可以使用用戶端程式庫gcloud spannerGoogle Cloud 主控台執行這些 SQL 陳述式。

列出前 1 分鐘間隔的鎖定統計資料

下列查詢會傳回每個發生鎖定衝突的資料列鍵的鎖定等待資訊,包括最近 1 分鐘時間間隔內鎖定衝突的總比例。

CAST() 函式會將 row_range_start_key BYTES 欄位轉換為 STRING。

SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
       t.total_lock_wait_seconds,
       s.lock_wait_seconds,
       s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
       s.sample_lock_requests
FROM spanner_sys.lock_stats_total_minute t, spanner_sys.lock_stats_top_minute s
WHERE t.interval_end =
  (SELECT MAX(interval_end)
   FROM spanner_sys.lock_stats_total_minute)
AND s.interval_end = t.interval_end
ORDER BY s.lock_wait_seconds DESC;
查詢輸出
row_range_start_key total_lock_wait_seconds lock_wait_seconds frac_of_total sample_lock_requests
歌曲(2,1,1) 2.37 1.76 0.7426 LOCK_MODE:ReaderShared

COLUMN:Singers.SingerInfo

LOCK_MODE:WriterShared

COLUMN:Singers.SingerInfo
Users(3, 2020-11-01 12:34:56.426426+00:00) 2.37 0.61 0.2573 LOCK_MODE:ReaderShared

COLUMN: users._exists1

LOCK_MODE:WriterShared

COLUMN: users._exists1

1 _exists 是用來檢查特定資料列是否存在的內部欄位。

資料保留

Spanner 至少會在下列時間範圍保留每個資料表的資料:

  • SPANNER_SYS.LOCK_STATS_TOP_MINUTESPANNER_SYS.LOCK_STATS_TOTAL_MINUTE:涵蓋前 6 個小時的間隔。

  • SPANNER_SYS.LOCK_STATS_TOP_10MINUTESPANNER_SYS.LOCK_STATS_TOTAL_10MINUTE:涵蓋前 4 天的間隔。

  • SPANNER_SYS.LOCK_STATS_TOP_HOURSPANNER_SYS.LOCK_STATS_TOTAL_HOUR: 涵蓋前 30 天的間隔。

使用鎖定統計資料排解資料庫中的鎖定衝突

您可以使用 SQL 或「鎖定洞察」資訊主頁,查看資料庫中的鎖定衝突。

下列主題說明如何使用 SQL 程式碼調查這類鎖定衝突。

選取要調查的時間範圍

您檢查 Spanner 資料庫的延遲指標,發現應用程式在某段時間內延遲偏高,CPU 使用率也偏高。舉例來說,問題是在 2020 年 11 月 12 日晚上 10:50 左右開始發生。

判斷所選期間的交易提交延遲時間是否隨著鎖定等待時間增加

交易會取得鎖定,因此如果鎖定衝突導致等待時間過長,我們應該會看到交易提交延遲時間增加,以及鎖定等待時間增加。

選取開始調查的時間範圍後,我們會將該時間的交易統計資料 TXN_STATS_TOTAL_10MINUTE 與鎖定統計資料 LOCK_STATS_TOTAL_10MINUTE 結合,瞭解平均提交延遲時間的增加是否與鎖定等待時間的增加有關。

SELECT t.interval_end, t.avg_commit_latency_seconds, l.total_lock_wait_seconds
FROM spanner_sys.txn_stats_total_10minute t
LEFT JOIN spanner_sys.lock_stats_total_10minute l
ON t.interval_end = l.interval_end
WHERE
  t.interval_end >= "2020-11-12T21:50:00Z"
  AND t.interval_end <= "2020-11-12T23:50:00Z"
ORDER BY interval_end;

以下列資料為例,說明我們從查詢傳回的結果。

interval_end avg_commit_latency_seconds total_lock_wait_seconds
2020-11-12 21:40:00-07:00 0.002 0.090
2020-11-12 21:50:00-07:00 0.003 0.110
2020-11-12 22:00:00-07:00 0.002 0.100
2020-11-12 22:10:00-07:00 0.002 0.080
2020-11-12 22:20:00-07:00 0.030 0.240
2020-11-12 22:30:00-07:00 0.034 0.220
2020-11-12 22:40:00-07:00 0.034 0.218
2020-11-12 22:50:00-07:00 3.741 780.193
2020-11-12 23:00:00-07:00 0.042 0.240
2020-11-12 23:10:00-07:00 0.038 0.129
2020-11-12 23:20:00-07:00 0.021 0.128
2020-11-12 23:30:00-07:00 0.038 0.231

上述結果顯示,在 2020 年 11 月 12 日 22:40:002020 年 11 月 12 日 22:50:00 期間,avg_commit_latency_secondstotal_lock_wait_seconds 大幅增加,之後則下降。請注意,avg_commit_latency_seconds 僅為「提交」步驟的平均花費時間。另一方面,total_lock_wait_seconds 是該期間的匯總鎖定時間,因此時間看起來比交易提交時間長得多。

我們已確認鎖定等待時間與寫入延遲時間增加密切相關,接下來將調查哪些資料列和資料欄導致等待時間過長。

瞭解所選期間內,哪些資料列鍵和資料欄的鎖定等待時間較長

如要找出在調查期間發生高鎖定等待時間的資料列鍵和資料欄,請查詢 LOCK_STAT_TOP_10MINUTE 資料表,其中列出最常造成鎖定等待的資料列鍵和資料欄。

下列查詢中的 CAST() 函式會將 row_range_start_key BYTES 欄位轉換為 STRING。

SELECT CAST(s.row_range_start_key AS STRING) AS row_range_start_key,
       t.total_lock_wait_seconds,
       s.lock_wait_seconds,
       s.lock_wait_seconds/t.total_lock_wait_seconds frac_of_total,
       s.sample_lock_requests
FROM spanner_sys.lock_stats_total_10minute t, spanner_sys.lock_stats_top_10minute s
WHERE
  t.interval_end = "2020-11-12T22:50:00Z" and s.interval_end = t.interval_end;
row_range_start_key total_lock_wait_seconds lock_wait_seconds frac_of_total sample_lock_requests
歌手(32) 780.193 780.193 1 LOCK_MODE:WriterShared

COLUMN:Singers.SingerInfo

LOCK_MODE:ReaderShared

COLUMN:Singers.SingerInfo

從這個結果表格中,我們可以發現 Singers 資料表在鍵 SingerId=32 發生衝突。Singers.SingerInfo 是指 ReaderSharedWriterShared 之間發生鎖定衝突的資料欄。

當一項交易嘗試讀取特定儲存格,而另一項交易嘗試寫入同一儲存格時,就會發生這類常見的衝突。我們現在知道交易爭奪鎖定的確切資料格,因此在下一個步驟中,我們會找出爭奪鎖定的交易。

找出哪些交易正在存取鎖定衝突涉及的資料欄

如要找出特定時間間隔內,因鎖定衝突而導致提交延遲時間顯著增加的交易,您需要從 SPANNER_SYS.TXN_STATS_TOTAL_10MINUTE 資料表中查詢下列資料欄:

  • fprint
  • read_columns
  • write_constructive_columns
  • avg_commit_latency_seconds

您需要篩選出從 SPANNER_SYS.LOCK_STATS_TOP_10MINUTE 表格中識別出的鎖定資料欄:

  • 交易嘗試取得 ReaderShared 鎖定時,如果讀取任何導致鎖定衝突的資料欄,就會遭到拒絕。

  • 交易嘗試取得 WriterShared 鎖定時,如果寫入任何導致鎖定衝突的資料欄,就會遭到拒絕。

SELECT
  fprint,
  read_columns,
  write_constructive_columns,
  avg_commit_latency_seconds
FROM spanner_sys.txn_stats_top_10minute t2
WHERE (
  EXISTS (
    SELECT * FROM t2.read_columns columns WHERE columns IN (
      SELECT DISTINCT(req.COLUMN)
      FROM spanner_sys.lock_stats_top_10minute t, t.SAMPLE_LOCK_REQUESTS req
      WHERE req.LOCK_MODE = "ReaderShared" AND t.interval_end ="2020-11-12T23:50:00Z"))
OR
  EXISTS (
    SELECT * FROM t2.write_constructive_columns columns WHERE columns IN (
      SELECT DISTINCT(req.COLUMN)
      FROM spanner_sys.lock_stats_top_10minute t, t.SAMPLE_LOCK_REQUESTS req
      WHERE req.LOCK_MODE = "WriterShared" AND t.interval_end ="2020-11-12T23:50:00Z"))
)
AND t2.interval_end ="2020-11-12T23:50:00Z"
ORDER BY avg_commit_latency_seconds DESC;

查詢結果會依 avg_commit_latency_seconds 欄排序,因此您會先看到提交延遲時間最長的交易。

fprint read_columns write_constructive_columns avg_commit_latency_seconds
1866043996151916800


['Singers.SingerInfo',
'Singers.FirstName',
'Singers.LastName',
'Singers._exists']
['Singers.SingerInfo'] 4.89
4168578515815911936 [] ['Singers.SingerInfo'] 3.65

查詢結果顯示,有兩筆交易嘗試存取 Singers.SingerInfo 欄,而該欄在該時間範圍內發生鎖定衝突。找出導致鎖定衝突的交易後,您可以使用交易的指紋 fprint 分析交易,找出可能導致鎖定衝突的問題。

查看 fprint=1866043996151916800 的交易後,您可以使用 read_columnswrite_constructive_columns 欄,找出觸發交易的應用程式程式碼部分。然後,您就可以查看未依主鍵 SingerId 篩選的基礎 DML。這導致系統全面掃描資料表,並在交易提交前鎖定資料表。

如要解決鎖定衝突,請採取下列做法:

  1. 使用唯讀交易找出必要的 SingerId 值。
  2. 使用個別的讀取/寫入交易,更新必要 SingerId 值的資料列。

套用最佳做法,減少鎖定爭用

在我們的範例情境中,我們能夠使用鎖定統計資料和交易統計資料,將問題範圍縮小至更新時未採用資料表主索引鍵的交易。我們根據是否事先知道要更新的資料列鍵,提出改善交易的構想。

查看解決方案中的潛在問題,甚至是設計解決方案時,請考慮採用下列最佳做法,減少資料庫中的鎖定衝突次數。

  • 避免在讀寫交易內執行大量讀取作業

  • 盡可能使用唯讀交易,因為這類交易不會取得任何鎖定。

  • 避免在讀寫交易中掃描整個資料表。包括根據主鍵寫入 DML 條件,或在使用 Read API 時指派特定鍵範圍。

  • 在讀寫交易中讀取資料後,請盡快提交變更,縮短鎖定期間。讀寫交易可確保您讀取資料後,資料不會變更,直到您成功修訂變更為止。為此,交易必須在讀取和提交期間鎖定資料格。因此,如果能縮短鎖定期間,交易就比較不會發生鎖定衝突。

  • 請盡量使用小型交易,而非大型交易,或考慮使用分區 DML 處理長時間執行的 DML 交易。長時間執行的交易會長時間取得鎖定,因此請考慮將觸及數千列的交易分成多個較小的交易,盡可能更新數百列。

  • 如果您不需要讀寫交易提供的保證,請避免在讀寫交易中讀取任何資料,然後再提交變更,例如在個別的唯讀交易中讀取資料。大多數鎖定衝突都是因為強烈保證所致,目的是確保資料在讀取和提交之間保持不變。因此,如果讀寫交易未讀取任何資料,就不需要長時間鎖定儲存格。

  • 在讀寫交易中,只指定所需的最低欄數。由於 Spanner 鎖定是以資料格為單位,當讀寫交易讀取過多資料欄時,系統會對這些資料格取得 ReaderShared 鎖定。如果其他交易在寫入過多資料欄時取得 WriterShared 鎖定,可能會導致鎖定衝突。舉例來說,請考慮在讀取時指定一組資料欄,而不是 *

  • 盡量減少讀寫交易中的 API 呼叫次數。API 呼叫的延遲時間可能會導致 Spanner 中的鎖定爭用,因為 API 呼叫會受到網路延遲和服務端延遲的影響。建議您盡可能在讀寫交易以外進行 API 呼叫。如果必須在讀寫交易中執行 API 呼叫,請務必監控 API 呼叫的延遲時間,盡量縮短鎖定取得期間。

  • 遵循結構定義設計最佳做法

後續步驟