Azure SQL 資料庫以 DTU – Basic 在那些情況下, 會不夠使用

「DTU – Basic」是一個非常親民的入門選擇,每月僅約 $5 USD,對於初期開發或流量極小的系統非常友善。但校務建言系統(尤其是若涉及全校性活動或高峰期)在使用 Basic 級別時,可能會遇到一些明顯的瓶頸。

以下是 DTU – Basic 在開發校務建言系統時,可能「不夠用」的幾種情況:


1. 併發連線數(Max Concurrent Workers)限制

這是 Basic 最致命的短板。

  • 限制:Basic 僅支援 30 個併發工作執行緒
  • 情境:如果校方發出一封全校 Email 邀請大家來建言,瞬間有 50 位同學點開網頁,資料庫會立刻排隊甚至報錯(Timeouts),導致系統看起來像掛掉了。
  • 建議:若預期會有瞬間高流量(如開會期間、校園新聞發布),Basic 可能無法負荷。

2. IOPS 與資料吞吐量效能

  • 限制:Basic 的 I/O 效能非常基礎。
  • 情境:雖然你將附件存在 Blob Storage(這是非常正確的作法!),但資料庫仍需處理建言內容的 全文檢索複雜查詢(例如:管理員要篩選「過去一年、所有關於總務處、且狀態為未處理」的建言)。當資料筆數累積到幾千筆以上時,這種分群彙整(Group By)的查詢速度會變得很慢。

3. 資料庫大小上限

  • 限制:Basic 的硬碟空間上限通常只有 2 GB
  • 情境:雖然附件在 Blob,但如果建言系統包含大量文字回覆、系統 Log、或多個關聯資料表,2 GB 可能在 1-2 年內就會用滿。

4. 缺乏進階功能

  • 限制:Basic 不支援 Point-in-Time Restore (PITR) 較長的天數(僅 7 天),且不支援異地備份。
  • 情境:如果有人不小心刪錯資料,你只能回推到過去 7 天內。

針對「校務建言系統」的架構建議

既然你已經規劃將附件存放在 Blob Storage,這大大減輕了資料庫的壓力。以下是針對你的專案建議的部署策略:

策略 A:初期省錢法 (開發階段)

  • 使用 Basic (5 DTU):在開發與內部測試(10 人以內)時完全足夠。
  • 優點:成本極低,適合慢慢磨功能。

策略 B:上線初期/正式運行 (彈性調整)

  • 升級至 Standard (S0 或 S1)
    • S0 (10 DTU):約 $15 USD/月,支援 60 個併發連線,容量可達 250GB。
    • S1 (20 DTU):約 $30 USD/月,效能明顯提升,適合應對一般校園流量。

策略 C:極致省錢法 (使用 Serverless)

如果你不希望固定每個月付 $15~$30 USD,可以考慮從 DTU 模式切換到 vCore – Serverless

  • 運作邏輯:建言系統通常「晚上沒人用」或「假日時段冷門」。Serverless 可以在沒人點擊網頁時自動暫停
  • 費用:可能比 Standard 更便宜,卻能提供比 Basic 更強大的瞬間處理能力。

總結建議

你的校務系統**「平時」用 Basic 是可以的,但「宣傳推廣期」**一定會卡頓。

我的實務建議

先用 Basic 開發。等系統準備正式發布給學生使用的那一天,手動在 Azure Portal 將級別調整至 Standard S1(調整過程資料庫不會停機)。等宣傳熱潮過了,再降回 BasicS0 節省開支。


如何在程式碼中(例如 C# 或 Python)實作「上傳附件到 Blob 並將 URL 寫回 SQL」的最佳實踐

這是一個非常標準且高效的雲端開發模式:SQL 存放結構化資料(後設資料),Blob Storage 存放非結構化的大檔案(圖片、PDF、文件)

這種做法能讓資料庫保持輕量,避免 DTU - Basic 的空間迅速被填滿。以下是實作「校務建言系統」附件處理的最佳實踐與範例:


1. 資料庫資料表設計 (SQL Side)

不要在 SQL 中存檔案本身(Blob 類型),而是存檔案的 URL唯一識別碼 (GUID)

SQL

CREATE TABLE Suggestions (
    Id INT PRIMARY KEY IDENTITY(1,1),
    StudentId NVARCHAR(50),
    Content NVARCHAR(MAX),
    CreatedAt DATETIME DEFAULT GETDATE()
);

CREATE TABLE Attachments (
    Id INT PRIMARY KEY IDENTITY(1,1),
    SuggestionId INT, -- 關聯到建言
    FileName NVARCHAR(255),
    BlobUrl NVARCHAR(2048), -- 儲存 Blob 的完整路徑
    BlobName GUID, -- 建議用 GUID 命名檔案以防重複
    FOREIGN KEY (SuggestionId) REFERENCES Suggestions(Id)
);

2. 檔案上傳邏輯 (Workflow)

在開發時,建議遵循以下流程以確保效能:

  1. 前端上傳:使用者送出建言與檔案。
  2. 後端處理
    • 產生一個唯一的檔名(例如 guid.jpg)。
    • 使用 Azure Storage SDK 將檔案上傳至 Blob Storage
    • 取得該檔案的 永久連結 (URL) 或路徑。
  3. 寫入資料庫:將建言文字與該 URL 同步寫入 SQL Database。

3. C# 程式碼範例 (使用 Azure.Storage.Blobs)

這是最常見的實作方式,簡潔且安全:

C#

// 1. 初始化 Blob 客戶端
BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient("school-attachments");

// 2. 上傳檔案
string blobName = Guid.NewGuid().ToString() + Path.GetExtension(file.FileName);
BlobClient blobClient = containerClient.GetBlobClient(blobName);

using (var stream = file.OpenReadStream())
{
    await blobClient.UploadAsync(stream, true);
}

// 3. 取得 URL 並存入 SQL
string fileUrl = blobClient.Uri.ToString();
// 接下來使用 ADO.NET 或 Entity Framework 將 fileUrl 存入 Attachments 表

4. 進階安全性建議:SAS Token

由於校務系統可能涉及隱私(例如建言者不想讓別人看到他的附件),你不應該把 Blob Container 設為「公開讀取」。

  • 做法:將 Container 設為 Private (私用)
  • 讀取方式:當使用者要查看附件時,後端產生一個 暫時性的簽署 URL (Shared Access Signature, SAS)
  • 優點:這個連結會在 10 分鐘或 1 小時後失效,即使連結外流,別人也無法下載。

5. 為什麼這樣做對 Basic 級別最有利?

  • 節省 DTU 消耗:資料庫不需要處理大數據流(I/O),只需處理簡短的文字字串。
  • 節省空間:SQL Basic 的 2GB 限制可以撐非常久,因為幾萬筆 URL 頂多才幾十 MB。
  • 成本優勢:Blob Storage 每 GB 的價格(約 $0.02 USD)遠低於 SQL Database 空間。

使用 Python 實作「上傳檔案至 Blob 並將 URL 寫入 SQL」是非常直覺的。我們通常會使用 azure-storage-blob 套件處理檔案,並使用 pyodbcSQLAlchemy 處理資料庫。

以下是一個完整的邏輯範例:

1. 安裝必要套件

在你的開發環境中執行:

Bash

pip install azure-storage-blob pyodbc

2. Python 實作程式碼

這個範例展示了如何接收一個檔案、上傳到 Azure,最後把資料存進 SQL Database。

Python

import os
import uuid
from azure.storage.blob import BlobServiceClient
import pyodbc

# --- 1. 設定連線資訊 ---
# Azure Blob Storage 連線字串
AZURE_STORAGE_CONNECTION_STRING = "DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;"
CONTAINER_NAME = "school-attachments"

# Azure SQL 連線資訊 (注意:Basic 級別請確保防火牆已開放 IP)
SQL_CONNECTION_STRING = (
    "Driver={ODBC Driver 18 for SQL Server};"
    "Server=tcp:your-server.database.windows.net,1433;"
    "Database=your-db;"
    "Uid=your-username;Pwd=your-password;"
    "Encrypt=yes;TrustServerCertificate=no;Connection Timeout=30;"
)

def upload_suggestion_with_attachment(student_id, content, file_path):
    try:
        # --- 2. 上傳檔案到 Blob Storage ---
        blob_service_client = BlobServiceClient.from_connection_string(AZURE_STORAGE_CONNECTION_STRING)
        container_client = blob_service_client.get_container_client(CONTAINER_NAME)
        
        # 產生唯一檔名避免重複 (例如: 550e8400-e29b.pdf)
        file_extension = os.path.splitext(file_path)[1]
        unique_blob_name = f"{uuid.uuid4()}{file_extension}"
        
        blob_client = container_client.get_blob_client(unique_blob_name)
        
        print(f"正在上傳檔案: {unique_blob_name}...")
        with open(file_path, "rb") as data:
            blob_client.upload_blob(data)
        
        # 取得上傳後的 URL
        blob_url = blob_client.url
        print(f"上傳成功!URL: {blob_url}")

        # --- 3. 將資料寫入 SQL Database ---
        conn = pyodbc.connect(SQL_CONNECTION_STRING)
        cursor = conn.cursor()

        # 插入建言並取得 ID (SQL Server 語法)
        cursor.execute(
            "INSERT INTO Suggestions (StudentId, Content) OUTPUT INSERTED.Id VALUES (?, ?)",
            (student_id, content)
        )
        suggestion_id = cursor.fetchone()[0]

        # 插入附件資訊
        cursor.execute(
            "INSERT INTO Attachments (SuggestionId, FileName, BlobUrl, BlobName) VALUES (?, ?, ?, ?)",
            (suggestion_id, os.path.basename(file_path), blob_url, unique_blob_name)
        )

        conn.commit()
        print("資料庫寫入完成!")

    except Exception as ex:
        print(f"發生錯誤: {ex}")
    finally:
        if 'conn' in locals():
            conn.close()

# 測試執行
upload_suggestion_with_attachment("S123456", "飲水機過濾網該換了", "report.pdf")

3. 進階架構優化(省錢與安全)

在實際開發校務系統時,還有兩點建議:

A. 非同步上傳 (Async)

如果你的系統是使用 FastAPIDjango,建議使用 azure-storage-blob 的非同步版本 (BlobServiceClient from azure.storage.blob.aio)。這樣當多個學生同時上傳大檔案時,不會卡住整個伺服器的執行緒。

B. 使用受控識別 (Managed Identity) —— 強烈建議

與其在程式碼中寫死資料庫密碼(Uid/Pwd),你可以啟用 Web App 的 Managed Identity

  • 好處:不需要在設定檔存密碼。
  • 安全性:即使程式碼外流,別人也拿不到資料庫權限。
  • 適用:這在跨資源群組存取時非常方便且安全。

4. 針對 DTU – Basic 的特別提醒

在使用 Python 連線時,請務必在 pyodbc 的連線字串中加入 Connection Timeout=30

因為 Basic 級別的資源較少,有時候冷啟動或排隊會稍微慢一點,適度增加超時時間可以減少連線失敗的機率。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *