分析系統偏好欄式資料(Columnar),主要是因為分析查詢常常會:
WHERE、GROUP BY、SUM、AVG。欄式資料會把同一欄位的值集中存放。當 SQL 只需要 city、amount 與 is_member 時,查詢引擎可以避免讀取不需要的 order_id,減少 I/O 與記憶體搬移。
不過,欄式資料不是所有情況下都比較快。若系統經常需要新增、修改或讀取單筆完整紀錄,Row-based 的資料排列通常更合適。
今天的重點是:資料如何排列,會直接影響 Query Engine 要讀多少資料,以及如何處理查詢。
假設有一張訂單表:
| order_id | city | amount | is_member |
|---|---|---|---|
| 1 | 台北 | 1200 | true |
| 2 | 台中 | 850 | false |
| 3 | 台北 | 2300 | true |
這張表在畫面上看起來是一列一筆訂單;但資料真正寫入檔案或放入記憶體時,可以採用不同的排列方式。

圖 1:Row-based 與 Columnar 的資料排列差異。欄式資料能只讀取查詢需要的欄位,減少不必要的 I/O。圖由本文整理。
接下來分別看看兩種排列方式。
Row-based 又可稱為列式資料。它會把同一筆資料的所有欄位放在一起:
[1, 台北, 1200, true]
[2, 台中, 850, false]
[3, 台北, 2300, true]
如果系統想取得第 2 筆訂單,便可以直接讀取這筆完整紀錄:
[2, 台中, 850, false]
因此,Row-based 通常適合:
例如購物網站建立訂單時,系統通常需要同時處理訂單編號、商品、金額、付款狀態與收件資訊。這類情境重視的是「快速取得一筆完整資料」。
這種工作負載常被稱為 OLTP(Online Transaction Processing,線上交易處理)。
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 |
|---|---|---|
| 資料排列方式 | 一筆完整資料放在一起 | 同一欄位的值放在一起 |
| 適合工作 | 交易、單筆查詢、頻繁更新 | 分析、聚合、大量掃描 |
| 取得完整一筆資料 | 通常較直接 | 需要從多個欄位組合 |
| 只讀少數欄位 | 可能讀到額外資料 | 通常更有效率 |
| 壓縮效果 | 通常較不集中 | 通常較好 |
| 常見場景 | OLTP | OLAP、資料倉儲、資料湖 |
可以先記住一句話:
Row-based 適合「看一筆完整資料」。
Columnar 適合「看大量資料中的少數欄位」。
目前我們已經接觸到三個概念:
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 作為資料處理基礎。