在無伺服器運算上快取 DataFrame

Important

這項功能目前處於 公開預覽版。 Workspace 管理員可從 Previews 頁面控制 無伺服器運算中的 DataFrame 快取 的存取權限。 請參閱 管理 Azure Databricks 預覽。

在無伺服器運算中,你可以使用 df.cache() 或 df.persist() 來快取資料框架,以便在工作階段中重複使用成本高昂的結果。 Azure Databricks 在你呼叫cache()時會計算 DataFrame,並將結果以快照形式實體化。 DataFrame 以及你從它建立的 DataFrame 之後會在該工作階段的其餘時間內讀取快照,而不是重新計算。

Requirements

  • 已為您的工作區啟用 無伺服器運算上的 DataFrame 快取 預覽版(預設啟用)。 Workspace 管理員可以從 預覽 頁面關閉它。 在啟用 合規安全設定檔 的工作空間中,預覽無法使用。 請參閱管理工作區層級預覽。
  • 無伺服器運算上的筆記本與工作必須使用 環境版本 5 或以上。
  • Python 版 Databricks Connect 必須是 18.1.8 或以上,Scala 版 Databricks Connect 必須是 18.1.5 或以上。

較舊版的用戶端會出現 CACHE_REQUIRES_NEWER_CLIENT_ON_SERVERLESS_GC 錯誤。

何時在無伺服器運算中使用 DataFrame 快取

當您需要以下操作時,請在無伺服器運算上快取 DataFrame:

  • 在同一個工作階段中,重複使用昂貴的連接、聚合或過濾結果多次。
  • 在函 foreachBatch 式中計算一次串流微批次,然後寫入多個接收端。
  • 在同一個已準備好的特徵資料上訓練或調整多個機器學習模型。
  • 即使來源資料表改變,執行多個分析步驟時也要保持穩定的快照。

請參閱最佳實務。

在無伺服器運算上快取 DataFrame API

以下範例會快取一天內與客戶資料合併的訂單,計算當天的總營收,將區域收入附加到報告表中,然後釋出快取。 兩個結果都是讀取快取快照,而非重新執行 join 。

Python

from pyspark.sql import functions as F

# The DataFrame to cache: one day of orders joined with customer data.
oct_15_orders = (
    spark.table("main.sales.orders")
    .filter(F.col("order_date") == "2026-10-15")
    .join(spark.table("main.sales.customers"), "customer_id")
)
oct_15_orders.cache()

# Both results are built from oct_15_orders, so they read the cached snapshot.
total_revenue = oct_15_orders.agg(F.sum("amount")).first()[0]
print(f"Total revenue on 2026-10-15: {total_revenue}")

revenue_by_region = oct_15_orders.groupBy("order_date", "region").agg(F.sum("amount").alias("revenue"))
revenue_by_region.write.mode("append").saveAsTable("main.reports.revenue_by_region")

# Release the cache and the DataFrame when you no longer need it.
# oct_15_orders and DataFrames built from it can no longer be used.
oct_15_orders.unpersist()

Scala

import org.apache.spark.sql.functions._

// The DataFrame to cache: one day of orders joined with customer data.
val oct15Orders = spark.table("main.sales.orders")
  .filter(col("order_date") === "2026-10-15")
  .join(spark.table("main.sales.customers"), "customer_id")
oct15Orders.cache()

// Both results are built from oct15Orders, so they read the cached snapshot.
val totalRevenue = oct15Orders.agg(sum("amount")).first().get(0)
println(s"Total revenue on 2026-10-15: $totalRevenue")

val revenueByRegion = oct15Orders.groupBy("order_date", "region").agg(sum("amount").alias("revenue"))
revenueByRegion.write.mode("append").saveAsTable("main.reports.revenue_by_region")

// Release the cache and the DataFrame when you no longer need it.
// oct15Orders and DataFrames built from it can no longer be used.
oct15Orders.unpersist()

在以下 API 描述中,df 代表您工作階段中的任何 DataFrame,例如前述範例中的 oct_15_orders。

df.cache()

會立即計算 df ,並將結果快取為 快照 ,供你整個會話使用。

  • 回傳:相同的快取 DataFrame,df現在是以快照為基礎,而不是以其來源資料表上的運算為基礎。 請參考以下運作方式。
  • 行為:
    • 會立即執行 DataFrame 的查詢,並在呼叫返回前將結果具體化為快照。
    • 呼叫結束後,df 和回傳的資料幀(df 本身)都是基於快照。 它們不再連接到來源資料表的計算,因此會讀取快照,而非重新計算查詢。
    • 直接或透過其他衍生資料框架,從 df 衍生的資料框架會自動讀取快照,而不是再次執行原始查詢。 請參閱 閱讀快照說明。
    • 快照是你的工作階段本地的。 只有已快取 df 的工作階段才能讀取它。 參見 隔離與特權。
    • 快照會持續到會話結束,最多七天。 Azure Databricks 會自動在背景中回收其儲存空間。 呼叫 df.unpersist() 可以讓它更早回收儲存空間。 請參閱 生命週期與時間限制 ,以及 儲存與清理。
    • 如果運算 df 失敗,df.cache() 會引發錯誤,且 df 不會被快取。
    • 對已經快取的 DataFrame 呼叫 df.cache() 不會執行任何操作。 df 不再計算。

當你呼叫 df.cache() 之後,快照會固定下來。 之後寫入到 df 讀取資料的資料表中的資料,以及對這些資料表的結構變更(如新增或重新命名欄位),都不會影響 df。 若要從最新資料重新計算,請從來源資料表重新建立 DataFrame,必要時快取新的 DataFrame。

檢視快照

在無伺服器運算中,快取比對是以 DataFrame 衍生為基礎。 之後 df.cache(),只有以下資料框架自動讀取快照,而非重新計算來源資料表的查詢:

  • 你快取的 DataFrame df。
  • 由 df衍生出的資料框架,例如帶有 select()、 filter()、 groupBy()、 或 join()。
  • 從這些衍生的 DataFrame 再進一步鏈結而來的 DataFrame。

一個新的 DataFrame,若重複執行相同查詢,會讀取來源資料表,儘管其邏輯相同,因為它並非源自 df。

這使得快取匹配變得明確且由你控制:DataFrame 只有在你從已快取的 DataFrame 衍生它時,才會讀取快取資料。 每兩個 DataFrame 中的一個會讀取來源資料表。

Python

oct_15_orders = (
    spark.table("main.sales.orders")
    .filter(F.col("order_date") == "2026-10-15")
    .join(spark.table("main.sales.customers"), "customer_id")
)
# The join runs one time, when you call cache().
oct_15_orders.cache()

# Derived from oct_15_orders: read the snapshot, so the join doesn't run again.
large_orders = oct_15_orders.filter(F.col("amount") > 1000)
revenue_by_region = oct_15_orders.groupBy("order_date", "region").agg(F.sum("amount").alias("revenue"))

# Derived from large_orders, which is derived from oct_15_orders: also reads the snapshot.
large_orders_by_region = large_orders.groupBy("region").count()

# Not derived from oct_15_orders: reads the source tables and runs the join again.
same_query = (
    spark.table("main.sales.orders")
    .filter(F.col("order_date") == "2026-10-15")
    .join(spark.table("main.sales.customers"), "customer_id")
)

Scala

val oct15Orders = spark.table("main.sales.orders")
  .filter(col("order_date") === "2026-10-15")
  .join(spark.table("main.sales.customers"), "customer_id")
// The join runs one time, when you call cache().
oct15Orders.cache()

// Derived from oct15Orders: read the snapshot, so the join doesn't run again.
val largeOrders = oct15Orders.filter(col("amount") > 1000)
val revenueByRegion = oct15Orders.groupBy("order_date", "region").agg(sum("amount").alias("revenue"))

// Derived from largeOrders, which is derived from oct15Orders: also reads the snapshot.
val largeOrdersByRegion = largeOrders.groupBy("region").count()

// Not derived from oct15Orders: reads the source tables and runs the join again.
val sameQuery = spark.table("main.sales.orders")
  .filter(col("order_date") === "2026-10-15")
  .join(spark.table("main.sales.customers"), "customer_id")

因為 df 讀取的是快照,而不是建立它所依據的資料框架,你無法透過這些資料框架來參考 df 的欄位。 請參閱限制。

df.persist(storageLevel)

接受儲存層級的 df.cache() 別名。

  • 參數:
    • storageLevel (可選):一個 StorageLevel,例如 StorageLevel.DISK_ONLY。 無伺服器運算支援任何儲存層級並忽略它。 快照對每個儲存層級都以相同方式儲存,且 df.storageLevel 會回傳 StorageLevel.DISK_ONLY。

回傳值與行為與 df.cache() 相同。

df.unpersist(blocking)

釋放 df 的已快取快照,並銷毀 DataFrame。

  • 參數:
    • blocking (可選):忽略。 df.unpersist() 無論是哪個值,都會立即傳回。
  • 回傳:同一個 DataFrame,df現已銷毀。
  • 行為:
    • 標記快照為可回收。 Azure Databricks 接著會刪除快取的資料,並在背景回收儲存空間。
    • 摧毀 df:df 且先前從它衍生出的任何資料框架都不再可用,且對其的任何後續查詢都會引發 CACHE_DATAFRAME_ACCESS_AFTER_UNCACHE。 如果你在呼叫 df 前快取了從 df.unpersist() 衍生出來的資料幀,該資料幀仍可使用,因為它有自己的快照。
    • 若要繼續使用同一資料來源,請從原始資料來源建立新的 DataFrame,而非重複使用未快取的資料框架。
    • 對從未快取過或已經解除快取的資料框呼叫 df.unpersist(),不會執行任何操作。

當你不再需要快取資料時,例如當你的筆記本或工作中的後續步驟不再讀取這些資料時,再呼叫 df.unpersist() 。 這讓 Azure Databricks 能更早回收儲存空間。 不要用 df.unpersist() 來刷新資料。 要處理最新資料,請從來源資料表建立新的 DataFrame。

Python

oct_15_orders = spark.table("main.sales.orders").filter(F.col("order_date") == "2026-10-15")
oct_15_orders.cache()
large_orders = oct_15_orders.filter(F.col("amount") > 1000)
orders_per_customer = oct_15_orders.groupBy("customer_id").count()
orders_per_customer.cache()  # Has its own snapshot.

# Acknowledge that the cached data is no longer needed.
# This releases the storage and destroys oct_15_orders.
oct_15_orders.unpersist()

oct_15_orders.count()  # Raises CACHE_DATAFRAME_ACCESS_AFTER_UNCACHE.
large_orders.count()  # Raises CACHE_DATAFRAME_ACCESS_AFTER_UNCACHE.
orders_per_customer.count()  # Reads its own snapshot.

# If you need the data again later, build a new DataFrame from the source tables.
oct_15_orders = spark.table("main.sales.orders").filter(F.col("order_date") == "2026-10-15")

Scala

var oct15Orders = spark.table("main.sales.orders").filter(col("order_date") === "2026-10-15")
oct15Orders.cache()
val largeOrders = oct15Orders.filter(col("amount") > 1000)
val ordersPerCustomer = oct15Orders.groupBy("customer_id").count()
ordersPerCustomer.cache() // Has its own snapshot.

// Acknowledge that the cached data is no longer needed.
// This releases the storage and destroys oct15Orders.
oct15Orders.unpersist()

oct15Orders.count() // Throws CACHE_DATAFRAME_ACCESS_AFTER_UNCACHE.
largeOrders.count() // Throws CACHE_DATAFRAME_ACCESS_AFTER_UNCACHE.
ordersPerCustomer.count() // Reads its own snapshot.

// If you need the data again later, build a new DataFrame from the source tables.
oct15Orders = spark.table("main.sales.orders").filter(col("order_date") === "2026-10-15")

df.storageLevel

取得 df 的儲存層級。

  • 回傳:當 df 已快取時,傳回 StorageLevel.DISK_ONLY。 StorageLevel.NONE 在你快取它之前,以及在你呼叫 df.unpersist() 之後。

Python

from pyspark import StorageLevel

oct_15_orders.cache()
oct_15_orders.storageLevel == StorageLevel.DISK_ONLY  # True

Scala

import org.apache.spark.storage.StorageLevel

oct15Orders.cache()
oct15Orders.storageLevel == StorageLevel.DISK_ONLY // true

關於方法簽名,請參見 PySpark 關於 cache、 persist、 unpersist 和 storageLevel 的參考資料。

生命週期與時間限制

快取的資料只存在於你快取的 Azure Databricks 會話中。 當工作階段結束後,該系統將無法使用,且持續時間最多為七天。 超過七天限制後,使用快取的 DataFrame 會產生錯誤,而非重新讀取來源資料表。 若要再次快取,請開始新的工作階段。

儲存與清理

Azure Databricks 會自動管理快取資料的儲存空間。 快取的資料會儲存在雲端儲存,具體儲存位置依據你工作區類型而定:

  • 經典工作區:快取資料儲存在 工作區建立時設定的儲存帳戶中。 你可以在儲存帳號層級設定加密。

當快取資料無法存取時,Azure Databricks 會自動在背景收回儲存空間。 這包括因斷線或當機而結束的會話。 呼叫df.unpersist()可以讓 Azure Databricks 更早回收儲存空間。

孤立與特權

任何能在無伺服器運算上執行程式碼的使用者,都可以快取 DataFrame。 你不需要額外的特權。

快取資料提供會話層級的隔離。 只有快取 DataFrame 的會話能讀取其快取資料。 其他使用者無法存取,包括同一個筆記本的其他使用者。

快取的資料包含你快取時允許讀取的內容。 之後對來源資料表施加的權限變更、列篩選和欄位遮罩,則不會影響快取的資料。

最佳做法

  • 需要穩定快照時的快取:如果你對同一計算資料執行多次轉換、聚合或深入分析,快照會給出一個一致的基準,且在來源資料表變動時不會改變。
  • 快取高成本的運算,而非單純的讀表:df.cache()將查詢結果具體化,之後讀取時掃描該快照。 如果你快取了純的表讀,例如 spark.table("main.sales.orders").cache(),後續讀取仍會掃描完整的資料表副本,所以快取節省的資源有限。 快取在執行連接、彙總和複雜篩選等高成本作業後會見效。
  • 不要快取只讀一次的資料:df.cache() 會立即計算並儲存結果,這需要時間和儲存空間。 如果你只讀一次結果,前期成本就不值得。
  • 透過從已快取的 DataFrame 衍生來使用快取:要讀取快照,請從已快取的 DataFrame 變數建立後續操作,例如 df.select(...) 或 df.filter(...)。 一個獨立的 DataFrame 是根據來源資料表的相同計算所建構,不會使用快取。
  • 不要用快取來跨會話或跨使用者資料分享:快取的資料對你的會話是私密的。 其他工作階段、筆記本、工作執行和使用者都無法讀取它。 若要在會話間或與其他使用者分享計算結果,請將其寫入永久表。
  • 將快取資料的會話保持在七天以下:詳見 生命週期與時間限制。 如果你的工作流程需要將資料保留更久,就寫入資料表。
  • 當你不再需要快取資料時,呼叫df.unpersist():這讓 Azure Databricks 能更早回收儲存空間。 呼叫結束後,DataFrame 及其衍生的 DataFrame 無法再使用。
  • 不要用 df.unpersist() 來刷新資料:df.unpersist() 會結束快照的生命週期。 要擷取來源資料表的變更,請從來源資料表建立新的 DataFrame。

Limitations

無伺服器運算中的 DataFrame 快取有以下限制:

  • 欄位名稱與類型:以下 DataFrame 無法快取。 在快取前重新命名或轉型欄位,例如使用 .alias("total_amount")。
    • 欄位名稱重複的 DataFrame。
    • 欄位名稱包含空格或任何字元 ,;{}()= 的資料框架,例如彙總結果中的 sum(amount)。
    • 帶有區間欄位的 DataFrames。
    • 包含 NULL 型別值的陣列、對應或結構的資料框。
    • DataFrame 沒有欄位,或只有 NULL 型別欄位。
  • 欄位參考:快取 DataFrame 後,無法透過建立它時所依據的 DataFrame 來參考其欄位。 例如,在 oct_15_orders = orders.join(customers, "customer_id") 和 oct_15_orders.cache() 之後,oct_15_orders.select(orders.amount) 則會提高 CANNOT_RESOLVE_DATAFRAME_COLUMN。 請改用 oct_15_orders.amount 或 "amount"。
  • SQL 與目錄快取 API:SQL 快取陳述式(例如 CACHE TABLE)以及 spark.catalog 的資料表快取方法,不支援在無伺服器運算上使用。 詳見 快取限制。 若要在 SQL 中重用查詢結果,請使用 會話本地的暫存資料表。
  • 資訊結構查詢:當元儲存庫中有多個工作階段快取 DataFrames 時,對 information_schema.volumes 和 information_schema.schemata 的查詢可能會變慢或逾時。以目錄篩選 volumes 不會讓查詢更快。 查詢 information_schema.tables 不會受到影響。
  • Azure Government regions:Azure Government 區域中無伺服器運算不支援 DataFrame 快取。

從傳統運算遷移

對大多數工作負載來說,在無伺服器運算上快取的結果與傳統運算相同,且現有程式碼可不做變更地執行。 下表列出差異。

Behavior 無伺服器計算 傳統運算
快取比對 只有快取的 DataFrame 及其衍生的 DataFrame 會自動使用快取。 任何具有相同執行計畫形狀的查詢都會自動使用快取。
數據新鮮度 在你呼叫 df.cache() 的時候拍攝的一致性快照。 情況有所不同:快取資料在某些情況下會被刷新,在其他情況下則提供快取的快照結果。
共享範圍 只屬於你的工作階段。 在叢集內的多個會話間共享。
具象化 立即:當你呼叫 df.cache()時,立即計算。 懶惰:在 DataFrame 的第一個動作時計算。
df.unpersist() 會銷毀 DataFrame 並在明確時機釋放其快照。 釋放記憶體中的快取。 後續使用資料框時,可能會取得最新或過時的資料,視產品與資料來源而定。

以下將更詳細說明前兩個差異:

  • 快取比對:
    • 受此變更影響的工作負載:大多數工作負載不會受到影響。 如果你持續使用快取的 DataFrame 變數,例如帶有 df.select(...) 或 df.filter(...),快取會如先前一樣運作。 此變更僅影響獨立建置的 DataFrame 或 SQL 查詢因具有相同的計畫形狀而重複使用快取的工作負載。 在無伺服器運算中,這些查詢會讀取來源資料表。
    • 如果這會影響您的工作負載:請在需要快取結果時使用已快取的 DataFrame 變數,而不是重新建立相同的查詢。
  • 資料新近性:
    • 受影響的工作負載:大多數工作負載不會受影響,因為寫入來源資料表後再次讀取快取資料並不常見。 這項變更只影響那些寫入來源資料表,然後預期快取的 DataFrame 會顯示新資料的工作負載。
    • 如果你的工作負載受到影響:寫入來源資料表後,從它們建立新的 DataFrame,而不是重複使用已快取的 DataFrame。

其他資源