iT邦幫忙

2026 iThome 鐵人賽

DAY 7
1

先說結論

分析系統偏好欄式資料(Columnar),主要是因為分析查詢常常會:

  1. 掃描大量資料。
  2. 但只使用其中少數幾個欄位。
  3. 進行篩選、分組與聚合,例如 WHEREGROUP BYSUMAVG

欄式資料會把同一欄位的值集中存放。當 SQL 只需要 cityamountis_member 時,查詢引擎可以避免讀取不需要的 order_id,減少 I/O 與記憶體搬移。

不過,欄式資料不是所有情況下都比較快。若系統經常需要新增、修改或讀取單筆完整紀錄,Row-based 的資料排列通常更合適。

今天的重點是:資料如何排列,會直接影響 Query Engine 要讀多少資料,以及如何處理查詢。


同一份訂單資料,可以有兩種排列方式

假設有一張訂單表:

order_id city amount is_member
1 台北 1200 true
2 台中 850 false
3 台北 2300 true

這張表在畫面上看起來是一列一筆訂單;但資料真正寫入檔案或放入記憶體時,可以採用不同的排列方式。

Row-based 與 Columnar 的資料排列差異

圖 1:Row-based 與 Columnar 的資料排列差異。欄式資料能只讀取查詢需要的欄位,減少不必要的 I/O。圖由本文整理。

接下來分別看看兩種排列方式。


Row-based:以一列為單位排列

Row-based 又可稱為列式資料。它會把同一筆資料的所有欄位放在一起:

[1, 台北, 1200, true]
[2, 台中, 850, false]
[3, 台北, 2300, true]

如果系統想取得第 2 筆訂單,便可以直接讀取這筆完整紀錄:

[2, 台中, 850, false]

因此,Row-based 通常適合:

  • 新增一筆完整資料
  • 修改某筆資料的多個欄位
  • 依照主鍵查詢單筆資料
  • 需要頻繁讀寫完整紀錄的交易型系統

例如購物網站建立訂單時,系統通常需要同時處理訂單編號、商品、金額、付款狀態與收件資訊。這類情境重視的是「快速取得一筆完整資料」。

這種工作負載常被稱為 OLTP(Online Transaction Processing,線上交易處理)。


Columnar:以一欄為單位排列

Columnar 又稱為欄式資料。它不再把一筆訂單的所有欄位放在一起,而是將同一欄的資料集中儲存:

order_id : [1, 2, 3]
city     : [台北, 台中, 台北]
amount   : [1200, 850, 2300]
is_member: [true, false, true]

現在來看一段分析查詢:

SELECT city, SUM(amount)
FROM orders
WHERE is_member = true
GROUP BY city;

這段 SQL 需要使用:

city
amount
is_member

但不需要:

order_id

若資料來源支援欄位裁剪,查詢引擎就能只讀取需要的三個欄位,盡量略過 order_id

需要讀取:city、amount、is_member
不需要讀取:order_id

這種「只保留查詢需要欄位」的概念稱為 Column Pruning


為什麼欄式資料適合分析?

分析型查詢通常不是尋找一筆資料,而是從大量資料中計算出一個結果。

例如:

SELECT SUM(amount)
FROM orders
WHERE city = '台北';

查詢可能會掃描數百萬筆訂單,最後卻只回傳一個總金額。

如果資料採用 Row-based 排列,系統在讀取每筆訂單時,可能也會接觸到許多不需要的欄位:

order_id
customer_name
email
shipping_address
created_at
...

但欄式資料讓引擎能專注讀取:

city
amount

資料量越大、欄位越多、實際需要的欄位越少,欄式排列帶來的效益通常越明顯。


欄式資料也適合聚合運算

分析查詢常見的操作包括:

SUM(amount)
AVG(amount)
COUNT(*)
MAX(amount)
MIN(amount)

SUM(amount) 為例,查詢引擎只需要連續處理 amount 欄位:

amount: [1200, 850, 2300, 560, 1750, ...]

不需要在每筆資料中跳過字串、日期或其他欄位。

這樣的排列也有利於 Query Engine 以一批資料為單位處理,而不是一次只處理一列資料。後面介紹 Apache Arrow 的 Array 與 RecordBatch 時,我們會再深入看到這種欄式記憶體格式如何運作。


為什麼欄式資料通常更容易壓縮?

同一欄位中的資料型別相同,而且常常具有相似性。

例如:

city: [台北, 台北, 台北, 台中, 台中, ...]

或:

is_member: [true, true, true, false, false, ...]

這些連續且相似的值,通常比混合整數、字串、日期與布林值的一整列資料更容易壓縮。

因此,像 Parquet 這類欄式檔案格式通常能同時做到:

  • 降低儲存空間
  • 減少讀取的資料量
  • 加速分析型查詢

這也和 Day 6 的內容呼應:Parquet 會以欄位儲存資料,並搭配 Metadata 與統計資訊,協助 Query Engine 跳過不可能符合條件的資料區塊。


Row-based 與 Columnar 的差異

面向 Row-based Columnar
資料排列方式 一筆完整資料放在一起 同一欄位的值放在一起
適合工作 交易、單筆查詢、頻繁更新 分析、聚合、大量掃描
取得完整一筆資料 通常較直接 需要從多個欄位組合
只讀少數欄位 可能讀到額外資料 通常更有效率
壓縮效果 通常較不集中 通常較好
常見場景 OLTP OLAP、資料倉儲、資料湖

可以先記住一句話:

Row-based 適合「看一筆完整資料」。
Columnar 適合「看大量資料中的少數欄位」。

DataFusion、Arrow 與 Parquet 的關係

目前我們已經接觸到三個概念:

Parquet
└── 讓資料以欄式格式儲存在檔案中

Apache Arrow
└── 讓資料以欄式格式存在記憶體中

Apache DataFusion
└── 使用 Arrow 執行 SQL、規劃與最佳化查詢的 Query Engine

可以把整個資料流程先想成:

Parquet File
     ↓
讀取需要的欄位與資料區塊
     ↓
Arrow RecordBatch
     ↓
DataFusion 執行 Filter / Projection / Aggregate / Join
     ↓
Query Result

Parquet 關注資料如何儲存在檔案中;Arrow 關注資料如何在記憶體中表示;DataFusion 則負責規劃與執行查詢。


欄式資料不是萬用解法

欄式資料很適合分析,但不代表任何系統都應該使用欄式格式。

若系統需要快速取得或修改一筆訂單的完整內容,例如:

訂單資訊
付款狀態
商品內容
收件地址
物流狀態

那麼 Row-based 通常更自然。

資料工程中一個重要觀念是:

先理解工作負載,再選擇資料格式與儲存方式。

如果主要需求是大量掃描、篩選、聚合與報表分析,欄式格式通常是合理選擇;如果主要需求是單筆讀寫、即時交易與頻繁更新,則交易型資料庫的列式儲存更常見。


今日小結

今天先從結論出發:分析系統偏好欄式資料,不是因為它在所有情境下都比較快,而是因為分析查詢通常需要處理大量資料,卻只使用少數欄位。

欄式資料能讓 Query Engine:

  • 只讀取需要的欄位
  • 更有效率地執行篩選與聚合
  • 利用同欄位資料的連續性
  • 取得較好的壓縮效果

今天最重要的一句話是:

資料如何排列,會影響 Query Engine 要讀多少資料,以及能否有效率地完成查詢。

明天會繼續進入記憶體中的欄式資料結構,認識 Apache Arrow,以及 DataFusion 為什麼以 Arrow 作為資料處理基礎。


延伸閱讀


上一篇
Day 6|為分析而生的 Parquet
下一篇
Day 8|認識 Apache Arrow:DataFusion 的資料基礎
系列文
深入 SQL 查詢引擎:30 天用 Rust 與 Apache DataFusion 解構資料處理流程9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言