MySQL 如何實現一個只有一行數據的表

大家好,我是隻談技術不剪髮的 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 新增的函數索引甚至不需要創建額外的字段就可以實現相同的功能。

除了本文使用的方法之外,你還有沒有其他的實現方法?歡迎關注❤️、評論📝、點贊👍

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章