Go 語言系列(十五):PostgreSQL 進階

這是 Go 語言從零到 Web 應用系列的第十五篇。前面我們用 SQLite 作為開發資料庫,現在升級到生產環境常用的 PostgreSQL,學習進階的資料庫功能。 用 Docker Compose 啟動 PostgreSQL 不需要在本地安裝 PostgreSQL,用 Docker 最方便: # docker-compose.yml version: '3.8' services: postgres: image: postgres:16-alpine ports: - "5432:5432" environment: POSTGRES_USER: app POSTGRES_PASSWORD: secret POSTGRES_DB: bookstore volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata: docker compose up -d 連線字串:postgres://app:secret@localhost:5432/bookstore?sslmode=disable pgx Driver pgx 是目前 Go 生態中效能最好的 PostgreSQL driver,比傳統的 lib/pq 快且功能更完整。 安裝: go get github.com/jackc/pgx/v5 go get github.com/jackc/pgx/v5/pgxpool 基本連線 package main import ( "context" "fmt" "log" "github.com/jackc/pgx/v5/pgxpool" ) func main() { ctx := context.Background() // pgxpool 自帶連線池 pool, err := pgxpool.New(ctx, "postgres://app:secret@localhost:5432/bookstore?sslmode=disable") if err != nil { log.Fatal(err) } defer pool.Close() // 測試連線 if err := pool.Ping(ctx); err != nil { log.Fatal(err) } fmt.Println("Connected to PostgreSQL!") } 搭配 database/sql 使用 如果你想保持用 database/sql 的介面(例如搭配 sqlx): ...

2026年2月24日 · 4 min · 692 words · Jack

Go 語言系列(十四):ORM 與資料庫工具 — GORM vs sqlx

這是 Go 語言從零到 Web 應用系列的第十四篇。前面我們用 database/sql 手寫 SQL 操作資料庫,現在來看看 ORM 和資料庫工具如何簡化這些工作。 ORM vs 原生 SQL 面向 原生 SQL (database/sql) ORM (GORM) SQL-first (sqlx) 學習曲線 需要懂 SQL 可以少寫 SQL 需要懂 SQL 開發速度 較慢(模板程式碼多) 快(自動產生 SQL) 中等 SQL 控制力 完全控制 有限(複雜查詢困難) 完全控制 效能 最佳 有反射開銷 接近原生 Migration 需自行處理 內建 AutoMigrate 需自行處理 GORM 安裝: go get -u gorm.io/gorm go get -u gorm.io/driver/sqlite # 或 postgres、mysql Model 定義 package main import ( "time" "gorm.io/gorm" ) type User struct { ID uint `gorm:"primaryKey" json:"id"` Name string `gorm:"size:100;not null" json:"name"` Email string `gorm:"uniqueIndex;not null" json:"email"` Books []Book `gorm:"foreignKey:UserID" json:"books"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` // 軟刪除 } type Book struct { ID uint `gorm:"primaryKey" json:"id"` Title string `gorm:"size:200;not null" json:"title"` Author string `gorm:"size:100" json:"author"` UserID uint `json:"user_id"` } 連線與 AutoMigrate import ( "gorm.io/driver/sqlite" "gorm.io/gorm" ) func main() { db, err := gorm.Open(sqlite.Open("app.db"), &gorm.Config{}) if err != nil { panic("failed to connect database") } // AutoMigrate 自動建立或更新表結構 db.AutoMigrate(&User{}, &Book{}) } CRUD 操作 // Create user := User{Name: "Jack", Email: "[email protected]"} result := db.Create(&user) // result.RowsAffected = 1 // user.ID 自動填入 // Read var u User db.First(&u, 1) // 找 ID=1 db.Where("email = ?", "[email protected]").First(&u) // 條件查詢 // 查詢多筆 var users []User db.Where("name LIKE ?", "%Jack%").Find(&users) // Update db.Model(&user).Update("name", "Jack Tse") db.Model(&user).Updates(User{Name: "Jack Tse", Email: "[email protected]"}) // Delete(軟刪除,因為 User 有 DeletedAt 欄位) db.Delete(&user, 1) // 真正刪除 db.Unscoped().Delete(&user, 1) 關聯查詢 // 建立使用者和書籍 db.Create(&User{ Name: "Jack", Email: "[email protected]", Books: []Book{ {Title: "Go in Action", Author: "William Kennedy"}, {Title: "The Go Programming Language", Author: "Donovan & Kernighan"}, }, }) // Preload 關聯資料 var user User db.Preload("Books").First(&user, 1) // user.Books 會自動填入 // 條件 Preload db.Preload("Books", "author LIKE ?", "%Kennedy%").First(&user, 1) Hook // 在建立之前自動處理 func (u *User) BeforeCreate(tx *gorm.DB) error { if u.Email == "" { return errors.New("email is required") } return nil } // 在刪除之後清理關聯 func (u *User) AfterDelete(tx *gorm.DB) error { tx.Where("user_id = ?", u.ID).Delete(&Book{}) return nil } Transaction err := db.Transaction(func(tx *gorm.DB) error { user := User{Name: "Jack", Email: "[email protected]"} if err := tx.Create(&user).Error; err != nil { return err // 回傳 error 會自動 rollback } book := Book{Title: "New Book", UserID: user.ID} if err := tx.Create(&book).Error; err != nil { return err } return nil // 回傳 nil 會自動 commit }) sqlx 安裝: ...

2026年2月24日 · 4 min · 795 words · Jack

Go 語言系列(八):資料庫整合

這是 Go 語言從零到 Web 應用系列的第八篇。上一篇我們建立了結構清晰的專案,但資料存在記憶體中。這篇要把資料存進真正的資料庫。 為什麼選 SQLite? 對於教學和小型應用來說,SQLite 是完美的選擇: 零配置:不需要安裝資料庫伺服器 單一檔案:整個資料庫就是一個檔案 效能優秀:對於大多數應用場景都足夠快 SQL 相容:學到的 SQL 知識可以直接用在 PostgreSQL、MySQL 而且 Go 的 database/sql 是統一的資料庫介面,只要換一個 driver 就能切換到其他資料庫。 database/sql 介紹 database/sql 是 Go 標準庫提供的資料庫抽象層。它定義了統一的介面,不同的資料庫只需要提供對應的 driver。 常見的 driver: SQLite: github.com/mattn/go-sqlite3(CGO)或 modernc.org/sqlite(純 Go) PostgreSQL: github.com/lib/pq 或 github.com/jackc/pgx MySQL: github.com/go-sql-driver/mysql 我們使用純 Go 實作的 SQLite driver,不需要 CGO: go get modernc.org/sqlite 連接資料庫 import ( "database/sql" _ "modernc.org/sqlite" // 註冊 SQLite driver ) func main() { db, err := sql.Open("sqlite", "./bookapi.db") if err != nil { log.Fatal(err) } defer db.Close() // 驗證連線 if err := db.Ping(); err != nil { log.Fatal(err) } fmt.Println("Database connected!") } 注意 _ "modernc.org/sqlite" 這行——前綴的 _ 表示只引入套件的副作用(註冊 driver),不直接使用它的任何匯出名稱。 ...

2026年2月24日 · 7 min · 1315 words · Jack

MySQL 黃金法則完全指南:資深 DBA 的經驗結晶

這篇文章整理了 MySQL 專家 Rick James 的 Rules of Thumb (RoTs) - 數十年實戰經驗濃縮成的黃金法則。這些法則不是死板的規定,而是經過無數生產環境驗證的最佳實踐。 “Count the disk hits!” - 這是優化 MySQL 最重要的原則 一、記憶體配置黃金比例 💾 InnoDB Buffer Pool 法則 # my.cnf 配置 innodb_buffer_pool_size = [RAM的70%] # 最重要的設定! # 範例:32GB RAM 的伺服器 innodb_buffer_pool_size = 22G 為什麼是 70%? 留 30% 給作業系統和其他程序 避免 swap(絕對不要讓 MySQL 使用 swap) 保留空間給連線緩衝區和臨時表 其他記憶體設定 # 臨時表(RAM 的 1%) tmp_table_size = 320M max_heap_table_size = 320M # 必須與 tmp_table_size 相同 # 連線緩衝區(每個連線) sort_buffer_size = 2M # 不要超過 2M read_buffer_size = 2M # 順序掃描緩衝 join_buffer_size = 2M # JOIN 操作緩衝 # 執行緒快取 thread_cache_size = 10 # 小而非零的值 # 關閉查詢快取(MySQL 8.0 已移除) query_cache_type = 0 query_cache_size = 0 記憶體使用監控 -- 檢查 Buffer Pool 使用率 SHOW STATUS LIKE 'Innodb_buffer_pool%'; -- 理想情況: -- Innodb_buffer_pool_pages_free 不應該接近 0 -- Innodb_buffer_pool_wait_free = 0(不應該等待空閒頁) 二、索引設計的十大法則 🔍 法則 1:複合索引的黃金順序 -- WHERE 子句分析 WHERE status = 'active' -- 等值條件 AND type = 'premium' -- 等值條件 AND created > '2025-01-01' -- 範圍條件 ORDER BY priority; -- 排序 -- 正確的索引順序 INDEX idx_optimal (status, type, created, priority) -- 等值 → 範圍 → 排序 法則 2:索引選擇性原則 -- 計算選擇性 SELECT COUNT(DISTINCT column) / COUNT(*) AS selectivity FROM table_name; -- 選擇性指標: -- > 0.9 極佳(適合建立索引) -- 0.5-0.9 良好 -- 0.1-0.5 一般(考慮複合索引) -- < 0.1 差(避免單獨索引) 法則 3:覆蓋索引優先 -- 查詢 SELECT user_id, username, email FROM users WHERE status = 'active'; -- 覆蓋索引(包含所有需要的欄位) INDEX idx_covering (status, user_id, username, email) -- 完全避免回表查詢! 法則 4:避免索引陷阱 -- ❌ 錯誤:函數操作導致索引失效 WHERE YEAR(created_date) = 2025 WHERE DATE_FORMAT(created_date, '%Y-%m') = '2025-08' -- ✅ 正確:保持欄位原始形態 WHERE created_date >= '2025-01-01' AND created_date < '2026-01-01' WHERE created_date >= '2025-08-01' AND created_date < '2025-09-01' -- ❌ 錯誤:隱式類型轉換 WHERE phone = 123456 -- phone 是 VARCHAR -- ✅ 正確:類型一致 WHERE phone = '123456' 法則 5:五個欄位上限 -- 複合索引不要超過 5 個欄位 INDEX idx_too_many (a, b, c, d, e) -- 極限 INDEX idx_way_too_many (a, b, c, d, e, f) -- 太多了! -- 原因: -- 1. 索引維護成本增加 -- 2. 記憶體使用增加 -- 3. 優化器可能誤判 三、查詢優化的實戰法則 ⚡ 法則 6:20% 規則 -- 當需要超過 20% 的資料時,全表掃描比索引快 SELECT * FROM large_table WHERE status != 'deleted'; -- 如果 deleted 只佔 5%,那需要 95% 的資料 -- MySQL 會選擇全表掃描(正確的選擇) 法則 7:批次操作最佳大小 -- 單筆插入(慢) INSERT INTO table VALUES (1, 'a'); INSERT INTO table VALUES (2, 'b'); -- 批次插入(快,但不要太大) INSERT INTO table VALUES (1, 'a'), (2, 'b'), ... (1000, 'zzz'); -- 100-1000 筆最佳 -- 超過 1000 筆應該分批 -- 原因:避免鎖定時間過長,影響並發 法則 8:JOIN vs 子查詢 -- ❌ 子查詢(通常較慢) SELECT * FROM orders WHERE customer_id IN ( SELECT id FROM customers WHERE country = 'TW' ); -- ✅ JOIN(通常較快) SELECT o.* FROM orders o INNER JOIN customers c ON o.customer_id = c.id WHERE c.country = 'TW'; -- 例外:EXISTS 有時比 JOIN 好 SELECT * FROM orders o WHERE EXISTS ( SELECT 1 FROM customers c WHERE c.id = o.customer_id AND c.country = 'TW' ); 法則 9:LIMIT 優化 -- ❌ 深度分頁問題 SELECT * FROM posts ORDER BY id LIMIT 1000000, 20; -- 需要掃描 1000020 筆! -- ✅ 使用延遲關聯 SELECT p.* FROM posts p INNER JOIN ( SELECT id FROM posts ORDER BY id LIMIT 1000000, 20 ) AS tmp USING(id); -- ✅✅ 最佳:記住位置 SELECT * FROM posts WHERE id > last_seen_id ORDER BY id LIMIT 20; 四、資料類型選擇法則 📊 法則 10:最小化原則 -- 整數類型選擇 TINYINT -- -128 到 127(或 0-255 UNSIGNED) SMALLINT -- ±32K MEDIUMINT -- ±8M INT -- ±2B BIGINT -- ±9×10^18 -- 實例:年齡 age TINYINT UNSIGNED -- 0-255 夠用 -- 實例:訂單數量 quantity SMALLINT UNSIGNED -- 0-65535 通常夠用 -- 實例:用戶 ID user_id INT UNSIGNED -- 0-42億,足夠大部分應用 法則 11:字串類型策略 -- VARCHAR vs CHAR CHAR(10) -- 固定長度,適合:國家代碼、郵遞區號 VARCHAR(255) -- 可變長度,適合:姓名、地址 -- 長度設定原則 email VARCHAR(100) -- Email 很少超過 100 phone VARCHAR(20) -- 國際電話格式 username VARCHAR(30) -- 使用者名稱 password_hash CHAR(60) -- bcrypt 固定 60 字元 -- TEXT 類型謹慎使用 TINYTEXT -- 255 bytes TEXT -- 64KB MEDIUMTEXT -- 16MB LONGTEXT -- 4GB(避免使用) 法則 12:時間類型選擇 -- 日期時間存儲 DATE -- 只需要日期 DATETIME -- 需要日期和時間 TIMESTAMP -- 需要時區轉換(自動 UTC) -- 最佳實踐 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -- 避免存儲計算值 age INT -- ❌ 會過期 birthdate DATE -- ✅ 永遠正確 法則 13:金額存儲 -- ❌ 錯誤:浮點數不精確 price FLOAT price DOUBLE -- ✅ 正確:定點數 price DECIMAL(10,2) -- 一般商品 price DECIMAL(13,4) -- 金融交易(Rick 推薦) -- 或者用整數(分) price_cents INT -- 存儲分,顯示時除以 100 五、硬體與系統配置法則 🖥️ 法則 14:磁碟 I/O 預估 傳統硬碟 (HDD):~100 IOPS SSD:~1000 IOPS NVMe SSD:~10000+ IOPS -- 查詢需要的 IOPS 計算 1. 統計查詢的磁碟讀取次數 2. 乘以 QPS(每秒查詢數) 3. 加上寫入的 IOPS(通常是讀取的 20-30%) 法則 15:CPU 核心利用 -- 單一連線只能使用一個 CPU 核心 -- 但是: -- 4 核心 = 可以同時處理 4 個查詢 -- 8 核心 = 可以同時處理 8 個查詢 -- 檢查並發查詢數 SHOW STATUS LIKE 'Threads_running'; -- 如果 > 10,可能有嚴重問題 -- 如果 > CPU 核心數 × 2,必定有問題 法則 16:連線數設定 # 連線數設定 max_connections = 200 # 預設 151,通常夠用 # 計算方式: # max_connections = (可用RAM - 全域緩衝) / 每連線記憶體 # 每連線約需 1-3MB # 監控連線使用 SHOW STATUS LIKE 'Max_used_connections'; # 應該 < max_connections × 0.8 六、架構設計法則 🏗️ 法則 17:正規化 vs 反正規化 -- 適度正規化(通常到第三正規化) -- 但不要過度正規化 -- ❌ 過度正規化範例 -- users, user_emails, user_phones, user_addresses... -- 每個屬性一個表,JOIN 地獄 -- ✅ 實用設計 CREATE TABLE users ( id INT PRIMARY KEY, email VARCHAR(100), phone VARCHAR(20), -- 常用欄位放一起 -- 不常用的才分表 ); -- 有時反正規化是對的 CREATE TABLE order_summary ( order_id INT PRIMARY KEY, total_amount DECIMAL(10,2), -- 冗餘但快 item_count INT, -- 冗餘但實用 -- 避免每次都要 JOIN 和 SUM ); 法則 18:表的數量限制 合理範圍:< 100 個表 警戒線:1000 個表(設計可能有問題) 危險區:10000+ 個表(系統會變慢) -- 太多表的徵兆: -- 1. 每個客戶一個表(錯誤!) -- 2. 每天一個表(考慮分區) -- 3. 過度分割(user_2025_08_25) 法則 19:主鍵設計原則 -- ✅ 好的主鍵 id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY -- ✅ 複合主鍵(多對多關聯) PRIMARY KEY (user_id, role_id) -- ❌ 糟糕的主鍵 email VARCHAR(100) PRIMARY KEY -- 可能變更 uuid CHAR(36) PRIMARY KEY -- 太大,隨機插入 -- UUID 的正確用法 id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, uuid BINARY(16) UNIQUE, -- 轉為二進位存儲 INDEX (uuid) 七、事務與鎖定法則 🔒 法則 20:事務大小原則 -- 事務持續時間:< 5 秒 -- 事務大小:100-1000 筆操作 START TRANSACTION; -- 100-1000 筆操作 COMMIT; -- ❌ 錯誤:超大事務 START TRANSACTION; DELETE FROM logs WHERE created < '2024-01-01'; -- 刪除一年資料 COMMIT; -- ✅ 正確:分批處理 REPEAT DELETE FROM logs WHERE created < '2024-01-01' LIMIT 1000; UNTIL ROW_COUNT() = 0 END REPEAT; 法則 21:鎖定優化 -- 使用 SELECT ... FOR UPDATE 要小心 BEGIN; SELECT * FROM inventory WHERE product_id = 123 FOR UPDATE; -- 鎖定這一行 -- 快速完成操作 UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 123; COMMIT; -- 避免間隙鎖 -- 確保查詢使用唯一索引或主鍵 八、監控與維護法則 📈 法則 22:慢查詢設定 # 慢查詢日誌(必開!) slow_query_log = ON long_query_time = 2 # 2 秒 log_queries_not_using_indexes = ON # 分析慢查詢 # pt-query-digest slow.log 法則 23:關鍵指標監控 -- 1. Buffer Pool 命中率(應該 > 99%) SELECT (1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)) * 100 AS buffer_pool_hit_rate FROM ( SELECT VARIABLE_VALUE AS Innodb_buffer_pool_reads FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads' ) AS reads, ( SELECT VARIABLE_VALUE AS Innodb_buffer_pool_read_requests FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests' ) AS requests; -- 2. 執行緒監控 SHOW STATUS LIKE 'Threads%'; -- Threads_running < 10(正常) -- Threads_running > 30(問題嚴重) -- 3. 臨時表監控 SHOW STATUS LIKE 'Created_tmp%'; -- Created_tmp_disk_tables 應該很少 法則 24:定期維護任務 -- 1. 更新統計資訊(每週) ANALYZE TABLE table_name; -- 2. 優化表(每月,如果有大量刪除) OPTIMIZE TABLE table_name; -- 會鎖表,小心使用 -- 3. 檢查表完整性(每季) CHECK TABLE table_name; 九、安全性法則 🔐 法則 25:最小權限原則 -- 應用程式帳號(最小權限) CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'strong_password'; GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'localhost'; -- 唯讀帳號(報表用) CREATE USER 'readonly'@'%' IDENTIFIED BY 'strong_password'; GRANT SELECT ON mydb.* TO 'readonly'@'%'; -- 備份帳號 CREATE USER 'backup'@'localhost' IDENTIFIED BY 'strong_password'; GRANT SELECT, LOCK TABLES, SHOW VIEW ON *.* TO 'backup'@'localhost'; 法則 26:敏感資料處理 -- ❌ 絕不存儲 -- 信用卡號(使用支付閘道) -- 明文密碼(使用 bcrypt/argon2) -- ✅ 加密存儲 -- 使用應用層加密 -- 或 MySQL 的透明加密(TDE) -- 資料脫敏 SELECT CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS masked_phone, CONCAT(LEFT(email, 2), '***@***', RIGHT(email, 4)) AS masked_email FROM users; 十、反面教材:絕對要避免的事 ⚠️ 不要做的事情清單 -- ❌ 1. SELECT * (浪費資源) SELECT * FROM large_table; -- ❌ 2. 沒有 WHERE 的 UPDATE/DELETE(災難) UPDATE users SET status = 'active'; -- 更新所有! DELETE FROM logs; -- 刪除所有! -- ❌ 3. 使用 OFFSET 做深度分頁 SELECT * FROM posts LIMIT 1000000, 20; -- ❌ 4. 在迴圈中查詢(N+1 問題) for user_id in user_ids: SELECT * FROM orders WHERE user_id = ? -- ❌ 5. 儲存計算值 age INT, -- 會過時 total_orders INT, -- 會不同步 -- ❌ 6. 使用 OR 連接不同欄位 WHERE phone = '123' OR email = '[email protected]' -- 無法有效使用索引 -- ❌ 7. 模糊查詢開頭 WHERE name LIKE '%jack' -- ❌ 8. 強制索引提示 SELECT * FROM users FORCE INDEX (idx_email) -- 讓優化器自己決定 十一、效能問題診斷速查表 🔧 常見問題與解決方案 症狀 可能原因 解決方案 查詢突然變慢 資料量增長 檢查索引、考慮分區 CPU 100% 缺少索引、錯誤查詢 檢查慢查詢日誌 記憶體不足 Buffer Pool 太大 調整為 RAM 的 70% 大量鎖等待 長事務 縮短事務、優化查詢 磁碟 I/O 高 Buffer Pool 太小 增加記憶體或優化查詢 連線數爆滿 連線洩漏 檢查應用程式連線池 快速診斷命令 -- 1. 查看當前查詢 SHOW PROCESSLIST; -- 2. 查看鎖等待 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; -- 3. 查看表狀態 SHOW TABLE STATUS LIKE 'table_name'; -- 4. 查看索引使用情況 SELECT * FROM sys.schema_unused_indexes; -- 5. 查看熱點表 SELECT * FROM sys.schema_table_statistics_with_buffer; 十二、版本遷移注意事項 📝 MySQL 5.7 → 8.0 重要變更 -- 1. 查詢快取已移除 -- 2. 預設字元集改為 utf8mb4 -- 3. 預設認證插件改變 -- 4. GROUP BY 不再隱式排序 -- 5. JSON 功能大幅增強 -- 升級前檢查 SELECT VERSION(); mysqlcheck -u root -p --all-databases --check-upgrade 經驗總結:Rick 的智慧箴言 💡 “Count the disk hits!” - 永遠關注磁碟 I/O “INDEXes are good; COMPOSITE INDEXes are great!” - 複合索引更強大 “Don’t queue it, just do it” - 資料庫不是消息隊列 “Normalize, but don’t over-normalize” - 適度正規化 “70% of RAM for Buffer Pool” - 記憶體配置黃金比例 “Avoid EAV schemas” - 避免實體-屬性-值模式 “Use the smallest practical datatype” - 資料類型最小化 “Transactions should be small and fast” - 事務要短小精悍 “Monitor, but don’t over-monitor” - 監控要適度 “When in doubt, EXPLAIN” - 有疑問就用 EXPLAIN 持續學習資源 📚 Rick James 的 MySQL 文件 - 實戰經驗寶庫 MySQL 官方文檔 - 權威參考 Percona 部落格 - 深度技術文章 High Performance MySQL - 經典書籍 記住:這些法則是指南,不是教條。每個系統都有其特殊性,要根據實際情況調整。但當你不確定時,這些法則能為你指明方向。 ...

2025年8月25日 · 8 min · 1539 words · Jack

MySQL 分區表實戰指南:維護與最佳實踐

MySQL 分區表是一個常被誤解的功能。許多人認為它能顯著提升查詢性能,但實際上,分區表的主要價值在於資料管理而非性能優化。本文基於 Rick James 的 Partition Maintenance 實戰經驗,探討分區表的正確使用方式。 分區表的本質與迷思 什麼是分區表? 分區表將一個邏輯表拆分成多個物理子表,但對應用程式而言仍是單一表。每個分區獨立存儲,可以獨立管理。 -- 一個按月分區的日誌表 CREATE TABLE logs ( id BIGINT AUTO_INCREMENT, log_time DATETIME NOT NULL, message TEXT, PRIMARY KEY (id, log_time) ) PARTITION BY RANGE (TO_DAYS(log_time)) ( PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01')), PARTITION p202502 VALUES LESS THAN (TO_DAYS('2025-03-01')), PARTITION p202503 VALUES LESS THAN (TO_DAYS('2025-04-01')), PARTITION p_future VALUES LESS THAN MAXVALUE ); 常見迷思破解 ❌ 迷思 1:分區表能大幅提升查詢性能 ...

2025年8月25日 · 6 min · 1240 words · Jack

MySQL 索引最佳實踐完整指南

在資料庫優化的世界裡,索引是提升查詢效能的關鍵武器。本文基於 Rick James 的 MySQL Indexing Cookbook 整理出 MySQL 索引設計的核心原則與實戰技巧。 為什麼索引如此重要? 想像一下在沒有目錄的百科全書中查找特定資訊 - 這就是沒有索引的資料表查詢。適當的索引可以將查詢時間從數秒縮短到毫秒級別,特別是在處理大量資料時。 索引設計的黃金法則 1. 三步驟索引設計演算法 建立有效索引的基本步驟: 步驟 1:優先處理等值條件 將 WHERE 子句中與常數比較的欄位放在索引最前面: -- 查詢 SELECT * FROM users WHERE status = 'active' AND department = 'IT'; -- 索引設計 INDEX(status, department) 步驟 2:加入範圍條件欄位 範圍查詢(BETWEEN、>、< 等)的欄位放在等值條件之後: -- 查詢 SELECT * FROM orders WHERE status = 'pending' AND created_date >= '2025-01-01'; -- 索引設計 INDEX(status, created_date) -- 等值在前,範圍在後 步驟 3:考慮 GROUP BY 和 ORDER BY 如果沒有範圍條件,可以將 GROUP BY 或 ORDER BY 的欄位加入索引: ...

2025年8月25日 · 3 min · 576 words · Jack