大家好,我是隻談技術不剪髮的 Tony 老師。我們在之前的文章中介紹了如何在 PostgreSQL 中實現一個只能存儲一行數據的表。今天我們來看看如何在 MySQL 中實現相同的功能。
只有一行數據的表通常被用於某些特定的功能,例如存儲系統的版本或者配置信息,全局計數器,心跳錶等。假如我們有一個表 t_visit,用於記錄網頁的訪問量:
create table t_visit(
hits bigint not null,
update_at datetime not null
);
在部署程序時需要生成一條記錄,將訪問量初始化爲 0;以後每次用戶訪問時需要加 1;爲了前端頁面能夠正確顯示,不允許用戶刪除該記錄。這種需求該如何實現?
MySQL 5.6 實現
對於 MySQL 5.6 以及之前的版本,可以通過增加一個 ENUM 枚舉字段和唯一約束限制表中只能存儲一行數據:
alter table t_visit add only_one_row enum('Y') not null default 'Y';
alter table t_visit add constraint uk_visit unique key (only_one_row);
新增加的字段 only_one_row 非空且取值只能爲 Y,同時基於該字段創建了一個唯一約束,因此整個表中只能存儲一行數據。
初始化時插入數據如下:
insert into t_visit(hits, update_at) values (0, now());
select * from t_visit;
hits|update_at |only_one_row|
----|-------------------|------------|
0|2020-07-07 08:05:03|Y |
如果數據已經存在,再次插入數據將會返回錯誤:
insert into t_visit(hits, update_at) values (1, now());
ERROR 1062 (23000): Duplicate entry 'Y' for key 't_visit.uk_visit'
實際上此時應該進行更新操作:
update t_visit
set hits=hits+1,
update_at= now()
where only_one_row = 'Y';
或者,可以使用INSERT ... ON DUPLICATE KEY UPDATE
語句進行統一操作:
-- 清空數據
truncate table t_visit;
-- 第一次插入數據
insert into t_visit(hits, update_at) values (1, now())
on duplicate key update hits=ifnull(hits,0)+1, update_at= now();
select * from t_visit;
hits|update_at |only_one_row|
----|-------------------|------------|
1|2020-07-07 08:22:32|Y |
-- 第 N 次插入數據
insert into t_visit(hits, update_at) values (1, now())
on duplicate key update hits=ifnull(hits,0)+1, update_at= now();
select * from t_visit;
hits|update_at |only_one_row|
----|-------------------|------------|
2|2020-07-07 08:22:59|Y |
爲了防止表中的數據被誤刪除,需要通過一個觸發器來實現禁止刪除操作:
delimiter $$
create trigger tri_disable_visit_del
before delete on t_visit
for each row
begin
signal sqlstate '45000'
set message_text = '禁止刪除訪問信息!';
end$$
delimiter ;
該觸發器是一個針對刪除操作的 BEFORE 觸發器,在任何刪除操作之前返回錯誤信息。創建觸發器之後,當我們嘗試刪除表中的記錄:
delete
from t_visit
where only_one_row = 'Y';
ERROR 1644 (45000): 禁止刪除訪問信息!
select * from t_visit;
hits|update_at |only_one_row|
----|-------------------|------------|
2|2020-07-07 08:22:59|Y |
刪除語句返回了錯誤信息,表中的數據仍然存在。不過,TRUNCATE TABLE 語句仍然可以清除表中的數據,因爲它不會觸發 DML 觸發器。
MySQL 5.7 實現
MySQL 5.7 除了使用前面的方法之外,還可以利用新增加的計算列(Generated Column)加唯一約束的方式。例如:
-- 刪除 t_visit 表
drop table t_visit;
create table t_visit(
hits bigint not null,
update_at datetime not null,
only_one_row int as (1) unique
);
其中,字段 only_one_row 是一個虛擬的計算列,同時具有唯一性。插入數據的結果如下:
insert into t_visit(hits, update_at) values (0, now());
select * from t_visit;
hits|update_at |only_one_row|
----|-------------------|------------|
0|2020-07-07 09:22:13| 1|
insert into t_visit(hits, update_at) values (1, now());
ERROR 1062 (23000): Duplicate entry '1' for key 't_visit.only_one_row'
同樣,我們也可以使用INSERT ... ON DUPLICATE KEY UPDATE
語句進行統一操作,具體參考上文中的 MySQL 5.6 實現。
利用觸發器可以防止表中的數據被誤刪除,具體參考上文中的 MySQL 5.6 實現。
MySQL 8.0 實現
MySQL 8.0.13 之後除了可以使用上面介紹的兩種方法之外,還可以通過函數索引實現相同的功能。例如:
create table t_visit(
hits bigint not null,
update_at datetime not null
);
create unique index uk_visit on t_visit( (1) );
其中,索引 uk_visit 是一個基於常量 1 的函數索引,並且具有唯一性。函數索引實際上是虛擬列索引的擴展,因此兩者非常相似。
插入數據的結果如下:
insert into t_visit(hits, update_at) values (0, now());
select * from t_visit;
hits|update_at |only_one_row|
----|-------------------|------------|
0|2020-07-07 09:22:13| 1|
insert into t_visit(hits, update_at) values (1, now());
ERROR 1062 (23000): Duplicate entry '1' for key 't_visit.uk_visit'
同樣,我們也可以使用INSERT ... ON DUPLICATE KEY UPDATE
語句進行統一操作,具體參考上文中的 MySQL 5.6 實現。
利用觸發器可以防止表中的數據被誤刪除,具體參考上文中的 MySQL 5.6 實現。
總結
在 MySQL 中爲了實現一個只有一行數據的表,可以創建一個數值爲常量的字段,並且基於該字段創建唯一索引。ENUM 枚舉數據類型和計算列都可以用於實現該功能,MySQL 8.0 新增的函數索引甚至不需要創建額外的字段就可以實現相同的功能。
除了本文使用的方法之外,你還有沒有其他的實現方法?歡迎關注❤️、評論📝、點贊👍