Oracle 物化視圖支援 ON COMMIT FAST REFRESH(提交時增量刷新)。MySQL 無原生物化視圖,但可透過 **觸發器** 在基表 INSERT/UPDATE/DELETE 時同步維護彙總表,實現同等效果。本文提供完整實現代碼。
-- 基表:訂單明細
CREATE TABLE Orders (
order_id INT NOT NULL AUTO_INCREMENT,
product_name VARCHAR(30) NOT NULL,
price DECIMAL(10,0) NOT NULL,
amount SMALLINT NOT NULL,
PRIMARY KEY (order_id)
) ENGINE=InnoDB;
-- 物化視圖表:商品銷售彙總
CREATE TABLE Order_mv (
product_name VARCHAR(30) NOT NULL,
price_sum DECIMAL(8,2) NOT NULL,
amount_sum INT NOT NULL,
price_avg FLOAT NOT NULL,
order_cnt INT NOT NULL,
UNIQUE INDEX (product_name)
) ENGINE=InnoDB;
DELIMITER $$
CREATE TRIGGER tgr_Orders_insert
AFTER INSERT ON Orders
FOR EACH ROW
BEGIN
DECLARE old_price_sum DECIMAL(8,2) DEFAULT 0;
DECLARE old_amount_sum INT DEFAULT 0;
DECLARE old_price_avg FLOAT DEFAULT 0;
DECLARE old_order_cnt INT DEFAULT 0;
SELECT IFNULL(price_sum,0), IFNULL(amount_sum,0), IFNULL(price_avg,0), IFNULL(order_cnt,0)
INTO old_price_sum, old_amount_sum, old_price_avg, old_order_cnt
FROM Order_mv WHERE product_name = NEW.product_name;
SET @new_price_sum = old_price_sum + NEW.price;
SET @new_amount_sum = old_amount_sum + NEW.amount;
SET @new_order_cnt = old_order_cnt + 1;
SET @new_price_avg = @new_price_sum / @new_order_cnt;
REPLACE INTO Order_mv
VALUES (NEW.product_name, @new_price_sum, @new_amount_sum, @new_price_avg, @new_order_cnt);
END$$
DELIMITER ;
DELIMITER $$
CREATE TRIGGER tgr_Orders_update
BEFORE UPDATE ON Orders
FOR EACH ROW
BEGIN
DECLARE old_price_sum DECIMAL(8,2) DEFAULT 0;
DECLARE old_amount_sum INT DEFAULT 0;
DECLARE old_price_avg FLOAT DEFAULT 0;
DECLARE old_order_cnt INT DEFAULT 0;
DECLARE cur_price DECIMAL(10,0) DEFAULT 0;
DECLARE cur_amount SMALLINT DEFAULT 0;
SELECT price, amount INTO cur_price, cur_amount FROM Orders WHERE order_id = NEW.order_id;
SELECT IFNULL(price_sum,0), IFNULL(amount_sum,0), IFNULL(price_avg,0), IFNULL(order_cnt,0)
INTO old_price_sum, old_amount_sum, old_price_avg, old_order_cnt
FROM Order_mv WHERE product_name = NEW.product_name;
SET @new_price_sum = old_price_sum - cur_price + NEW.price;
SET @new_amount_sum = old_amount_sum - cur_amount + NEW.amount;
SET @new_order_cnt = old_order_cnt;
SET @new_price_avg = @new_price_sum / @new_order_cnt;
REPLACE INTO Order_mv
VALUES (NEW.product_name, @new_price_sum, @new_amount_sum, @new_price_avg, @new_order_cnt);
END$$
DELIMITER ;
DELIMITER $$
CREATE TRIGGER tgr_Orders_delete
AFTER DELETE ON Orders
FOR EACH ROW
BEGIN
DECLARE old_price_sum DECIMAL(8,2) DEFAULT 0;
DECLARE old_amount_sum INT DEFAULT 0;
DECLARE old_price_avg FLOAT DEFAULT 0;
DECLARE old_order_cnt INT DEFAULT 0;
DECLARE cur_price DECIMAL(10,0) DEFAULT 0;
DECLARE cur_amount SMALLINT DEFAULT 0;
SELECT price, amount INTO cur_price, cur_amount FROM Orders WHERE order_id = OLD.order_id;
SELECT IFNULL(price_sum,0), IFNULL(amount_sum,0), IFNULL(price_avg,0), IFNULL(order_cnt,0)
INTO old_price_sum, old_amount_sum, old_price_avg, old_order_cnt
FROM Order_mv WHERE product_name = OLD.product_name;
SET @new_price_sum = old_price_sum - OLD.price;
SET @new_amount_sum = old_amount_sum - OLD.amount;
SET @new_order_cnt = old_order_cnt - 1;
IF @new_order_cnt > 0 THEN
SET @new_price_avg = @new_price_sum / @new_order_cnt;
REPLACE INTO Order_mv
VALUES (OLD.product_name, @new_price_sum, @new_amount_sum, @new_price_avg, @new_order_cnt);
ELSE
DELETE FROM Order_mv WHERE product_name = OLD.product_name;
END IF;
END$$
DELIMITER ;
SELECT ... FROM Orders WHERE order_id = OLD.order_id 返回空,導致變數為 0,彙總邏輯失效。需改用 OLD.price/OLD.amount 直接取值(MySQL 觸發器中 OLD 列可直接引用)。GET_LOCK() 應用層鎖序列化REPLACE INTO Order_mv
SELECT product_name, SUM(price), SUM(amount), AVG(price), COUNT(*)
FROM Orders GROUP BY product_name;
| 場景 | 推薦方案 |
|---|---|
| 寫入頻率低、讀取極高、彙總維度固定 | 觸發器物化視圖(本方案) |
| 高併發寫入、彙總維度多變 | ClickHouse/Apache Doris 等 OLAP 引擎 |
| 近實時大屏展示 | 物化視圖 + 定時全量重建校準 |
| 跨庫/異構數據源彙總 | 數據倉庫 ETL / CDC 同步 |