Spanner 提供鎖定統計資料,可協助您找出特定時間範圍內,資料庫中造成交易鎖定衝突的主要來源資料列鍵和資料表欄。您可以使用 SQL 陳述式,從 SPANNER_SYS.LOCK_STATS*
系統資料表擷取這些統計資料。
查看鎖定統計資料
Spanner 會在 SPANNER_SYS
結構定義中提供鎖定統計資料。您可以透過下列方式存取 SPANNER_SYS
資料:
資料庫在 Google Cloud 控制台中的 Spanner Studio 頁面
executeSql
或executeStreamingSql
方法。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<
|
這個陣列中的每個項目,都對應到一個導致鎖定衝突的樣本鎖定要求,因為該要求正在等待鎖定,或阻礙其他交易在指定資料列鍵 (範圍) 上取得鎖定。這個陣列中的樣本數上限為 20 個。 每個樣本都包含下列三個欄位: 系統會以隨機方式平均取樣所有導致鎖定衝突的鎖定要求,因此這個陣列中可能只會記錄衝突的一半 (持有者或等待者)。 |
鎖定模式
如果 Spanner 作業屬於讀寫交易,就會取得鎖定。唯讀交易不會取得鎖定。Spanner 會使用不同的鎖定模式,盡可能讓更多交易在特定時間存取特定資料格。不同鎖具有不同的特性。舉例來說,有些鎖定可供多筆交易共用,有些則無法。
當您嘗試在交易中取得下列其中一種鎖定模式時,可能會發生鎖定衝突。
ReaderShared
鎖定 - 鎖定可讓其他讀取作業繼續存取資料,直到交易準備好修訂為止。讀寫交易讀取資料時,會取得這個共用鎖定。WriterShared
鎖定 - 讀寫交易嘗試提交寫入作業時,會取得這項鎖定。Exclusive
鎖定 - 讀寫交易已取得 ReaderShared 鎖定,並在讀取完成後嘗試寫入資料時,會取得專屬鎖定。專屬鎖是ReaderShared
鎖的升級版,交易同時持有ReaderShared
鎖定和WriterShared
鎖定時,即為獨占鎖定,這是交易的特殊案例。其他交易無法在同一儲存格上取得任何鎖定。WriterSharedTimestamp
鎖定:特殊類型的WriterShared
鎖定,當您將新資料列插入以修訂時間戳記做為主鍵的資料表時,系統會取得這類鎖定。這類鎖定可防止交易參與者建立完全相同的資料列,因此不會相互衝突。Spanner 會更新插入資料列的鍵,以符合執行插入作業的交易修訂時間戳記。
如要進一步瞭解交易類型和可用的鎖定類型,請參閱「交易」。
鎖定模式衝突
下表列出不同鎖定模式之間可能發生的衝突。
鎖定模式 | ReaderShared |
WriterShared |
Exclusive |
WriterSharedTimestamp |
---|---|---|---|---|
ReaderShared |
否 | 是 | 是 | 是 |
WriterShared |
是 | 否 | 是 | 不適用 |
Exclusive |
是 | 是 | 是 | 不適用 |
WriterSharedTimestamp |
是 | 不適用 | 不適用 | 是 |
只有在插入新資料列時,且時間戳記是主鍵的一部分,才會使用 WriterSharedTimestamp
鎖定。寫入現有儲存格或插入沒有時間戳記的新列時,會使用 WriterShared
和 Exclusive
鎖定。因此,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 spanner 或 Google 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_MINUTE
和SPANNER_SYS.LOCK_STATS_TOTAL_MINUTE
:涵蓋前 6 個小時的間隔。SPANNER_SYS.LOCK_STATS_TOP_10MINUTE
和SPANNER_SYS.LOCK_STATS_TOTAL_10MINUTE
:涵蓋前 4 天的間隔。SPANNER_SYS.LOCK_STATS_TOP_HOUR
和SPANNER_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:00 至 2020 年 11 月 12 日 22:50:00 期間,avg_commit_latency_seconds
和 total_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
是指 ReaderShared
和 WriterShared
之間發生鎖定衝突的資料欄。
當一項交易嘗試讀取特定儲存格,而另一項交易嘗試寫入同一儲存格時,就會發生這類常見的衝突。我們現在知道交易爭奪鎖定的確切資料格,因此在下一個步驟中,我們會找出爭奪鎖定的交易。
找出哪些交易正在存取鎖定衝突涉及的資料欄
如要找出特定時間間隔內,因鎖定衝突而導致提交延遲時間顯著增加的交易,您需要從 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_columns
和 write_constructive_columns
欄,找出觸發交易的應用程式程式碼部分。然後,您就可以查看未依主鍵 SingerId
篩選的基礎 DML。這導致系統全面掃描資料表,並在交易提交前鎖定資料表。
如要解決鎖定衝突,請採取下列做法:
- 使用唯讀交易找出必要的
SingerId
值。 - 使用個別的讀取/寫入交易,更新必要
SingerId
值的資料列。
套用最佳做法,減少鎖定爭用
在我們的範例情境中,我們能夠使用鎖定統計資料和交易統計資料,將問題範圍縮小至更新時未採用資料表主索引鍵的交易。我們根據是否事先知道要更新的資料列鍵,提出改善交易的構想。
查看解決方案中的潛在問題,甚至是設計解決方案時,請考慮採用下列最佳做法,減少資料庫中的鎖定衝突次數。
盡可能使用唯讀交易,因為這類交易不會取得任何鎖定。
避免在讀寫交易中掃描整個資料表。包括根據主鍵寫入 DML 條件,或在使用 Read API 時指派特定鍵範圍。
在讀寫交易中讀取資料後,請盡快提交變更,縮短鎖定期間。讀寫交易可確保您讀取資料後,資料不會變更,直到您成功修訂變更為止。為此,交易必須在讀取和提交期間鎖定資料格。因此,如果能縮短鎖定期間,交易就比較不會發生鎖定衝突。
請盡量使用小型交易,而非大型交易,或考慮使用分區 DML 處理長時間執行的 DML 交易。長時間執行的交易會長時間取得鎖定,因此請考慮將觸及數千列的交易分成多個較小的交易,盡可能更新數百列。
如果您不需要讀寫交易提供的保證,請避免在讀寫交易中讀取任何資料,然後再提交變更,例如在個別的唯讀交易中讀取資料。大多數鎖定衝突都是因為強烈保證所致,目的是確保資料在讀取和提交之間保持不變。因此,如果讀寫交易未讀取任何資料,就不需要長時間鎖定儲存格。
在讀寫交易中,只指定所需的最低欄數。由於 Spanner 鎖定是以資料格為單位,當讀寫交易讀取過多資料欄時,系統會對這些資料格取得
ReaderShared
鎖定。如果其他交易在寫入過多資料欄時取得WriterShared
鎖定,可能會導致鎖定衝突。舉例來說,請考慮在讀取時指定一組資料欄,而不是*
。盡量減少讀寫交易中的 API 呼叫次數。API 呼叫的延遲時間可能會導致 Spanner 中的鎖定爭用,因為 API 呼叫會受到網路延遲和服務端延遲的影響。建議您盡可能在讀寫交易以外進行 API 呼叫。如果必須在讀寫交易中執行 API 呼叫,請務必監控 API 呼叫的延遲時間,盡量縮短鎖定取得期間。
遵循結構定義設計最佳做法。