// Producer-Consumer pattern with BlockingCollection
var queue = new BlockingCollection<WorkItem>(boundedCapacity: 100);
// Producer thread
Task.Run(() =>
{
foreach (var item in GetWorkItems())
{
// Blocks if queue is full (back-pressure!)
queue.Add(item);
Console.WriteLine($"Produced: {item.Id}");
}
queue.CompleteAdding(); // Signal no more items
});
// Consumer thread
Task.Run(() =>
{
// Blocks automatically when queue is empty
// Exits when CompleteAdding() is called and queue is drained
foreach (var item in queue.GetConsumingEnumerable())
{
ProcessItem(item);
Console.WriteLine($"Consumed: {item.Id}");
}
});
這篇文章是我從 DBA 到全端架構師這幾年,在 SQL Server 效能優化上踩過的坑的總整理。不是教科書式的理論,而是每一條都是我實際測試、實際踩雷後的血淚經驗。如果你正在處理 SQL Server 效能優化的問題——DELETE 後空間沒釋放、查詢莫名其妙變慢、鎖定機制搞不清楚——這篇應該能幫你少走不少冤枉路。
TL;DR 重點摘要
DELETE 不會釋放磁碟空間,只是標記刪除。要真正回收空間,必須用 TRUNCATE 或 ALTER INDEX REBUILD。
NOT IN 是效能炸彈,改用 NOT EXISTS 可以讓查詢快數十倍,尤其在子查詢結果集大的時候。
在 Azure SQL Database 上,大量 DELETE 後看到儲存空間快滿了——這是假警報。空間根本沒被釋放。DELETE ... WITH (TABLOCK) 效果有限,必須搭配 TRUNCATE 或 ALTER INDEX ALL ON [TableName] REBUILD 才能真正回收。
-- Problem: Without TABLOCKX, T2 can read T1's uncommitted changes
-- Session 1
BEGIN TRAN T1;
UPDATE Orders SET Amount = 999 WHERE OrderID = 1;
-- (not committed yet)
-- Session 2 (runs concurrently, sees Amount = 999 → Dirty Read!)
SELECT Amount FROM Orders WHERE OrderID = 1;
-- Solution: Use TABLOCKX for exclusive access
BEGIN TRAN T1;
SELECT * FROM Orders WITH (TABLOCKX) WHERE OrderID = 1;
-- Now T2 is BLOCKED until T1 commits or rolls back
UPDATE Orders SET Amount = 999 WHERE OrderID = 1;
COMMIT TRAN T1;
否定查詢(NOT EXISTS vs NOT IN):NOT EXISTS 遠遠快於 NOT IN,差距可達數十倍。
-- NOT IN: Slow — performs O(n*m) comparison, NULL handling issues
SELECT * FROM Products
WHERE ProductID NOT IN (
SELECT ProductID FROM OrderDetails
);
-- NOT EXISTS: Fast — uses semi-join, stops at first match
SELECT * FROM Products p
WHERE NOT EXISTS (
SELECT 1 FROM OrderDetails od
WHERE od.ProductID = p.ProductID
);
-- Additional trap: if OrderDetails.ProductID contains ANY NULL value,
-- NOT IN returns ZERO rows! NOT EXISTS handles NULL correctly.
NOT IN 之所以慢,是因為它必須對子查詢的每一筆結果做比對,而且還要處理 NULL 的三值邏輯。NOT EXISTS 則是用半連接(Semi-Join)策略,找到第一筆匹配就停止。
排序與 TOP 的隱藏陷阱
加上 TOP 之後,SQL Server 的排序演算法會完全改變。沒有 TOP 時用完整排序(Full Sort),有 TOP 時用 Top-N Sort,記憶體需求和執行路徑完全不同。
SQL 執行順序(必背)
很多查詢優化的問題,根源是不理解 SQL 的實際執行順序:
FROM → JOIN → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → TOP/OFFSET
注意 SELECT 在 WHERE 之後,所以你不能在 WHERE 中使用 SELECT 裡定義的別名。而 ORDER BY 在 SELECT 之後,所以可以用別名排序。理解這個順序,很多「為什麼這樣寫不行」的問題都迎刃而解。
另外,索引不只消除全表掃描,還能跳過排序階段。如果 ORDER BY 的欄位剛好有索引,SQL Server 可以直接按索引順序讀取,省掉排序的 CPU 和記憶體開銷。
4. 全文檢索 — 比 LIKE ‘%keyword%’ 快一百倍
如果你的應用有「搜尋文章內容」的需求,還在用 LIKE '%keyword%',那你的查詢基本上每次都是全表掃描。全文檢索(Full-Text Search)透過反向索引(Inverted Index)來加速文字搜尋,效能差距是數量級的。
建立全文檢索的前提與步驟
前提:目標表必須有主鍵(Primary Key)。因為反向索引需要唯一識別碼來對應每筆資料。
-- Step 1: Enable full-text search on the database (if not already)
-- (SQL Server installs Full-Text Search as a feature)
-- Step 2: Create a full-text catalog
CREATE FULLTEXT CATALOG ftCatalog AS DEFAULT;
-- Step 3: Create a full-text index on the table
-- The table MUST have a primary key
CREATE FULLTEXT INDEX ON Articles (
Title LANGUAGE 1028, -- 1028 = Traditional Chinese
Content LANGUAGE 1028
)
KEY INDEX PK_Articles -- Must reference the PK
ON ftCatalog
WITH CHANGE_TRACKING AUTO; -- Auto-update when data changes
-- Step 4: Query using CONTAINS or FREETEXT
SELECT * FROM Articles
WHERE CONTAINS(Content, N'效能優化');
-- Compare with LIKE (full table scan every time)
SELECT * FROM Articles
WHERE Content LIKE N'%效能優化%';
SQL Server Profiler 在生產環境不一定能用(效能開銷太大,或者根本沒權限)。這時候 DMV(Dynamic Management Views)就是你的救星。
追蹤特定時間範圍的查詢
-- Find top queries by CPU time within a time range
SELECT TOP 20
qs.last_execution_time,
qs.execution_count,
qs.total_worker_time / 1000 AS total_cpu_ms,
qs.total_elapsed_time / 1000 AS total_elapsed_ms,
qs.total_logical_reads,
SUBSTRING(st.text,
(qs.statement_start_offset / 2) + 1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset) / 2) + 1
) AS query_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
WHERE qs.last_execution_time >= '2026-04-04 09:00:00'
AND qs.last_execution_time <= '2026-04-04 18:00:00'
ORDER BY qs.total_worker_time DESC;
查看當前執行中的程序
-- Quick check: who's running what right now?
EXEC sp_who2;
-- Or with more detail via DMV
SELECT
r.session_id,
r.status,
r.command,
r.wait_type,
r.wait_time,
t.text AS query_text,
r.cpu_time,
r.reads,
r.writes
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
WHERE r.session_id > 50; -- Exclude system sessions
全庫儲存空間盤點
-- Iterate all user tables and check space usage
CREATE TABLE #SpaceUsed (
TableName NVARCHAR(128),
Rows NVARCHAR(20),
Reserved NVARCHAR(20),
Data NVARCHAR(20),
IndexSize NVARCHAR(20),
Unused NVARCHAR(20)
);
DECLARE @tbl NVARCHAR(128);
DECLARE tbl_cursor CURSOR FOR
SELECT TABLE_SCHEMA + '.' + TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE';
OPEN tbl_cursor;
FETCH NEXT FROM tbl_cursor INTO @tbl;
WHILE @@FETCH_STATUS = 0
BEGIN
INSERT INTO #SpaceUsed
EXEC sp_spaceused @tbl;
FETCH NEXT FROM tbl_cursor INTO @tbl;
END
CLOSE tbl_cursor;
DEALLOCATE tbl_cursor;
SELECT * FROM #SpaceUsed ORDER BY CAST(REPLACE(Reserved, ' KB', '') AS BIGINT) DESC;
DROP TABLE #SpaceUsed;
-- Table variable: optimizer always estimates 1 row
DECLARE @small TABLE (ID INT, Name NVARCHAR(50));
INSERT INTO @small SELECT TOP 10 ID, Name FROM Products;
-- Local temp table: has statistics, better for large datasets
CREATE TABLE #bigtemp (ID INT, Name NVARCHAR(50));
INSERT INTO #bigtemp SELECT ID, Name FROM Products;
CREATE INDEX IX_bigtemp_ID ON #bigtemp(ID); -- Can add indexes
-- Global temp table: visible to all sessions
CREATE TABLE ##shared (ID INT, Name NVARCHAR(50));
-- Other sessions can SELECT from ##shared
# Python Crawler — Everything That Can Go Wrong
## ROC Date Format
- [source: analyst] "1150309" 被解析成 AD 1150 年,要用 7 位 YYMMDD ROC 格式
- [source: analyst] TPEX 欄位名 TransactionAmount 不是 TradingMoney
## Holiday / Empty Response
- [source: analyst] TWSE API 假日返回空值,必須 guard `if not data: return []`
然後在 CLAUDE.md 裡強制 agent 在開工前讀 brain:
## Domain Brain — MANDATORY before ANY implementation work
Before writing any plan, code, or review, you MUST:
1. Find the `## Domain Brain:` line in the project's CLAUDE.md
2. Read each listed brain file
3. If you skip this step and a bug was documented in brain, that is YOUR failure
今日的科技圈展現了從深空探索到微觀量子力學的跨維度進展,同時也揭示了軟硬體生態系正面臨的實質挑戰。從 NASA 的登月任務更新到 Linux 在遊戲市場的突破,這些動態不僅關乎技術演進,更直接影響了開發者的工具選擇與企業的營運成本。
🤖 AI / 機器學習
Claude Code 洩漏事件解析
近期關於 Claude Code 的相關程式碼洩漏事件引起了開發者社群的高度關注。這起事件揭露了 Anthropic 在開發 AI 輔助編程工具時的內部機制與邏輯處理方式。對於依賴 AI 工具的開發者而言,這不僅是一個技術洩漏問題,更引發了關於 AI 工具安全性與程式碼隱私的深度討論。了解這些底層邏輯,有助於我們更謹慎地評估 AI 輔助工具在企業環境中的應用邊界。
OCaml 編譯器(ocamlc)新增了一個 C++ 後端,這標誌著該函數式編程語言在互操作性上的重大飛躍。這個新的後端允許 OCaml 程式碼更無縫地與 C++ 生態系整合,並可能帶來更好的性能優化空間。對於追求高性能且需要利用現有 C++ 庫的系統開發者而言,這項更新大幅擴展了 OCaml 的應用場景。
知名科技評論家 Jeff Geerling 指出,不斷攀升的 DRAM 成本正嚴重打擊單板電腦(SBC)市場,如 Raspberry Pi 等產品。原本以低廉價格著稱的硬體,現在因為記憶體成本佔比過高,迫使製造商調漲售價或縮減規格,這對自造者文化(Maker Culture)與教育用途專案造成了巨大阻礙。這反映了全球半導體供應鏈波動對微型運算生態系的深遠影響。
今天的科技圈焦點呈現出極端的對比:一方面我們面臨著如 Axios 套件遭竄改等嚴峻的供應鏈安全威脅,另一方面則是 AI 技術在邊緣運算與專業領域(如時間序列預測)的持續深化。身為開發者或技術決策者,理解這些趨勢不僅能幫助你保護現有的系統,更能讓你掌握下一波開發效率提升的關鍵工具。
🤖 AI / 機器學習
1. Ollama 現支援 Apple Silicon MLX 加速(預覽版)
熱門的本地 LLM 執行工具 Ollama 宣佈在預覽版中整合了 Apple 的 MLX 框架。這意味著在 Mac 上執行大型語言模型時,將能更深層地利用 Apple Silicon 的統一記憶體架構與 GPU 加速,顯著提升推論速度與效率。對於追求隱私且習慣在本地端開發 AI 應用的開發者來說,這是一個重大的性能跨越。🚀