你好,夥伴!
今天,我們來談談什麼是資料庫遷移以及為什麼它們如此重要。在今天的世界裡,任何對資料庫的變更都應該小心謹慎並按照特定的流程進行,這並不意外。理想情況下,這些步驟應該整合到我們的 CI/CD 流程中,以便一切自動運行。
這是我們的議程:
- 問題是什麼?
- 我們如何解決它?
- 一個簡單的例子
- 一個更複雜的例子
- 建議
- 結果
- 結論
問題是什麼?
如果你的團隊從未處理過資料庫遷移,並且你不完全確定為什麼需要它們,我們來澄清一下。如果你已經知道基本概念,請隨意跳過。
主要挑戰
當我們對資料庫進行“計劃性”和“平穩”的變更時,我們需要保持 服務可用性 並滿足 SLA 要求(以免用戶遭受停機或延遲)。想像一下,你想在一個有 500 萬用戶的表中更改一個列的類型。如果你“直接”這樣做(例如,簡單地運行 ALTER TABLE
而不做準備),該表可能會被鎖定相當長的時間——而你的用戶將無法使用服務。
為了避免這種麻煩,請遵循兩條規則:
- 以不鎖定表的方式應用遷移(或至少最小化鎖定)。
- 如果您需要更改列类型,通常最容易的方法是先创建一个具有正确类型的新列,然后再删除旧列。
另一个问题:版本控制和回滚
有时您需要回滚迁移。
手动执行这个操作——进入生产数据库并处理数据——不仅风险高,而且如果您没有直接访问权限的话,这也很可能无法实现。这就是专用迁移工具派上用场的地方。它们可以让您清晰地应用更改并在必要时撤销它们。
我们该如何解决?使用正确的工具
每种语言和生态系统都有自己的迁移工具:
- 对于Java,常见的有Liquibase或Flyway。
- 对于Go,一个常见选择是goose(我们将在这里介绍)。
- 等等。
Goose:它是什么以及为什么有用
Goose是一个轻量级的Go实用工具,可帮助您自动管理迁移。它提供:
- 简单性。最小依赖关系和迁移的透明文件结构。
- 多功能性。支持各种数据库驱动程序(PostgreSQL、MySQL、SQLite等)。
- 灵活性。可以使用SQL或Go代码编写迁移。
安装Goose
go install github.com/pressly/goose/v3/cmd/goose@latest
它是如何運作的:遷移結構
默認情況下,Goose會在db/migrations
目錄中尋找遷移文件。每個遷移文件都遵循這個格式:
NNN_migration_name.(sql|go)
NNN
是遷移編號(例如:001
,002
,等等)。- 之後,您可以使用任何描述性名稱,例如
init_schema
。 - 擴展名可以是
.sql
或.go
。
SQL遷移的示例
文件:001_init_schema.sql
:
-- +goose Up
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now()
);
-- +goose Down
DROP TABLE users;
我們的第一個示例
更改列類型(字符串→整數)
假設我們有一個帶有類型VARCHAR(255)
的users
表中的age
列。現在我們想將其更改為INTEGER
。這是遷移可能看起來像的地方(文件005_change_column_type.sql
):
-- +goose Up
ALTER TABLE users ALTER COLUMN age TYPE INTEGER USING (age::INTEGER);
-- +goose Down
ALTER TABLE users ALTER COLUMN age TYPE VARCHAR(255) USING (age::TEXT);
這裡發生了什麼:
-
向上遷移
- 我們將
age
列更改為INTEGER
。USING (age::INTEGER)
子句告訴PostgreSQL如何將現有數據轉換為新類型。 - 請注意,如果
age
中有任何非數字數據,此遷移將失敗。在這種情況下,您將需要一種更複雜的策略(請參見下文)。
- 我們將
-
回退遷移
- 如果我們回退,將
age
恢復為VARCHAR(255)
。 - 我們再次使用
USING (age::TEXT)
將INTEGER
轉換回文本。
- 如果我們回退,將
第二種和複雜情況:多步遷移
如果 age
列可能包含混亂數據(不僅僅是數字),則安全起見,最好分幾個步驟執行:
- 添加一個新的列 (
age_int
),類型為INTEGER
。 - 將有效數據複製到新列中,處理或刪除無效條目。
- 刪除舊列。
-- +goose Up
-- Step 1: Add a new column
ALTER TABLE users ADD COLUMN age_int INTEGER;
-- Step 2: Try to move data over
UPDATE users
SET age_int = CASE
WHEN age ~ '^[0-9]+$' THEN age::INTEGER
ELSE NULL
END;
-- (optional) remove rows where data couldn’t be converted
-- DELETE FROM users WHERE age_int IS NULL;
-- Step 3: Drop the old column
ALTER TABLE users DROP COLUMN age;
-- +goose Down
-- Step 1: Recreate the old column
ALTER TABLE users ADD COLUMN age VARCHAR(255);
-- Step 2: Copy data back
UPDATE users
SET age = age_int::TEXT;
-- Step 3: Drop the new column
ALTER TABLE users DROP COLUMN age_int;
為了允許正確的回退,Down
部分只是反向鏡像操作。
自動化是關鍵
為了節省時間,將遷移命令添加到 Makefile(或任何其他構建系統)中非常方便。以下是一個包含主要 Goose 命令的示例 Makefile 用於 PostgreSQL。
假設:
- 數據庫的 DSN 為
postgres://user:password@localhost:5432/dbname?sslmode=disable
。 - 遷移文件位於
db/migrations
。
# File: Makefile
DB_DSN = "postgres://user:password@localhost:5432/dbname?sslmode=disable"
MIGRATIONS_DIR = db/migrations
# Install Goose (run once)
install-goose:
go install github.com/pressly/goose/v3/cmd/goose@latest
# Create a new SQL migration file
new-migration:
ifndef NAME
$(error Usage: make new-migration NAME=your_migration_name)
endif
goose -dir $(MIGRATIONS_DIR) create $(NAME) sql
# Apply all pending migrations
migrate-up:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) up
# Roll back the last migration
migrate-down:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) down
# Roll back all migrations (be careful in production!)
migrate-reset:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) reset
# Check migration status
migrate-status:
goose -dir $(MIGRATIONS_DIR) postgres $(DB_DSN) status
如何使用?
1. 創建新的遷移(SQL 文件)。這將生成一個文件db/migrations/002_add_orders_table.sql
。
make new-migration NAME=add_orders_table
2. 應用所有遷移。Goose 將在您的數據庫中創建一個schema_migrations
表(如果尚不存在),並按升序應用任何新的遷移。
make migrate-up
3. 回滾最後一個遷移。只回滾最後一個。
make migrate-down
4. 回滾所有遷移(在生產環境中謹慎使用)。完全重置。
make migrate-reset
5. 檢查遷移狀態。
make migrate-status
輸出示例:
$ goose status
$ Applied At Migration
$ =======================================
$ Sun Jan 6 11:25:03 2013 -- 001_basics.sql
$ Sun Jan 6 11:25:03 2013 -- 002_next.sql
$ Pending -- 003_and_again.go
總結
通過使用遷移工具和 Makefile,我們可以:
- 限制對生產數據庫的直接訪問,僅通過遷移進行更改。
- 輕鬆跟踪數據庫版本並在出現問題時回滾它們。
- 保持單一、一致的數據庫變更歷史。
- 執行“平滑”的遷移,不會在微服務世界中破壞正在運行的生產環境。
- 獲得額外的驗證 — 每個更改都將通過 PR 和代碼審查流程(假設您已經設置了這些設置)。
另一個優勢是很容易將所有這些命令集成到您的 CI/CD 流水線中。請記住 — 安全高於一切。
例如:
jobs
migrate
runs-on ubuntu-latest
steps
name Install Goose
run
make install-goose
name Run database migrations
env
DB_DSN $ secrets.DATABASE_URL
run
make migrate-up
結論和提示
這些主要觀念非常簡單:
- 保持遷移小而頻繁。這樣更容易審查、測試,並在需要時回滾。
- 在所有環境中使用相同的工具,這樣開發、測試和生產環境就能保持同步。
- 將遷移整合到CI/CD中,這樣您就不會依賴任何人手動運行它們。
這樣一來,您將擁有一個可靠且受控的流程來更改您的數據庫結構 — 這不會影響生產環境,並且讓您在出現問題時能夠快速響應。
祝您在遷移中好運!
感謝您的閱讀!
Source:
https://dzone.com/articles/goose-as-crucial-tool-for-your-service