【技術筆記】Npgsql 的 CommandTimeout 預設為 30 秒:大量資料查詢失敗的原因竟然是這個

| SQL | 9 Reads

最近在開發一個工具,從 PostgreSQL 匯出大量資料為 CSV。功能本身很單純,平時匯出幾百筆資料沒問題,但當資料筆數上升到幾萬筆時,程式卻開始失敗,並在中途中斷。

初期無法得知錯誤具體原因,只知道程式落入了 catch (Exception) 分支。經過一番調查與錯誤記錄強化,最終找到關鍵錯誤訊息:

Exception while reading from stream
 ---> System.TimeoutException: Timeout during reading attempt

這個訊息揭示了一個不容易注意的關鍵點:

Npgsql 的 CommandTimeout 預設值為 30 秒。

也就是說,當查詢執行與讀取結果的整體耗時超過 30 秒時,Npgsql 會主動取消操作,並拋出 TimeoutException。


什麼是 CommandTimeout?

CommandTimeout 是 Npgsql 的一個屬性,控制每條 SQL 指令允許執行與讀取結果的最長時間。預設值為 30 秒。

這個限制在多數情況下不會造成問題,但當:

  • 查詢本身較為複雜(例如使用多表 JOIN、排序、ROW_NUMBER 等)

  • 資料筆數較多

  • 匯出流程每次查詢一部分資料(分頁)

這些因素疊加起來,很容易讓單次查詢時間超過 30 秒,導致操作被中斷。


解法:明確設定較長的逾時時間

要解決這個問題,只需要在連線字串中加入:

Command Timeout=300;

這表示允許單一 SQL 指令最多執行 300 秒(5 分鐘),就能有效避免逾時錯誤。

範例設定:

{
  "ConnectionStrings": {
    "PostgresLocal": "Host=localhost;Port=5432;Username=admin;Password=admin;Database=exampledb;Command Timeout=300;"
  }
}

若你的程式會動態產生連線字串,也可以用 NpgsqlConnectionStringBuilder 來設定:

var builder = new NpgsqlConnectionStringBuilder(baseConn)
{
    CommandTimeout = 300
};
var conn = new NpgsqlConnection(builder.ConnectionString);

額外提醒:CommandTimeout ≠ ConnectionTimeout

  • CommandTimeout:控制單一指令(查詢 + 讀取結果)的最大等待時間,預設 30 秒

  • ConnectionTimeout:控制建立連線所需的最大時間,預設 15 秒

兩者常被混淆,但用途與風險完全不同。


小結

這次經驗讓我牢記一件事:

Npgsql 的 CommandTimeout 預設是 30 秒,若未手動設定,查詢量大時極易觸發 TimeoutException。

實務上,建議針對批次查詢、匯出等場景都明確設定一個足夠的 CommandTimeout,避免程式在資料量變大時才暴露問題。


如你也遇到類似 PostgreSQL 匯出逾時問題,希望這篇筆記對你有所幫助。若你對進一步優化匯出效能(例如 COPY TO STDOUT)有興趣,也歡迎留言討論。

This article was last edited at