管理系統建立版本之時態表中的歷程記錄資料保留

適用於: SQL Server 2016 (13.x) 和更新版本 Azure SQL DatabaseAzure SQL 受控執行個體

使用系統版本設定的時態表時態表,歷程記錄數據表可能會比一般數據表增加資料庫大小,特別是在下列情況下:

  • 您的歷程記錄資料保留一段很長的時間
  • 您更新或刪除大量資料修改模式

大型且不斷成長的歷程記錄數據表可能會因為單純的記憶體成本而成為問題,並對時態性查詢徵收效能稅。 因此,開發資料保留原則來管理歷程記錄資料表中的資料是規劃及管理每個時態表生命週期的重要環節。

歷程記錄資料表的資料保留管理

若要管理時態表的資料保留,第一步是決定每個時態表所需的保留期限。 在大部分情況下,您應將保留原則視為使用時態表之應用程式商務邏輯的一部分。 例如,資料稽核和時間移動案例中的應用程式,對於供應線上查詢之歷程記錄資料的保留期間有嚴格的規範。

決定數據保留期間之後,下一個步驟是開發管理歷程記錄數據的計劃。 您必須決定儲存歷程記錄數據的方式和位置,以及如何刪除比保留需求還舊的歷程記錄數據。 以下是在時態性歷程記錄數據表中管理歷程記錄數據的方法:

對於以上任一種方式,移轉或清除歷程記錄資料的邏輯乃基於與目前資料表之期間結束相對應的資料行。 每個數據列的句號結束值會決定當數據列版本關閉的那一刻,也就是當它落入歷程記錄數據表時。 例如, ValidTo < DATEADD (DAYS, -30, SYSUTCDATETIME ()) 條件指定超過一個月的歷程記錄資料需要移除或移出歷程記錄資料表。

注意

本文中的範例會使用此 建立系統版本設定的時態表

使用數據表數據分割方法

數據分割數據表和索引 可讓大型數據表更容易管理且可調整。 透過資料表資料分割方法,您可以使用記錄資料表資料分割來實作以時間為基礎的自訂資料清除或離線封存。 數據表數據分割也可讓您使用數據分割消除來查詢數據歷程記錄子集上的時態表時的效能優勢。

透過數據表數據分割,您可以實作滑動視窗,從歷程記錄數據表移出歷史數據的最舊部分,並在年齡方面保留保留部分的大小-維護歷程記錄數據表中的數據等於所需的保留期間。 當 是 ONSYSTEM_VERSIONING,支援從歷程記錄數據表切換數據的作業,這表示您可以清除部分記錄數據,而不需要引入維護期間或封鎖您的一般工作負載。

注意

若要執行數據分割切換,記錄數據表上的叢集索引必須與數據分割架構一致(必須包含 ValidTo)。 系統所建立的默認歷程記錄數據表包含包含 和 ValidFrom 數據行的叢集索引ValidTo,最適合用於數據分割、插入新的記錄數據,以及一般時態查詢。 如需詳細資訊,請參閱 時態表

滑動視窗有兩組您需要執行的工作:

  • 資料分割組態工作
  • 週期性資料分割維護工作

針對此圖,假設您想要將歷程記錄數據保留六個月,而且您想要將每個月的數據保留在個別的數據分割中。 此外,假設您已在 2023 年 9 月啟用系統版本設定。

資料分割組態工作會建立歷程記錄資料表的初始資料分割組態。 在此範例中,您會建立與滑動視窗大小相同的數位分割,以月份為單位,加上預先準備的額外空白分割區(本文稍後說明)。 此設定可確保當您第一次啟動週期性分割維護工作時,系統能夠正確儲存新的數據,並保證您永遠不會使用數據分割分割區分割,以避免昂貴的數據移動。 您應該使用本文稍後的範例腳本,使用 Transact-SQL 來執行這項工作。

下圖顯示初始數據分割設定,以保留六個月的數據。

Diagram showing initial partitioning configuration to keep six months of data.

注意

請參閱本文稍後的數據表數據分割效能考慮,以瞭解使用 RANGE LEFTRANGE RIGHT 設定數據分割時的效能影響。

第一個和最後一個數據分割分別 在較低和上限上開啟 ,以確保每個新數據列都有目的地數據分割,而不論數據分割數據行中的值為何。 隨著時間的流逝,歷程記錄數據表中的新數據列會落在較高的分割區中。 當第六個分割區填滿時,您會到達目標保留期間。 這是首次啟動週期性資料分割維護工作的時候 (您需要將它排程為定期執行,在本範例中為每月一次)。

下圖說明週期性分割維護工作(請參閱本節稍後的詳細步驟)。

Diagram showing the recurring partition maintenance tasks.

週期性資料分割維護工作的詳細步驟如下︰

  1. SWITCH OUT:建立臨時表,然後使用 ALTER TABLE (Transact-SQL) 語句搭配 SWITCH PARTITION 自變數,在歷程記錄數據表與臨時表之間切換數據分割(請參閱範例 C.在數據表之間切換數據分割)。

    ALTER TABLE [<history table>] SWITCH PARTITION 1 TO [<staging table>];
    

    在數據分割切換之後,您可以選擇性地從臨時表封存數據,然後卸除或截斷臨時表,以便下次執行此週期性分割維護工作時準備好。

  2. MERGE RANGE:使用 ALTER PARTITION FUNCTION (Transact-SQL) 與 合併空白數據分割1MERGE RANGE分割2區(請參閱範例 B)。 藉由使用此函式移除最低界限,您可以有效地合併空白數據分割 1 與先前 2 的數據分割,以形成新的分割區 1。 其他資料分割也可以有效地變更其序數。

  3. SPLIT RANGE:使用 ALTER PARTITION FUNCTION (Transact-SQL) 搭配 SPLIT RANGE 建立新的空白分割區 7 (請參閱範例 A)。 藉由使用這個函數加入新的上限,您可以有效地為下個月份建立個別資料分割。

使用 Transact-SQL 在歷程記錄資料表上建立資料分割

使用下列 Transact-SQL 腳本來建立數據分割函數、分割區架構,然後重新建立叢集索引,以與分割區架構、分割區對齊。 在此範例中,您會建立具有每月分割區的六個月滑動視窗,從 2023 年 9 月開始。

BEGIN TRANSACTION

/*Create partition function*/
CREATE PARTITION FUNCTION [fn_Partition_DepartmentHistory_By_ValidTo] (DATETIME2(7))
AS RANGE LEFT FOR VALUES (
    N'2023-09-30T23:59:59.999',
    N'2023-10-31T23:59:59.999',
    N'2023-11-30T23:59:59.999',
    N'2023-12-31T23:59:59.999',
    N'2024-01-31T23:59:59.999',
    N'2024-02-29T23:59:59.999'
);

/*Create partition scheme*/
CREATE PARTITION SCHEME [sch_Partition_DepartmentHistory_By_ValidTo]
AS PARTITION [fn_Partition_DepartmentHistory_By_ValidTo] TO (
    [PRIMARY], [PRIMARY], [PRIMARY], [PRIMARY],
    [PRIMARY], [PRIMARY], [PRIMARY]
);

/*Re-create index to be partition-aligned with the partitioning schema*/
CREATE CLUSTERED INDEX [ix_DepartmentHistory] ON [dbo].[DepartmentHistory] (
    ValidTo ASC,
    ValidFrom ASC
)
WITH (
    PAD_INDEX = OFF,
    STATISTICS_NORECOMPUTE = OFF,
    SORT_IN_TEMPDB = OFF,
    DROP_EXISTING = ON,
    ONLINE = OFF,
    ALLOW_ROW_LOCKS = ON,
    ALLOW_PAGE_LOCKS = ON,
    DATA_COMPRESSION = PAGE
) ON [sch_Partition_DepartmentHistory_By_ValidTo](ValidTo);

COMMIT TRANSACTION;

使用 Transact-SQL 在滑動視窗案例中維護數據分割

使用下列 Transact-SQL 腳本,在滑動視窗案例中維護數據分割。 在此範例中,您會使用 MERGE RANGE切換 2023 年 9 月的數據分割,然後使用 新增 2024 SPLIT RANGE年 3 月的新分割區。

BEGIN TRANSACTION

/*(1) Create staging table */
CREATE TABLE [dbo].[staging_DepartmentHistory_September_2023] (
    DeptID INT NOT NULL,
    DeptName VARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
    ManagerID INT NULL,
    ParentDeptID INT NULL,
    ValidFrom DATETIME2(7) NOT NULL,
    ValidTo DATETIME2(7) NOT NULL
) ON [PRIMARY]
WITH (DATA_COMPRESSION = PAGE);

/*(2) Create index on the same filegroups as the partition that will be switched out*/
CREATE CLUSTERED INDEX [ix_staging_DepartmentHistory_September_2023]
ON [dbo].[staging_DepartmentHistory_September_2023] (
    ValidTo ASC,
    ValidFrom ASC
)
WITH (
    PAD_INDEX = OFF,
    SORT_IN_TEMPDB = OFF,
    DROP_EXISTING = OFF,
    ONLINE = OFF,
    ALLOW_ROW_LOCKS = ON,
    ALLOW_PAGE_LOCKS = ON
) ON [PRIMARY];

/*(3) Create constraints matching the partition that will be switched out*/
ALTER TABLE [dbo].[staging_DepartmentHistory_September_2023]
    WITH CHECK ADD CONSTRAINT [chk_staging_DepartmentHistory_September_2023_partition_1]
    CHECK (ValidTo <= N'2023-09-30T23:59:59.999')

ALTER TABLE [dbo].[staging_DepartmentHistory_September_2023]
    CHECK CONSTRAINT [chk_staging_DepartmentHistory_September_2023_partition_1]

/*(4) Switch partition to staging table*/
ALTER TABLE [dbo].[DepartmentHistory] SWITCH PARTITION 1
TO [dbo].[staging_DepartmentHistory_September_2023]
    WITH (WAIT_AT_LOW_PRIORITY(MAX_DURATION = 0 MINUTES, ABORT_AFTER_WAIT = NONE))

/*(5) [Commented out] Optionally archive the data and drop staging table
      INSERT INTO [ArchiveDB].[dbo].[DepartmentHistory]
      SELECT * FROM [dbo].[staging_DepartmentHistory_September_2023];
      DROP TABLE [dbo].[staging_DepartmentHIstory_September_2023];
*/
/*(6) merge range to move lower boundary one month ahead*/
ALTER PARTITION FUNCTION [fn_Partition_DepartmentHistory_By_ValidTo]()
    MERGE RANGE(N'2023-09-30T23:59:59.999');
/*(7) Create new empty partition for "April and after" by creating new boundary point and specifying NEXT USED file group*/
ALTER PARTITION SCHEME [sch_Partition_DepartmentHistory_By_ValidTo] NEXT USED [PRIMARY]
    ALTER PARTITION FUNCTION [fn_Partition_DepartmentHistory_By_ValidTo]()
    SPLIT RANGE(N'2024-03-31T23:59:59.999');
COMMIT TRANSACTION

您可以稍微修改先前的文稿,並在一般每月維護程式中使用它:

  1. 在步驟 (1) 中,為您想要移除的月份建立新的臨時表(本範例中的下一個 10 月)。
  2. 在步驟 (3) 中,建立及檢查符合要移除之資料月份的條件約束︰ValidTo <= N'2023-10-31T23:59:59.999' (適用於 10 月資料分割)。
  3. 在步驟 (4) SWITCH 分割 1 至新建立的臨時表。
  4. 在步驟 (6) 中,藉由合併下限來改變分割區函式: MERGE RANGE(N'2023-10-31T23:59:59.999' 移出 10 月的數據之後。
  5. 在步驟 (7) 中,分割區函式會建立新的上限: SPLIT RANGE (N'2024-04-30T23:59:59.999' 移出 10 月的數據之後。

然而,最好的方案是定期執行不需要修改指令碼即可在每個月執行適當動作的一般性 Transact-SQL 指令碼。 可以將先前的腳本一般化,以根據提供的參數採取行動(需要合併的下邊界,以及使用分割區分割建立的新界限)。 為了避免在每個月建立暫存資料表,您可以將檢查條件約束變更為比對要切換移出的資料分割,藉此預先建立暫存資料表並重複使用。請參閱下列頁面以了解如何使用 Transact-SQL 指令碼將 滑動視窗全面自動化 的概念。

資料表資料分割效能考量

請務必執行 MERGESPLIT RANGE 作業,以避免任何數據移動,因為數據移動可能會造成顯著的效能負擔。 如需詳細資訊,請參閱 修改分割區函式。 您可以使用 ,而不是RANGE RIGHT在建立分割區函式時執行此RANGE LEFT動作。

讓我們先以可視化方式說明 和 RANGE RIGHT 選項的意義RANGE LEFT

Diagram showing the RANGE LEFT and RANGE RIGHT options.

當您將分割區函 RANGE LEFT式定義為 時,指定的值是數據分割的上限。 當您使用 RANGE RIGHT時,指定的值是數據分割的下限。 當您使用 MERGE RANGE 作業從數據分割函數定義中移除界限時,基礎實作也會移除包含界限的分割區。 如果該分割區不是空的,數據會移至作業結果的數據 MERGE RANGE 分割。

在滑動視窗案例中,您一律會移除最低的分割區界限。

  • RANGE LEFT 案例:最低數據分割界限屬於 1分割區 ,這是空的 (在分割區切換離開之後),因此 MERGE RANGE 不會產生任何數據移動。
  • RANGE RIGHT case:最低分割區界限屬於 2數據分割,這不是空的,因為 1 切換離開分割區已清空。在此情況下, MERGE RANGE 會產生數據移動(數據分割 2 中的數據會移至數據分割 1)。 若要避免這種情況, RANGE RIGHT 在滑動視窗案例中,必須有一律空白的數據分割 1。 這表示如果您使用 RANGE RIGHT,則應該建立和維護一個與案例相較 RANGE LEFT 之下的額外分割區。

結論:在滑動分割區中使用 RANGE LEFT 較容易進行數據分割管理,並避免數據移動。 不過,使用 定義數據分割界限 RANGE RIGHT 會稍微簡單一點,因為您不需要處理日期時間檢查問題。

使用自訂清除文本方法

如果數據表數據分割不可行,另一種方法是使用自定義清除腳本從記錄數據表中刪除數據。 只有在 時 SYSTEM_VERSIONING = OFF,才能從歷程記錄數據表中刪除數據。 為了避免數據不一致,請在維護期間執行清除(當修改數據的工作負載不在作用中時)或交易內(有效地封鎖其他工作負載)。 此作業需要 CONTROL 目前和歷程記錄數據表的許可權。

為了盡可能避免阻礙一般應用程式和使用者查詢,於交易內執行清除指令碼時,請在設定延遲的情況下以較小的區塊刪除資料。 雖然在所有案例中刪除每個數據區塊沒有最佳大小,但在單一交易中刪除超過 10,000 個數據列可能會造成顯著的影響。

每個時態表的清除邏輯都相同,因此您可以透過排程定期執行的泛型預存程式,針對您想要限制數據歷程記錄的每個時態表進行自動化。

下圖說明如何安排單一資料表的清除邏輯,以減少對執行中工作負載的影響。

Diagram showing how your cleanup logic should be organized for a single table to reduce impact on the running workloads.

以下是一些實作程序的高階指導方針。 將清除邏輯排程為每天執行,並且逐一處理所有需要清除資料的時態表。 使用 SQL Server Agent 或其他工具來排程這個程序:

  • 刪除每個時態表中的歷程記錄數據,從社區塊數個反覆專案中最舊到最近的數據列開始,並避免刪除單一交易中的所有數據列,如上圖所示。
  • 實作每個反覆項目作為從歷程記錄數據表中移除部分數據的泛型預存程式調用(請參閱下列程式碼範例。
  • 在每次叫用程序時,計算需要刪除單一時態表中多少資料列。 根據這一點和您想要擁有的反覆項目數目,判斷每個程式調用的動態分割點。
  • 規劃在單一數據表的反覆項目之間有一段延遲期間,以減少對存取時態表的應用程式的影響。

對於刪除單一時態表之資料的預存程序,其內容可能與以下程式碼片段相似 (在環境中套用之前,請仔細檢閱以下程式碼並加以調整):

DROP PROCEDURE IF EXISTS usp_CleanupHistoryData;
GO
CREATE PROCEDURE usp_CleanupHistoryData @temporalTableSchema SYSNAME,
    @temporalTableName SYSNAME,
    @cleanupOlderThanDate DATETIME2
AS
DECLARE @disableVersioningScript NVARCHAR(MAX) = '';
DECLARE @deleteHistoryDataScript NVARCHAR(MAX) = '';
DECLARE @enableVersioningScript NVARCHAR(MAX) = '';
DECLARE @historyTableName SYSNAME
DECLARE @historyTableSchema SYSNAME
DECLARE @periodColumnName SYSNAME

/*Generate script to discover history table name and end of period column for given temporal table name*/
EXECUTE sp_executesql
N'SELECT @hst_tbl_nm = t2.name,
      @hst_sch_nm = s2.name,
      @period_col_nm = c.name
  FROM sys.tables t1
  INNER JOIN sys.tables t2 ON t1.history_table_id = t2.object_id
  INNER JOIN sys.schemas s1 ON t1.schema_id = s1.schema_id
  INNER JOIN sys.schemas s2 ON t2.schema_id = s2.schema_id
  INNER JOIN sys.periods p ON p.object_id = t1.object_id
  INNER JOIN sys.columns c ON p.end_column_id = c.column_id AND c.object_id = t1.object_id
  WHERE t1.name = @tblName AND s1.name = @schName',
N'@tblName sysname,
    @schName sysname,
    @hst_tbl_nm sysname OUTPUT,
    @hst_sch_nm sysname OUTPUT,
    @period_col_nm sysname OUTPUT',
@tblName = @temporalTableName,
@schName = @temporalTableSchema,
@hst_tbl_nm = @historyTableName OUTPUT,
@hst_sch_nm = @historyTableSchema OUTPUT,
@period_col_nm = @periodColumnName OUTPUT

IF @historyTableName IS NULL OR @historyTableSchema IS NULL OR @periodColumnName IS NULL
    THROW 50010, 'History table cannot be found. Either specified table is not system-versioned temporal or you have provided incorrect argument values.', 1;

/*Generate 3 statements that run inside a transaction:
  (1) SET SYSTEM_VERSIONING = OFF,
  (2) DELETE FROM history_table,
  (3) SET SYSTEM_VERSIONING = ON
  On SQL Server 2016, it is critical that (1) and (2) run in separate EXEC statements, or SQL Server generates the following error:
  Msg 13560, Level 16, State 1, Line XXX
  Cannot delete rows from a temporal history table '<database_name>.<history_table_schema_name>.<history_table_name>'.
*/

SET @disableVersioningScript = @disableVersioningScript
    + 'ALTER TABLE [' + @temporalTableSchema + '].[' + @temporalTableName
    + '] SET (SYSTEM_VERSIONING = OFF)'
SET @deleteHistoryDataScript = @deleteHistoryDataScript + ' DELETE FROM ['
    + @historyTableSchema + '].[' + @historyTableName + '] WHERE ['
    + @periodColumnName + '] < ' + '''' + CONVERT(VARCHAR(128), @cleanupOlderThanDate, 126) + ''''
SET @enableVersioningScript = @enableVersioningScript + ' ALTER TABLE ['
    + @temporalTableSchema + '].[' + @temporalTableName
    + '] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema
    + '].[' + @historyTableName + '], DATA_CONSISTENCY_CHECK = OFF )); '

BEGIN TRANSACTION
    EXEC (@disableVersioningScript);
    EXEC (@deleteHistoryDataScript);
    EXEC (@enableVersioningScript);
COMMIT;

使用時態歷程記錄保留原則方法

適用於:SQL Server 2017 (14.x) 和更新版本,以及 Azure SQL Database。

時態性歷程記錄保留可以在個別的數據表層級設定,讓用戶能夠建立彈性的過時原則。 套用暫時保留很簡單:只需要在資料表建立或結構描述變更期間設定一個參數。

定義保留原則之後,資料庫引擎會開始定期檢查是否有可自動清除資料的合格歷程記錄資料列。 識別相符資料列及從歷程記錄資料表中移除它們的作業,會以明確的方式在系統排程和執行的背景工作中進行。 記錄數據表數據列的存留期條件會根據代表期間結束的數據 SYSTEM_TIME 行來檢查。 如果保留期限 (例如,設定為六個月) 資料表資料列符合清除資格,請滿足下列條件:

ValidTo < DATEADD (MONTH, -6, SYSUTCDATETIME())

在上述範例中,數據 ValidTo 行會對應至句點的 SYSTEM_TIME 結尾。

如何設定保留原則?

設定時態資料表的保留原則之前,請先檢查是否已在資料庫層級啟用時態歷程記錄保留:

SELECT is_temporal_history_retention_enabled, name
FROM sys.databases;

資料庫旗標 is_temporal_history_retention_enabled 預設會設定為 ON ,但使用者可以使用 語句加以變更 ALTER DATABASE 。 這個值會在時間點還原 (PITR) 作業之後自動設定為 OFF 。 若要啟用資料庫的時態性歷程記錄保留清除,請執行下列語句:

ALTER DATABASE [<myDB>]
SET TEMPORAL_HISTORY_RETENTION ON;

保留原則是在數據表建立期間設定,方法是指定 參數的值 HISTORY_RETENTION_PERIOD

CREATE TABLE dbo.WebsiteUserInfo
(
    UserID INT NOT NULL PRIMARY KEY CLUSTERED,
    UserName NVARCHAR(100) NOT NULL,
    PagesVisited int NOT NULL,
    ValidFrom DATETIME2(0) GENERATED ALWAYS AS ROW START,
    ValidTo DATETIME2(0) GENERATED ALWAYS AS ROW END,
    PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
 )
WITH (SYSTEM_VERSIONING = ON
    (
        HISTORY_TABLE = dbo.WebsiteUserInfoHistory,
        HISTORY_RETENTION_PERIOD = 6 MONTHS
    )
);

您可以使用不同的時間單位來指定保留期間:DAYS、、 WEEKSMONTHSYEARS。 如果 HISTORY_RETENTION_PERIOD 省略, INFINITE 則會假設保留期。 您也可以明確地使用 INFINITE 關鍵詞。

在某些情況下,您可能會想要在建立數據表之後設定保留,或變更先前設定的值。 在此情況下,請使用 ALTER TABLE 語句:

ALTER TABLE dbo.WebsiteUserInfo
SET (SYSTEM_VERSIONING = ON (HISTORY_RETENTION_PERIOD = 9 MONTHS));

若要檢閱目前的保留原則狀態,請使用下列查詢,以聯結資料庫層級的暫時保留啟用旗標與個別資料表的保留期間:

SELECT DB.is_temporal_history_retention_enabled,
    SCHEMA_NAME(T1.schema_id) AS TemporalTableSchema,
    T1.name AS TemporalTableName,
    SCHEMA_NAME(T2.schema_id) AS HistoryTableSchema,
    T2.name AS HistoryTableName,
    T1.history_retention_period,
    T1.history_retention_period_unit_desc
FROM sys.tables T1
OUTER APPLY (
    SELECT is_temporal_history_retention_enabled
    FROM sys.databases
    WHERE name = DB_NAME()
    ) AS DB
LEFT JOIN sys.tables T2
    ON T1.history_table_id = T2.object_id
WHERE T1.temporal_type = 2;

SQL Database 如何刪除過時資料列?

清除處理序取決於歷程記錄資料表的索引配置。 只有具有叢集索引的記錄數據表(B+ 樹狀目錄或數據行存放區)可以設定有限的保留原則。 建立的背景工作可利用有限的保留期間為所有時態表執行過時資料清除。 資料列存放區 (B+ 型樹狀目錄) 叢集索引的清除邏輯會刪除較小區塊 (最多 10K) 中的過時資料列,最大限度地減輕資料庫記錄和 I/O 子系統的壓力。 雖然清除邏輯會利用必要的 B+ 樹狀結構索引,但無法堅決保證超過保留期間的數據列刪除順序。 因此,請勿對應用程式中的清除順序採取任何相依性。

叢集資料行存放區清除工作可一次移除整個資料列群組 (每個通常包含 1 百萬個資料列),這是非常有效率的方法,特別是在高速產生歷程記錄資料時。

Screenshot of clustered columnstore retention.

卓越的資料壓縮和有效率的保留清除,可讓叢集資料行存放區索引成為您的工作負載快速產生大量歷程記錄資料時的完美選擇。 此模式一般適用於使用時態表進行變更追蹤和稽核、趨勢分析或 IoT 資料擷取的大量交易處理工作負載。

如需詳細資訊,請參閱 使用保留原則管理時態表中的歷程記錄數據。