架構背景:緩存一致性難題
讀多寫少場景下,Memcached/Redis 作為前置緩存層可大幅降低 DB 壓力。但**緩存與數據庫雙寫一致性**是經典難題:
- 先寫 DB 再刪緩存?主從延遲導致舊數據被重新讀入緩存
- 先刪緩存再寫 DB?併發讀導致舊數據回種
- 應用層雙寫?代碼侵入性強、易漏、事務回滾時緩存已更新
MySQL 5.1+ 引入 **觸發器** + **UDF (User Defined Function)** 機制,可在數據庫內核層面攔截寫操作,**原子性地**同步更新緩存,實現「數據庫即緩存權威源」。
技術棧組件
- Memcached Server:高性能分佈式內存緩存
- libmemcached:C/C++ 客戶端庫,UDF 依賴
- Memcached Functions for MySQL (UDF):提供
memc_get/set/increment/servers_set 等 SQL 函數
- MySQL 5.1+:觸發器支持
部署步驟詳解
1. 安裝 Memcached
# CentOS/RHEL
yum install memcached libmemcached-devel -y
systemctl enable memcached --now
# Ubuntu/Debian
apt-get install memcached libmemcached-dev -y
systemctl enable memcached --now
2. 安裝 MySQL 5.1+(或 5.5/5.6/5.7)
確保編譯時啟用 --with-plugin-innodb 等,標準安裝即可。
3. 編譯安裝 libmemcached
wget https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz
tar xzf libmemcached-1.0.18.tar.gz
cd libmemcached-1.0.18
./configure --prefix=/usr/local/libmemcached
make && make install
4. 編譯安裝 Memcached Functions for MySQL
wget https://download.tangent.org/memcached_functions_mysql-1.1.tar.gz
tar xzf memcached_functions_mysql-1.1.tar.gz
cd memcached_functions_mysql-1.1
./configure --with-mysql=/usr/local/mysql/bin/mysql_config \
--libdir=/usr/local/mysql/lib/mysql/
make && make install
mysql_config 路徑指向 MySQL 安裝目錄下的配置腳本,--libdir 指向 MySQL 插件目錄(通常為 plugin_dir 變量值)。
5. 在 MySQL 中註冊 UDF 函數
方式一:執行源碼目錄下的 SQL 腳本
mysql -u root -p < sql/install_functions.sql
方式二:運行 Perl 安裝腳本
cd utils
perl install.pl
驗證安裝:
mysql> SELECT memc_servers_set('127.0.0.1:11211');
+-----------------------------------------+
| memc_servers_set('127.0.0.1:11211') |
+-----------------------------------------+
| 1 |
+-----------------------------------------+
mysql> SELECT memc_set('test_key', 'hello');
+-------------------------------+
| memc_set('test_key', 'hello') |
+-------------------------------+
| 1 |
+-------------------------------+
mysql> SELECT memc_get('test_key');
+----------------------+
| memc_get('test_key') |
+----------------------+
| hello |
+----------------------+
核心 UDF 函數速查
| 函數 | 作用 | 返回值 |
memc_servers_set('host:port[,host2:port2...]') | 設置 Memcached 服務器列表(會話級) | 1 成功 / 0 失敗 |
memc_set('key', 'value'[, expiry]) | 設置鍵值,可選過期時間(秒) | 1 成功 / 0 失敗 |
memc_get('key') | 獲取值 | 值字符串 / NULL |
memc_delete('key') | 刪除鍵 | 1 成功 / 0 失敗 |
memc_increment('key'[, offset]) | 原子遞增,返回新值 | 新值 / NULL |
memc_decrement('key'[, offset]) | 原子遞減 | 新值 / NULL |
memc_cas('key', 'value', cas_token) | Compare-And-Swap 原子更新 | 1 成功 / 0 失敗 |
實戰:URL 短鏈接服務自動同步緩存
場景:urls 表存儲長短鏈接映射,插入新記錄時自動分配自增 ID 並同步至 Memcached。
-- 1. 創建表
DROP TABLE IF EXISTS urls;
CREATE TABLE urls (
id INT(3) NOT NULL PRIMARY KEY,
url VARCHAR(64) NOT NULL DEFAULT ''
) ENGINE=InnoDB;
-- 2. 初始化 Memcached 服務器與序列號
SELECT memc_servers_set('127.0.0.1:11211');
SELECT memc_set('urls:sequence', 0); -- 全局自增計數器
-- 3. 創建 BEFORE INSERT 觸發器
DELIMITER $$
DROP TRIGGER IF EXISTS url_mem_insert $$
CREATE TRIGGER url_mem_insert
BEFORE INSERT ON urls
FOR EACH ROW
BEGIN
-- 1. 獲取全局唯一 ID(原子遞增)
SET NEW.id = memc_increment('urls:sequence');
-- 2. 同步寫入緩存:key=urls:id, value=url
SET @dummy = memc_set(CONCAT('urls:', NEW.id), NEW.url);
END $$
DELIMITER ;
-- 4. 測試插入
INSERT INTO urls (url) VALUES ('https://google.com');
INSERT INTO urls (url) VALUES ('https://www.example.com/page');
INSERT INTO urls (url) VALUES ('https://github.com');
-- 5. 驗證
SELECT * FROM urls;
+----+-----------------------------+
| id | url |
+----+-----------------------------+
| 1 | https://google.com |
| 2 | https://www.example.com/page |
| 3 | https://github.com |
+----+-----------------------------+
SELECT memc_get('urls:1'); -- https://google.com
SELECT memc_get('urls:2'); -- https://www.example.com/page
SELECT memc_get('urls:3'); -- https://github.com
進階模式:更新/刪除同步
-- UPDATE 觸發器:同步更新緩存
DROP TRIGGER IF EXISTS url_mem_update $$
CREATE TRIGGER url_mem_update
BEFORE UPDATE ON urls
FOR EACH ROW
BEGIN
IF NEW.url <> OLD.url THEN
SET @dummy = memc_set(CONCAT('urls:', NEW.id), NEW.url);
END IF;
END $$
-- DELETE 觸發器:同步刪除緩存
DROP TRIGGER IF EXISTS url_mem_delete $$
CREATE TRIGGER url_mem_delete
BEFORE DELETE ON urls
FOR EACH ROW
BEGIN
SET @dummy = memc_delete(CONCAT('urls:', OLD.id));
END $$
關鍵優勢與局限性分析
✅ 優勢
- 強一致性:觸發器在同一事務內執行,DB 提交 ⇔ 緩存更新原子綁定
- 零應用侵入:業務代碼無感知,避免雙寫邏輯分散、漏寫、回滾不一致
- 原子操作:
memc_increment 保證 ID 分配無衝突,無需 DB 自增鎖
- 性能極佳:UDF 直接調用 libmemcached C API,無網絡協議解析開銷(本地 Unix Socket 通信)
⚠️ 局限與風險
- Memcached 故障導致寫入失敗/阻塞:觸發器內 UDF 失敗會導致 SQL 執行報錯回滾。**生產必須**:Memcached 高可用集群、UDF 端超時控制、熔斷降級機制
- 主從複製延遲問題:觸發器在主庫執行,從庫重放 Binlog 時**不會再次觸發觸發器**(Row 格式直接應用行變更)。從庫緩存**不會自動同步**!
- 大事務/批量導入性能:每行觸發器調用 UDF,網絡往返累積延遲。批量導入建議暫用
SET sql_log_bin=0 禁用觸發器或改用應用層同步
- UDF 崩潰風險:C 代碼段錯誤會拖垮整個 mysqld 進程。嚴格代碼審計、獨立測試
- MySQL 8.0+ 建議改用:
INSTALL PLUGIN 方式載入 UDF,或遷移至應用層雙寫 + Canal/Maxwell 訂閱 Binlog 異步同步緩存
生產級增強方案
- 異步解耦:觸發器只寫入本地「緩存變更日志表」,獨立異步進程讀取日志批量推送 Memcached/Redis,失敗重試、不阻塞主業務
- 雙寫兜底:關鍵業務應用層仍保留雙寫,觸發器作為「最後一道防線」修正漏寫
- 監控告警:監控 UDF 調用成功率、延遲、Memcached 可用性、主從緩存一致性校驗任務
總結
MySQL 觸發器 + UDF 實現緩存自動同步是一種**數據庫內核級的一致性方案**,適合「寫入頻率中等、強一致性要求高、業務代碼難改造」的存量系統改造。但需清醒認識其**單點故障風險、主從複製不同步、大批量寫入性能瓶頸**等局限。現代雲原生架構推薦:**應用層雙寫 + Binlog 訂閱 (Canal/Debezium) 異步同步緩存**,將一致性責任前移至應用層,數據庫專注存儲核心職責。
- 上一篇文章: MySQL 生產環境優化實戰:硬件選型、編譯參數、my.cnf 核心配置全解析
- 下一篇文章: Windows Server 下 MySQL 安全權限最小化配置指南
Copyright ©
程式碼之家 All Rights Reserved