更新紀錄(僅初版,2026年07月29日 發布)
- 初次發布
引用本文(DOI(已登錄存檔): 10.5281/zenodo.22175288)
以下 DOI 指向先前登錄的存檔,內容可能與目前正文不同。引用目前正文時,請使用本頁網址。
Go Komura(2026)。〈MSMQ 還能用多久 ── 「連淘汰都稱不上」的遺留佇列遷移判斷〉。小村軟體有限公司。 https://comcomponent.com/zh-TW/blog/msmq-migration-decision-guide/
- DOI(已登錄存檔)
- 10.5281/zenodo.22175288
- DOI(上次登錄版本)
- 10.5281/zenodo.22175289
「聽說 MSMQ 已經廢除了。現在還在運作的系統,是不是也得馬上遷移?」──在既有系統的維護上,首先必須把這一點釐清。
MSMQ(Microsoft Message Queuing)作為作業系統功能是否存續,與從 .NET 使用它的 API 是否受支援,是兩回事。在本文的基準時點 2026 年 7 月,MSMQ 並未列入官方的淘汰清單,仍以 Windows 選用功能的形式保留著。另一方面,標準函式庫 System.Messaging 只支援 .NET Framework,並未移植到 .NET(Core 以後)。123
也就是說,問題不在於「明天 MSMQ 就會停掉」,而在於「把應用程式遷移到 .NET 時,佇列的實作會成為遷移的阻礙」。
本文寫給負責維護與遷移 MSMQ 系統的開發者,以及決定方針的資訊系統部門。先確認事實關係與繼續使用的條件,再進入盤點、遷移目標的選定、資料表佇列的實作與切換程序。
從 VB6 或 .NET Framework 遷移的整體做法,請參考「VB6 遷移到 .NET 的實務做法」「從 .NET Framework 遷移到 .NET 的著手前檢核表」。本文只聚焦佇列的部分。
1. 先講結論:把作業系統的存續與應用程式的遷移分開看
要把應用程式遷移到 .NET,原則上佇列也要一起遷移。如果短期內仍以 .NET Framework 運作,那麼在確認作業系統的支援期限與維運條件之後,可以選擇繼續使用。不建議在新專案採用 MSMQ。4
從你現在要決定的事情讀起
| 現在要決定的事 | 判斷的出發點 | 說明 |
|---|---|---|
| 「已經廢除」的說法是真的嗎 | 把作業系統功能與受管理 API 的狀況分開看 | 第 1 章:事實關係 |
| 短期內能不能就這樣繼續用 | 除了作業系統的支援期限,還要確認能否備齊監控、復原與交接 | 第 2 章:繼續使用的條件 |
| 想隨著 .NET 遷移一起替換掉 | 盤點依賴、訊息格式、DTC 與離線耐受性 | 第 3 章:盤點、第 4 章:遷移目標 |
| 能不能用 P/Invoke 或 CoreWCF 保留下來 | 把「呼叫得通」和「扛得住維護負擔」分開看 | 1.3 節:受管理 API 與 P/Invoke、1.4 節:CoreWCF |
| 要選 RabbitMQ 還是 Azure Service Bus | 在這兩個選項之前,先評估同一個業務資料庫上的資料表佇列夠不夠用 | 第 4 章:依需求選型、第 5 章:資料表佇列 |
| 想把取出用的 SQL 搬到自己的資料庫上 | 確認隔離等級、快照設定與鎖定提示 | 5.3 節:SQL 與資料庫設定 |
| 想維持佇列的處理順序 | 把取出順序與寫入業務資料的順序分開看 | 5.4 節:平行處理與順序 |
| 想把工作處理程序放進實際維運 | 確認交易邊界,以及回收、重試與清理 | 5.5 節:工作處理程序、5.6 節:維運 |
| 運作中的系統該怎麼切換 | 決定訊息格式、接收端、重複對策與殘留訊息的處理 | 第 6 章:切換程序 |
如果要通讀全文,順序是:確認存續 → 繼續使用的條件 → 盤點 → 遷移目標 → 實作 → 切換。回頭檢視判斷時,請參考第 7 章的總結。
1.1. 先確認在談哪一層
「MSMQ 還能不能用」的答案,會依層級而不同。狀態的基準時點與開頭相同,都是 2026 年 7 月。
| 層級 | 目前的狀態 | 實務上的意義 |
|---|---|---|
| 作業系統功能(Windows 的選用功能) | 存續。隨附於現行的 Windows 用戶端/Server,也沒有發布淘汰宣告12 | 只要啟用,現在依然能運作。在作業系統的支援期限內可以繼續使用 |
Win32 原生 API(MQSendMessage 等) |
有文件,可以使用5 | 從 .NET 以 P/Invoke 呼叫的路仍然存在。但格式器與交易整合要自己寫、自己維護 |
| .NET Framework 的 System.Messaging | 可以使用。但涵蓋範圍只到 .NET Framework 1.1~4.8.13 | 既有系統就跑在這裡。再往後沒有下文 |
| .NET(Core 以後)的官方受管理 API | 不存在。Windows 相容性套件中也沒有收錄6 | 把應用程式升級到 .NET 的那一刻,佇列部分就必須重做 |
| WCF 的 MSMQ 繫結 → CoreWCF.MSMQ | 存在社群主導的移植版本。CoreWCF 本身有支援政策,但 MSMQ 實作依賴 System.Messaging 的社群移植版本7 | 可以用來讓透過佇列被呼叫的 WCF 服務(接收端)延續使用,但既不是傳送端的替代,也不是通用的佇列 API |
| 新專案採用 | 不建議 | 因為沒有官方的未來路徑 |
1.2. 沒有被淘汰的是作業系統功能
Windows 用戶端的「Deprecated features」中列有 NTLM、VBScript、WordPad 等項目,卻沒有 MSMQ 這一項。Windows Server 的「Features Removed or No Longer Developed」也一樣,包含 Windows Server 2025 的分頁在內都沒有。12
淘汰(deprecated)是官方的一個階段,表示已結束積極開發、未來有可能被移除,與移除(removed)不同。就 MSMQ 而言,連這個淘汰宣告都還沒有發布,這就是本文所確認到的狀況。
1.3. 阻礙遷移到 .NET 的是受管理 API
System.Messaging.MessageQueue 的參考文件所涵蓋的是 .NET Framework 1.1~4.8.1,沒有 .NET(Core 以後)的版本。Windows 相容性套件(Microsoft.Windows.Compatibility)提供登錄檔、WMI、Windows 服務、EventLog 等約兩萬個 API,但不包含 System.Messaging。36
要用 P/Invoke 保留,就得連包裝層的維護一起扛下來
並不是連原生 API 都沒有了。MQSendMessage、MQReceiveMessage 等 Win32 API,從 .NET 以 P/Invoke 呼叫本身是可行的。但原本由 System.Messaging 承擔的格式器與交易整合,都要自己寫成包裝層並持續維護。這不是遷移的首選,而是無論如何都要保留 MSMQ 時的延續使用手段。5
1.4. 把 CoreWCF 當成「WCF 接收端」的路徑來判斷
.NET Framework 的 WCF 有使用 MSMQ 的繫結。要在 .NET 上代管它,路徑就是 CoreWCF 的 MSMQ 傳輸(CoreWCF.MSMQ)。7
不過,它的用途是透過佇列被呼叫的 WCF 服務的接收端。它既不是可以取代 System.Messaging 的通用佇列 API,也不是傳送端用戶端的替代品。
CoreWCF 本身有 Microsoft 的支援政策。另一方面,MSMQ 傳輸的實作依賴 .NET Framework 版 System.Messaging 的社群移植版本,不能把這部分依賴視為享有同樣的保證。採用前要確認所用版本是否在支援範圍內、依賴部分由誰維護。7
由此可見,「MSMQ 已經廢除」和「無法原樣遷移到 .NET」不是同一件事。選擇 P/Invoke 或 CoreWCF,等於把佇列的替換往後延,代價是扛下這條路徑的維護負擔。
圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 28 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle
2. 是否繼續使用:由 .NET 遷移計畫與維運體制來決定
2.1. 為什麼光憑「還在動」無法判斷
.NET Framework 4.8 作為 Windows 的元件,依所安裝之作業系統的生命週期受到支援。因此,在支援期限內持續運作的計畫是可以擬定的。4
要確認的不只是能不能運作。如果因為依賴 MSMQ 而讓整個應用程式留在 .NET Framework 上,下列維護與遷移上的負擔也會一併留下。
| 負擔 | 對遷移判斷的影響 |
|---|---|
| 維護人員的確保與交接 | 能說明 System.Messaging 與 DTC 的技術人員越來越少,重新調查組態的成本也隨之增加 |
| 執行環境與函式庫的限制 | 有些新的 C# 語法只要更新編譯器就能用,但執行環境的效能改善與新的標準函式庫用不上。把 .NET Framework 排除在支援範圍外的套件也在增加 |
| 對 DTC 的依賴 | 如果保留「把佇列取出與資料庫更新一起確定」的部分,就算周邊都 .NET 化了,最難的一關還是會留到最後 |
| 舊的序列化格式 | BinaryFormatter 的實作已在 .NET 9 從執行環境中移除,必須連格式一起遷移8 |
維護人員流失的問題,在「沒有原始碼也沒有文件的系統該怎麼維護」中也討論過。MSMQ 的諮詢之所以多半不是從故障處理、而是從遷移估算開始,正是因為真正的問題不在能不能運作,而在這類依賴與交接。
「作業系統還在支援期內就能運作」和「越等越容易遷移」是兩回事。即使短期內不遷移,也要先把難關盤點出來。
2.2. 短期內不遷移,就要滿足 4 個最低條件
沒有把應用程式升級到 .NET 的計畫,從成本效益看短期內也不打算改動,也就是所謂的「擱置不動」,在有條件的前提下是合理的。但前提是下列 4 項全部滿足。
| 確認 | 最低條件 | 具體要做的事 | 不滿足會發生什麼 |
|---|---|---|---|
| □ | 在裝機與還原程序中明確記載 MSMQ | MSMQ 是 Windows 的選用功能。把「開啟或關閉 Windows 功能」或以 DISM/PowerShell 啟用的步驟寫進環境建置手冊 | 更換電腦與伺服器時忘了啟用,遷移當天原因不明地跑不起來 |
| □ | 監控佇列長度 | 對堆積筆數設定門檻監控與通知。無法送達佇列與日誌佇列也要納入對象 | 接收端停了也不會出錯,訊息只會一直堆積,業務在無人察覺中停擺 |
| □ | 把組態文件化 | 記錄佇列路徑、權限、是否為交易佇列、格式器、日誌佇列設定(3.2 節的盤點表可以直接拿來用) | 將來的遷移估算又要從調查重來一次,準確度與工時都會變差 |
| □ | 每年重新檢視一次判斷 | 每年確認「是否被列入淘汰清單」「作業系統更新後行為有沒有改變」 | 等淘汰公告出來才手忙腳亂地行動 |
其中年度檢視,是為了不漏掉淘汰公告與作業系統更新影響的程序。為因應負責人異動,不只留下組態,還要留下復原程序。這些條件沒有補齊就維持現狀,就等於承擔將來既察覺不到系統已經停擺、也查不出原因的風險。
3. 盤點:記錄你把什麼交給了 MSMQ
3.1. 掌握功能與術語
在 MSMQ 中,傳送端把訊息寫進佇列,同一台機器或另一台機器上的應用程式再在方便的時機取出。需要讓遷移目標接手的性質,主要有下列 3 項。
傳送目標停了也能先累積:store-and-forward
即使傳送目標停止運作,訊息也會先累積在本機,等恢復之後再送達。這在不穩定的據點間線路上很有用,但它並不無條件保證跨重新啟動的持久性。
把佇列與資料庫更新一起確定:交易
佇列操作可以納入交易;搭配 MS DTC(分散式交易協調器)之後,可以把從佇列取出與資料庫更新放進一個分散式交易中一起確定。
不必導入額外中介軟體就能用:隨作業系統附帶
因為不需要額外導入中介軟體,2000 年代的訂單收發連動、報表的非同步處理、製造現場的工序間連動等場景都廣泛使用了它。這 3 項的組合,正是它至今仍然留存的原因。
用術語區分累積、持久化與交易
判斷表中用到的術語,也在這裡統一。9
| 術語 | 含義,以及需要確認它的理由 |
|---|---|
| 私人佇列/公用佇列 | 私人佇列只登錄在本機電腦上,不會發布到 Active Directory。以 .\private$\佇列名稱 這樣的形式指定。公用佇列會登錄到目錄服務中,可以在網域內搜尋到。中小規模的業務系統多半使用私人佇列 |
| express 訊息 | 預設的傳送模式。傳遞中與傳遞後都放在記憶體裡,因此速度快,但所在的電腦或 MSMQ 服務一停止就會遺失 |
| recoverable(可復原)訊息 | 在傳送端、中繼節點與目的佇列上都保存到磁碟,可以跨重新啟動保留下來。需要在傳送端明確指定 |
| 交易佇列 | 只處理交易訊息。這個屬性在建立佇列時就已決定,之後無法變更。交易訊息會持久化到磁碟 |
| DTC | 協調跨越 MSMQ 與資料庫等多個資源之交易的 Windows 服務。佇列與資料庫的一致性該如何取代,是遷移時的爭論焦點 |
| 日誌佇列/無法送達佇列 | MSMQ 產生的系統佇列。前者保存已傳送、已取出訊息的副本,後者保存無法送達的訊息。要監控放著不管所造成的容量增長 |
「接收端停了也能累積」和「MSMQ 或機器重新啟動後訊息仍在」是兩回事。要分開調查:只用到了前者,還是也需要由 recoverable 訊息或交易訊息提供的後者。
3.2. 從程式碼、組態與維運中找出依賴
不要只看函式庫的參考就結束盤點
先找 System.Messaging 的參考與 MessageQueue 的建立位置。但光憑那裡沒有參考,還不能斷定「沒有使用 MSMQ」。要依呼叫路徑分別確認。
| 呼叫路徑 | 在程式碼與組態中要找的東西 |
|---|---|
| .NET Framework 的函式庫 | System.Messaging 的參考、MessageQueue 的建立位置 |
| WCF 的組態 | netMsmqBinding / msmqIntegrationBinding |
| 原生 API 與 COM | MQSendMessage 等 MQ* 原生函式、透過 COM 的呼叫 |
逐一為每個佇列留下遷移判斷的依據
用下面的表,逐一記錄每個佇列的確認結果。目的不是確認完就結束,而是把它留存為遷移方針與簽核資料的依據。
| 觀察點 | 看哪裡 | 記錄什麼 | 對判斷的作用 |
|---|---|---|---|
| (a)佇列路徑 | 傳給 MessageQueue 的路徑字串、組態檔中的連線目標、FormatName: 指定 |
本機還是遠端、私人還是公用、對方的機器名稱 | 若是遠端或跨據點,就據此判斷是否需要 store-and-forward(4.2 節的「離線耐受性」那一列)。若在本機就能完結,則落在 4.2 節的第 1 列 |
| (b)交易與 recoverable | 佇列建立位置(是不是交易佇列)、傳送時是否指定了 recoverable、是否使用了 DTC | 是不是交易佇列、有沒有指定 recoverable、是否參與 DTC | 有用 DTC,遷移目標幾乎就確定是資料表佇列。兩者都沒有時,要寫明現行系統是以「重新啟動就會遺失訊息」為前提在運作的 |
| (c)格式器 | XmlMessageFormatter / BinaryMessageFormatter / ActiveXMessageFormatter 的指定位置 |
使用的格式器,以及訊息本體的型別 | 屬於 BinaryFormatter 系列就必須換成 JSON(6.1 節)。這會成為遷移工時的主要來源8 |
| (d)日誌佇列與無法送達佇列 | 佇列的屬性(是否啟用日誌佇列)、系統佇列裡的內容、維運手冊 | 是否使用日誌佇列、是否真的在看無法送達佇列 | 「失敗訊息怎麼處理」會成為遷移目標的設計需求(用資料表佇列的話就是轉存資料表) |
| (e)誰在建立與刪除 | 安裝程式、部署指令碼、應用程式啟動時的 Create 呼叫 |
建立的主體,以及由誰設定權限 | 遷移時「新佇列由誰準備」就是它的直接對應。選擇擱置不動時,要把它轉寫進裝機程序(第 2 章) |
4. 遷移目標:不看後繼產品的名稱,而看需要的性質
4.1. 用 4 個問題整理需求
根據盤點結果,確認下列 4 點。
- 接收端做的是不是更新自家業務資料庫的處理。
- 交易佇列與資料庫更新,是不是用 DTC 一起確定的。
- 傳送端與接收端是否在不同機器、不同據點。是否真的用到了 store-and-forward。
- 訊息量有多大。若是多數業務系統那種一天數千到數萬筆的規模,光看候選方案的處理能力很難成為決定因素。
4.2. 依性質給出的首選
如果是更新同一系統業務資料庫的非同步作業,先考慮資料表佇列。只有在出現向多個系統傳遞、路由等資料表佇列滿足不了的需求時,才選擇訊息代理。
| 用到的性質 | 首選 | 理由與注意事項 |
|---|---|---|
| 同一系統內的非同步處理(接收端更新資料庫) | 資料庫的資料表佇列 | 可以把「取出訊息」與「更新業務資料」放進同一個本機交易中確定,不再需要 DTC。既有的備份、監控與維運可以直接沿用。另外,傳送端也可以把「更新業務資料與新增訊息資料列」放進同一個交易,這就是所謂的 Outbox 模式 |
| 用 DTC 讓佇列與資料庫更新維持不可分割 | 資料庫的資料表佇列 | 把分散式交易換成本機交易才是這次遷移的本質。雲端佇列無法參與 DTC,只要還有這項需求,走訊息代理就是繞遠路 |
| 多系統、多語言之間鬆散耦合的連動 | RabbitMQ(地端)/Azure Service Bus(可放在雲端) | 傳遞的扇出與路由是訊息代理的強項。選 RabbitMQ 要把自行維運的負擔(備援、更新)估進去 |
| 據點間的離線耐受性(store-and-forward) | Azure Service Bus 等+重送設計,或據點端資料表佇列+同步 | 能透明取代 MSMQ 那種「先存在傳送端本機、之後再送達」的機制並不多。要改成由應用程式明確承擔在傳送端暫存這項職責的設計 |
| 同一處理程序內的生產者/消費者 | .NET 的 Channels 等記憶體內佇列 | 這類情況原本就不需要跨處理程序的佇列。在處理程序內完結即可,需要持久性再轉向資料表佇列 |
| 只當成 Windows 服務之間的處理程序間通訊來用 | 具名管道等 IPC | 但能這樣取代的只有雙方始終同時運作的情況。接收端停止期間,MSMQ(即使是 express 訊息)仍會接受傳送並在之後送達,而管道會立即失敗。如果依賴停止期間的累積,就要在應用程式端實作重送與緩衝,或者保留佇列。選法請見「Windows 的 IPC 判斷表」 |
為什麼先考慮資料表佇列
這裡說「先考慮資料表佇列」,並不是因為想在任何用途上都推薦資料庫。而是因為在 MSMQ 世代的業務系統中常見的更新同一個資料庫的非同步處理裡,用資料表佇列既能單純地保持一致性,又能活用既有的備份、監控與維運。
適合訊息代理的需求,以及要額外設計的部分
RabbitMQ 與 Azure Service Bus 適合多系統、多語言的鬆散耦合傳遞、扇出與路由。需要高吞吐量時也可以納入考慮。不過,由於佇列與業務資料庫成了不同的資源,不能把 MSMQ+DTC 的不可分割性原樣搬過去,而要在應用程式端設計冪等性與重送。新中介軟體的監控、備援、修補與人員教育也要算進估算裡。
就算遷移,也不代表停止期間的累積可以直接拿掉
請確認離線耐受性與接收端停止期間的累積,是不是可以拿掉的需求。選了雲端佇列,也不會自動附帶傳送端本機的累積;換成具名管道,停止期間的待接收也不會以同樣的形式保留下來。有需要的話,就在應用程式端加上重送與緩衝,或者把佇列保留下來。
5. 資料表佇列:把佇列操作與業務更新放在同一個資料庫中確定
本章先確認把兩者放進同一個資料庫的意義。在此基礎上,依序來看取出 SQL 的前提、順序的保證範圍、工作處理程序的交易、實際維運中要補上的處理。
5.1. 為什麼可以不再需要 DTC
在 MSMQ 中,佇列與資料庫是不同的資源。要把「已從佇列取出」與「已更新業務資料」一起確定,就需要由 DTC 支撐的分散式交易。
把佇列改成與業務資料同一個資料庫中的資料表之後,兩者都變成對該資料庫的更新。取出、業務更新與完成記錄可以用一個本機交易確定。把分散式交易換成本機交易,正是這次遷移的核心。
不只接收端,傳送端也能同時確定
在傳送端,也可以把業務資料的更新與訊息資料列的 INSERT 放進同一個交易。這就是 Outbox 模式,可以防止「只更新了業務資料,訊息卻沒有送出」這種不一致。
下面是 SQL Server 上的最小形式。C# 的部分是用來表示交易邊界的虛擬碼,不是可以直接部署的完整實作。
5.2. 儲存作業的資料表
CREATE TABLE dbo.JobQueue (
Id BIGINT IDENTITY(1,1) PRIMARY KEY,
Payload NVARCHAR(MAX) NOT NULL, -- 訊息內容。以 JSON 保存
Status TINYINT NOT NULL DEFAULT 0, -- 0:未處理 1:處理中 2:已完成
EnqueuedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(),
StartedAt DATETIME2 NULL, -- 用來回收停在「處理中」的資料列
RetryCount INT NOT NULL DEFAULT 0
);
CREATE INDEX IX_JobQueue_Status ON dbo.JobQueue (Status, Id);
Payload 中放 JSON 格式的訊息內容,用 Status 區分未處理、處理中與已完成。StartedAt 與 RetryCount 用於回收與重試的維運。
5.3. 取出一筆,以及資料庫設定的確認
用一段 SQL 同時完成取出資料列的更新與訊息內容的取得
取出時,一邊把一筆更新為「處理中」,一邊用 OUTPUT 接收訊息內容。有了 READPAST,就能跳過被其他工作處理程序鎖住資料列的候選而不必等待,用多個處理程序平行處理。10
WITH next_job AS (
SELECT TOP (1) *
-- READCOMMITTEDLOCK 在 READ_COMMITTED_SNAPSHOT 為 ON 的資料庫上是必要的(後述)。
-- 在 OFF 的資料庫上它與預設行為相同,所以一直帶著就能同時涵蓋兩種情況
FROM dbo.JobQueue WITH (READPAST, UPDLOCK, READCOMMITTEDLOCK)
WHERE Status = 0
ORDER BY Id -- 依投入順序取出。這是它成為「佇列」的條件
)
UPDATE next_job
SET Status = 1, StartedAt = SYSUTCDATETIME()
OUTPUT inserted.Id, inserted.Payload;
先確認快照設定與隔離等級
讀這段 SQL 時,要確認 READ_COMMITTED_SNAPSHOT 與工作階段的隔離等級。
官方文件指出,當資料庫的 READ_COMMITTED_SNAPSHOT 為 ON,且工作階段為 READ COMMITTED,或同時使用了 READCOMMITTED 提示時,就不能直接指定 READPAST。處理方式是:如果有 READCOMMITTED 提示就移除它,並加入 READCOMMITTEDLOCK。10
預設的隔離等級就是 READ COMMITTED,因此不做任何處理就把它搬到啟用了快照的資料庫上,結果不是單純被封鎖,而是SQL 陳述式直接出錯。Azure SQL Database 預設為 ON。地端的資料庫也可能為了緩解讀取封鎖而啟用了它,所以要先查清楚。
SELECT is_read_committed_snapshot_on FROM sys.databases WHERE name = DB_NAME();
上面的取出 SQL 加了 READCOMMITTEDLOCK,不論快照設定為何都使用以鎖定為基礎的讀取。在 OFF 的資料庫上,它與預設行為相同。10
不一併寫上 ROWLOCK 的理由,以及 READPAST 的極限
另一方面,與常見的 WITH (READPAST, UPDLOCK, ROWLOCK) 不同,這裡沒有一併寫上 ROWLOCK。因為ROWLOCK 與 READCOMMITTEDLOCK 屬於同一組粒度提示,同一張資料表上不能同時指定兩者。10
READPAST 能跳過的只有資料列鎖定,跳不過分頁鎖定。像本例這樣用 TOP (1) 的索引搜尋取一列的範圍內,鎖定擴大實際上不成問題;但如果想明確寫上 ROWLOCK,就要在確認 READ_COMMITTED_SNAPSHOT 為 OFF 之後,改成把 READCOMMITTEDLOCK 拿掉。
5.4. 取出順序與寫入業務資料的順序是兩回事
要為取出的候選指定排序
省略 ORDER BY 寫成 UPDATE TOP (1),會選中哪一列是不確定的。UPDATE 的 TOP 不會為目標資料列排序,光是存在 (Status, Id) 索引也不構成順序保證。為了避免舊作業一直被往後排,範例中指定了 ORDER BY Id。11
平行處理時先取出的未必先生效
不過,平行處理的完成順序是對不齊的。工作處理程序 A 在處理作業 1 期間,工作處理程序 B 會用 READPAST 跳過那一列,先把作業 2 提交。
| 能對齊的 | 對不齊的 |
|---|---|
哪一列先被取出(ORDER BY Id) |
哪一列先寫入業務資料 |
如果像對同一筆訂單編號的更新那樣,寫入順序本身有意義,就要在下面兩種做法中選一種。
| 保證順序的方法 | 得到的性質與付出的代價 |
|---|---|
| 只留一個消費者 | 捨棄平行度換取順序。只要還能滿足所需的吞吐量,這是最單純的做法 |
| 依索引鍵拆分 | 依訂單編號等把工作處理程序固定下來,或加上「同一個鍵正在處理中就不取出」的條件。保證的是同一個鍵內部的順序,跨鍵的順序不保證 |
兩種都不選,就要在設計中寫明平行處理不保證整體的 FIFO。在 MSMQ 上平行接收也會出現同樣的問題。重要的是,不要帶著「既然是佇列就一定照順序」的理解去做遷移。
5.5. 工作處理程序的交易邊界
接收端要對齊的,是取出、業務資料更新與完成記錄這三者的交易。下面的虛擬碼把這三項處理交給同一個連線與交易,最後一起確定。
while (!stoppingToken.IsCancellationRequested)
{
using var tx = connection.BeginTransaction();
var job = DequeueOne(connection, tx); // 上面的 UPDATE ... OUTPUT
if (job is null)
{
tx.Commit();
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken); // 空的時候只等待一個輪詢間隔
continue;
}
ApplyBusinessData(connection, tx, job); // ← 更新業務資料(真正想做的工作)
MarkDone(connection, tx, job.Id); // Status = 2
tx.Commit(); // 「取出」與「業務更新」在這一行同時確定
}
要點在於:DequeueOne、ApplyBusinessData、MarkDone 使用同一個連線與交易,最後的 Commit 把取出與業務更新一起確定。換成訊息代理時,同一個資料庫的這項性質就不存在了,必須另行設計一致性方案。
5.6. 實際維運中要補上的回收、重試與清理
在最小範例之外,還要設計下列維運處理。
| 維運項目 | 處理內容 |
|---|---|
| 回收停在「處理中」的資料列 | 把 Status = 1 且 StartedAt 早於一定時間的資料列改回 Status = 0 的復原處理 |
| 重試上限與轉存 | 把 RetryCount 超過上限的資料列移到別的資料表,或改成 Status = 9 之類。相當於 MSMQ 無法送達佇列的存放處 |
| 清理已完成的資料列 | 定期刪除或封存 Status = 2 的資料列,防止資料表與索引膨脹 |
在中小規模的業務系統中,用資料表加輪詢、或加上通知的形式,往往就夠用了。在增加一套佇列產品之前,先確認這種實作能否滿足所需的性質與維運要求。
6. 切換程序:先把格式、行為與接收端對齊
切換時,先把訊息格式以及輸入與結果的測試對齊。之後再選擇是用橋接連通新舊,還是把舊佇列清空後切換。
6.1. 決定新舊能夠並存的訊息格式
遷移後預設使用 JSON。不要帶過去的是 BinaryMessageFormatter 這類BinaryFormatter 系列的序列化。BinaryFormatter 的實作已在 .NET 9 從執行環境中移除,從安全性角度也不建議使用。8
另一方面,並不是所有二進位格式都不行。像 Protocol Buffers、MessagePack 這種規格由獨立一方維護的格式,直接把它帶進新系統本身沒有問題。先在 3.2 節的盤點中確認格式器與訊息內容的型別。
6.2. 改寫之前,先用測試把輸入與結果固定下來
佇列處理是容易出現時序相關缺陷的部分。先準備好「給這則輸入訊息,就會得到這個結果」的特性化測試,讓替換之後也能機械地確認結果一致。也請參考「用特性化測試固定行為之後再重構」。
6.3. 先從接收端做起,橋接要以會遺失、會重複為前提來設計
先讓接收端支援新佇列
先準備好新佇列,讓接收端支援它,再切換傳送端。
中間加一個把訊息從舊 MSMQ 搬到新佇列的小型橋接程式,就不必一次切換所有傳送端。橋接程式本身留在 .NET Framework 上也沒關係。
依有無交易區分交接的程序
不過,如果在從 MSMQ 取出之後、寫入新佇列之前停止運作,就會發生遺失或重複傳遞。要依來源佇列的種類,按下面的方式設計。
| 遷移來源 | 最低限度必要的交接方式 |
|---|---|
| 交易佇列 | 把 MSMQ 端改為交易接收,寫入失敗時可以退回。新佇列這端依訊息 ID 擋掉重複,做冪等處理 |
| 非交易佇列 | 用 Peek 讀取 → 冪等地寫入新佇列 → 確認寫入成功後再從舊佇列移除。移除之前停止運作所造成的重複,由新佇列這端吸收 |
佇列的交易屬性在建立時就已決定,之後無法變更。請注意,不能把交易接收的程序原樣用在非交易佇列上。
6.4. 橋接不划算,就清空再切換
如果為橋接所做的遺失與重複對策與系統規模不相稱,就不要勉強讓新舊並存,而是轉向以計畫停機把舊佇列清空之後再切換的做法。
留著訊息就切換,會造成重複處理與遺失。把佇列清空再切換,結果往往是最安全也最快的做法──這是本文基於實務給出的判斷。
7. 總結
在本文的基準時點 2026 年 7 月,MSMQ 尚未被官方列為淘汰。作為作業系統功能留存下來,和容易遷移到 .NET,是兩回事。System.Messaging 封閉在 .NET Framework 之內,要把應用程式升級到 .NET,原則上佇列也要一併重新檢視。123
如果短期內繼續使用,就要確認作業系統的支援期限,並備妥建置與復原程序、佇列監控、組態記錄以及每年一次的判斷檢視。擱置不動最大的風險不只在技術,還在於將來沒有人能碰它。
如果要遷移,先盤點依賴與訊息格式。對於更新同一個業務資料庫的非同步處理,資料表佇列是首選。它可以把原本由 MSMQ+DTC 承擔的一致性,換成同一個資料庫上的本機交易。出現向多個系統傳遞、路由等資料表佇列滿足不了的需求時,再考慮 RabbitMQ 或 Azure Service Bus。
在此基礎上,再確認 SQL 的隔離等級與提示、平行處理時的寫入順序,以及橋接的遺失與重複對策。重要的是,不要因為「聽說被廢除了」就著急,而要先掌握自己的系統究竟依賴什麼,再決定維持還是遷移。
相關文章
- VB6 應用程式能用到什麼時候 ── 執行環境的支援現況與務實的 .NET 遷移做法
- 從 .NET Framework 遷移到 .NET 的著手前檢核表
- 為沒有測試的遺留業務應用程式安全地動手修改 ── 特性化測試與重構的實踐
- Windows 的行程間通訊該怎麼選 ── 具名管道 / TCP / gRPC / 共享記憶體 / COM 判斷表
- TCP 中「Send 單位=Receive 單位」的誤解 ── 把 TCP 視為位元組串流來設計接收邏輯
- 接手了沒有原始碼也沒有規格書的系統 ── 不停機維運的實務步驟
- 為業務應用程式的 DB 結構做版本管理 ── 防止「各客戶端 DB 不一致」的遷移實踐
相關諮詢領域
合同會社小村軟體承接包含 MSMQ 在內的遺留架構盤點與遷移計畫、從 .NET Framework 遷移到 .NET,以及包含佇列處理在內的業務系統改造。
參考連結
-
Microsoft Learn, Deprecated features in the Windows client。Windows 用戶端中已停止積極開發(即已淘汰)功能的官方清單。關於截至 2026 年 7 月的清單中列有 NTLM、VBScript、WordPad 等項目,卻沒有列出 MSMQ(Microsoft Message Queuing);以及淘汰(deprecated)是指「並未積極開發,未來更新中可能會被移除」的階段,與移除(removed)有所區別這兩點。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Features Removed or No Longer Developed in Windows Server。Windows Server 中已移除功能與停止開發(已淘汰)功能的官方清單。關於包含 Windows Server 2025 分頁在內、截至 2026 年 7 月的清單中並未列出 MSMQ;以及已淘汰的元件仍會持續隨附於 Windows Server,依產品生命週期為正式環境提供支援,並持續獲得安全性更新與品質更新這兩點。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, MessageQueue Class (System.Messaging)。關於 System.Messaging.MessageQueue 類別的參考文件所涵蓋的版本為 .NET Framework 1.1 到 4.8.1,並不存在 .NET(Core 以後)版本這一點。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, .NET Framework official support policy。關於 .NET Framework 4.8 被定義為 Windows 作業系統的元件,依所安裝之母產品(作業系統)的生命週期政策提供支援這一點。 ↩ ↩2
-
Microsoft Learn(封存版), Message Queuing Functions 以及 MQSendMessage、MQReceiveMessage。關於 MSMQ 的 Win32 原生 API(MQCreateQueue、MQSendMessage、MQReceiveMessage 等)已針對 C/C++ 應用程式提供文件,可以不經由受管理 API 就完成佇列的建立、傳送與接收這一點。 ↩ ↩2
-
Microsoft Learn, Use the Windows Compatibility Pack to port code to .NET。關於 Windows 相容性套件(Microsoft.Windows.Compatibility 套件)為了在遷移到 .NET 時補足對 .NET Framework 專用 API 的依賴,提供了約兩萬個 API;以及其技術領域清單(CodeDom、組態、Directory Services、Drawing、ODBC、ACL、WCF、登錄檔、WMI、效能計數器、Windows 服務、EventLog 等)並未包含訊息傳遞(System.Messaging)這兩點。 ↩ ↩2
-
CoreWCF project, CoreWCF.MSMQ (NuGet) 以及 CoreWCF 1.4.0 Preview release、Microsoft, CoreWCF Support Policy。關於 CoreWCF 是把 WCF 伺服器端移植到 .NET 的社群主導專案,Microsoft 為其提供官方支援政策;作為佇列類傳輸的一環,已發布支援 MSMQ 的套件(CoreWCF.MSMQ);以及該 MSMQ 實作依賴 .NET Framework 版 System.Messaging 函式庫的社群移植版本這幾點。 ↩ ↩2 ↩3
-
Microsoft Learn, BinaryFormatter migration guide。關於 BinaryFormatter 基於安全性理由而逐步淘汰,自 .NET 9 起已從執行環境中移除實作、預設無法使用;以及官方建議改用 JSON(System.Text.Json)等安全的序列化格式作為遷移目標這兩點。 ↩ ↩2 ↩3
-
Microsoft Learn(封存版), Express and Recoverable Messaging、System-Generated Queues、Message Queuing (MSMQ)。關於 express 訊息在傳遞中與傳遞後都保留在 RAM 上,一旦訊息所在的電腦停止或 MSMQ 服務停止就會遺失;recoverable 訊息會在傳送端與中繼的各台電腦上寫入磁碟,在目的佇列中也保留在磁碟上;公用佇列會登錄到目錄服務,而私人佇列只登錄在本機電腦上、不會公開到目錄服務;以及保存已從佇列取出的訊息與已傳送訊息副本的日誌佇列,與保存無法送達之訊息的無法送達(dead-letter)佇列,都是 MSMQ 產生的系統佇列這幾點。 ↩
-
Microsoft Learn, 資料表提示 (Transact-SQL)。關於
READPAST是一種跳過而不讀取其他交易所鎖定之資料列的提示,可跳過的僅限資料列層級的鎖定,無法跳過分頁層級的鎖定,且只能在READ COMMITTED或REPEATABLE READ隔離等級下指定這一點。特別是以下這段敘述:「當READ_COMMITTED_SNAPSHOT資料庫選項設為ON,且 (a) 工作階段的交易隔離等級為READ COMMITTED,或 (b) 查詢中也指定了READCOMMITTED資料表提示,兩者之一成立時,就無法指定READPAST資料表提示。若要在這些情況下指定READPAST提示,須移除查詢中的READCOMMITTED資料表提示(如果有的話),並在查詢中加入READCOMMITTEDLOCK資料表提示」。另請參閱同一頁面中關於READCOMMITTEDLOCK是不論READ_COMMITTED_SNAPSHOT設定為何,都以鎖定方式執行READ COMMITTED的提示,以及對同一張資料表無法指定兩個以上粒度提示(PAGLOCK/NOLOCK/READCOMMITTEDLOCK/ROWLOCK/TABLOCK/TABLOCKX)的說明。資料庫端的設定可以透過 sys.databases 的is_read_committed_snapshot_on確認。 ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, TOP (Transact-SQL)。關於在 INSERT、UPDATE、MERGE、DELETE 中使用 TOP 時,被參照的資料列並不會依任何順序排列;以及若要決定順序,應使用同時具有 TOP 與 ORDER BY 的子查詢這兩點。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
VB6 應用程式能用到什麼時候 ── 執行環境的支援現況與務實的 .NET 遷移做法
VB6 應用程式究竟能用到什麼時候?本文整理 VB6 執行環境的支援政策(Windows 11 也在支援範圍內)與 IDE 支援早已終止這種不對稱現況,並以實務指南的形式,說明全面重寫、自動轉換、階段性遷移的判斷表、遷移前的資產盤點、VB6 與 .NET 的不相容之處,以及...
引數為什麼會壞掉 ── Windows 命令列引數的規則
在 Windows 上並不存在引數的陣列,傳給 CreateProcess 的是一條字串,分割由接收端負責。本文說明 CommandLineToArgvW・CRT・.NET 的分割規則,以及 .NET 的 ArgumentList 與 C++ 中正確的組裝方式。
多執行緒的實務最佳實踐 .NET篇 ── 在增加執行緒之前先決定的事
針對 .NET/C# 整理能防止「建立執行緒後偶爾當機、卡死」的設計定石。內容涵蓋不自行建立執行緒而改用 Task、減少共享可變狀態、鎖的紀律、以 CancellationToken 設計停止機制,以及 UI 執行緒的處理方式。
在 C# 與 PowerShell 中使用 WMI/CIM ── 硬體資訊取得、處理程序監控、遠端查詢實務指南
取得 PC 序號、監控磁碟可用空間、偵測處理程序啟動,這些需求的標準答案就是 WMI/CIM。本文說明 Get-CimInstance 等 CIM Cmdlet 的用法與從舊版 Get-WmiObject 的遷移、C# 中 System.Management 與 CIM A...
Windows I/O 的深層(第 4 回) ── 快取管理員:你的 WriteFile 究竟何時送達磁碟
本文是以圖解說明 Windows 快取管理員的系列第 4 回。整理了以檔案對應方式實作的快取、預先讀取與延遲寫入、FlushFileBuffers 與 FILE_FLAG_NO_BUFFERING 的分別使用時機,以及斷電導致資料消失的條件。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
Windows 軟體維護 & 現代化
支援既有 Windows 軟體的階段性升級、功能追加、64 位元就緒以及可維護性的重構。
常見問題
整理諮詢這個主題時常見的問題。
- MSMQ 已經被廢除了嗎?
- 沒有。截至 2026 年 7 月,無論是 Windows 用戶端的已淘汰功能清單,還是 Windows Server 的「已移除功能與停止開發功能」清單,都沒有列出 MSMQ。它仍以 Windows 選用功能的形式,隨附於現行的作業系統中。「已經廢除」這種說法之所以流傳開來,是因為與 .NET 端的狀況混為一談。處理 MSMQ 的標準類別庫 System.Messaging 只存在於 .NET Framework,並未移植到 .NET(Core 以後)。也就是說,正確的狀態是「作為作業系統功能仍然存續,但沒有從現代 .NET 使用它的官方途徑」。
- 有沒有辦法從 .NET 8 或 .NET 10 使用 MSMQ?
- 沒有官方的受管理 API。System.Messaging 是到 .NET Framework 4.8.1 為止的 API,也不包含在 Windows 相容性套件(Microsoft.Windows.Compatibility)中。透過 P/Invoke 呼叫 MSMQ 的原生 Win32 API(如 MQSendMessage)在技術上是可行的,但這代表要自行撰寫並維護包含格式器與交易整合在內的包裝層。社群方面,CoreWCF 專案發布了 MSMQ 傳輸用的套件(CoreWCF.MSMQ),但這是用來在現代 .NET 上代管透過佇列呼叫的 WCF 服務(WCF 伺服器端的移植),既不是可以取代 System.Messaging 的通用佇列 API,也不能替代傳送端的用戶端。其實作本身也依賴 .NET Framework 版 System.Messaging 的社群移植版本。它可以作為驗證或暫時延續使用的選項。CoreWCF 本身有 Microsoft 官方的支援政策,但這並不代表這個 MSMQ 實作所依賴的 System.Messaging 社群移植版本也享有同樣的保證,因此若要把它當成業務系統的前提,應先確認所使用的版本是否在支援政策範圍內,以及依賴部分要如何處理,再做判斷。實務上,正途是在把應用程式升級到 .NET 的同時,一併遷移佇列。
- 遷移目標選 RabbitMQ 還是 Azure Service Bus 比較好?
- 在這兩個選項之前,請先考慮把資料庫的資料表當成佇列使用的方案。使用 MSMQ 的中小規模業務系統,多數情況下佇列的對象都是更新自家資料庫的處理。這種情況下,資料表佇列可以在與業務資料相同的本機交易中,同時確定「取出訊息」與「更新業務資料」,用更單純的機制取代原本由 MSMQ+分散式交易(DTC)所實現的一致性。若出現資料表佇列滿足不了的需求,例如需要在多個系統之間鬆散耦合地傳遞,或需要高吞吐量,再依序考慮:地端需求較強就選 RabbitMQ,可以放在雲端就選 Azure Service Bus,依這個順序推進比較不容易失敗。
- 當下先維持 .NET Framework、擱置不動,這樣的判斷可以嗎?
- 有條件地可以。.NET Framework 4.8 作為 Windows 的元件,會依照作業系統的生命週期受到支援,而 MSMQ 本身也尚未被淘汰,因此可以預期它「會持續運作」。不過,若選擇擱置不動,至少要做到以下四點:在裝機程序中明確記載啟用 MSMQ 功能的步驟、建立佇列長度與日誌佇列的監控機制、把訊息格式與連線組態文件化、為負責人異動預留復原程序。擱置不動真正的風險不在技術本身,而在於「變得沒有人能碰它」。