技术分享 | MySQL 如何限制一张表的记录数

路昭
• 阅读 1389

作者:杨涛涛

资深数据库专家,专研 MySQL 十余年。擅长 MySQL、PostgreSQL、MongoDB 等开源数据库相关的备份恢复、SQL 调优、监控运维、高可用架构设计等。目前任职于爱可生,为各大运营商及银行金融企业提供 MySQL 相关技术支持、MySQL 相关课程培训等工作。

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


背景

本文又是来源于客户咨询的问题: 能否控制单表在一个固定的记录数,比如说1W条,超过不让插入新记录或者说直接抛出错误?

关于这个问题,没有一个简化的答案,比如执行一条命令或者说简单设置一个参数都不能完美解决。接下来我给出一些可选解决方案。

正文

对数据库来讲,一般问题的解决方案无非有两种,一种是在应用端;另外一种是在数据库端。

首先是在数据库端(假设表硬性限制为1W条记录):
一、触发器解决方案:
触发器的思路很简单,每次插入新记录前,检查表记录数是否到达限定数量,数量未到,继续插入;数量达到,先插入一条新记录,再删除最老的记录,或者反着来也行。为了避免每次检测表总记录数全表扫,规划另外一张表,用来做当前表的计数器,插入前,只需查计数器表即可。要实现这个需求,需要两个触发器和一张计数器表。

t1为需要限制记录数的表,t1_count 为计数器表:

mysql:ytt_new>create table t1(id int auto_increment primary key, r1 int);
Query OK, 0 rows affected (0.06 sec)
   
mysql:ytt_new>create table t1_count(cnt smallint unsigned);
Query OK, 0 rows affected (0.04 sec)
   
mysql:ytt_new>insert t1_count set cnt=0;
Query OK, 1 row affected (0.11 sec)

得写两个触发器,一个是插入动作触发:

DELIMITER $$

USE `ytt_new`$$

DROP TRIGGER /*!50032 IF EXISTS */ `tr_t1_insert`$$

CREATE
    /*!50017 DEFINER = 'ytt'@'%' */
    TRIGGER `tr_t1_insert` AFTER INSERT ON `t1` 
    FOR EACH ROW BEGIN
       UPDATE t1_count SET cnt= cnt+1;
    END;
$$

DELIMITER ;

另外一个是删除动作触发:

DELIMITER $$

USE `ytt_new`$$

DROP TRIGGER /*!50032 IF EXISTS */ `tr_t1_delete`$$

CREATE
    /*!50017 DEFINER = 'ytt'@'%' */
    TRIGGER `tr_t1_delete` AFTER DELETE ON `t1` 
    FOR EACH ROW BEGIN
       UPDATE t1_count SET cnt= cnt-1;
    END;
$$

DELIMITER ;

给表t1造1W条数据,达到上限:

mysql:ytt_new>insert t1 (r1) with recursive tmp(a,b) as (select 1,1 union all select a+1,ceil(rand()*20) from tmp where a<10000 ) select b from tmp;
Query OK, 10000 rows affected (0.68 sec)
Records: 10000  Duplicates: 0  Warnings: 0

计数器表 t1_count 记录为1W。

mysql:ytt_new>select cnt from t1_count;
+-------+
| cnt   |
+-------+
| 10000 |
+-------+
1 row in set (0.00 sec)

插入前需要判断计数器表是否到达限制,如果到了这个限制则删除老旧记录先。我写一个存储过程简单理下逻辑:

DELIMITER $$

USE `ytt_new`$$

DROP PROCEDURE IF EXISTS `sp_insert_t1`$$

CREATE DEFINER=`ytt`@`%` PROCEDURE `sp_insert_t1`(
    IN f_r1 INT
    )
BEGIN
      DECLARE v_cnt INT DEFAULT 0;
      SELECT cnt INTO v_cnt FROM t1_count;
      IF v_cnt >=10000 THEN
        DELETE FROM t1 ORDER BY id ASC LIMIT 1;
      END IF;
      INSERT INTO t1(r1) VALUES (f_r1);          
    END$$

DELIMITER ;

此时,调用存储过程即可实现:

mysql:ytt_new>call sp_insert_t1(9999);
Query OK, 1 row affected (0.02 sec)

mysql:ytt_new>select count(*) from t1;
+----------+
| count(*) |
+----------+
|    10000 |
+----------+
1 row in set (0.01 sec)

这个存储过程的处理逻辑也可以继续优化为一次批量处理。 比如每次多缓存一倍的表记录数,判断逻辑变为在2W条以前,只插入新记录,并不删除老记录,当到达2W条后,一次性删除旧的1W条记录。

这种方案有以下几个缺陷:

  1. 计数器表的记录更新是由insert/delete触发,如果对表进行truncate则计数器表不触发更新从而数据不一致。
  2. 对表进行drop 操作则触发器也跟着删除,需要重建触发器,重置计数器表。
  3. 对表写入只能是类似存储过程这样的单一入口,不能是其他入口。
二、分区表解决方案
建立一个 range 分区,第一个分区有1W条记录,第二个分区为默认分区,等表记录数达到限制后,删除第一个分区,重新调整分区定义即可。

分区表初始定义:

mysql:ytt_new>create table t1(id int auto_increment primary key, r1 int) partition by range(id) (partition p1 values less than(10001), partition p_max values less than(maxvalue));
Query OK, 0 rows affected (0.45 sec)

查找第一个分区是否已满:

mysql:ytt_new>select count(*) from t1 partition(p1);
+----------+
| count(*) |
+----------+
|    10000 |
+----------+
1 row in set (0.00 sec)

删除第一个分区,并且重新调整分区表:

mysql:ytt_new>alter table t1 drop partition p1;
Query OK, 0 rows affected (0.06 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql:ytt_new>alter table t1 reorganize partition p_max into (partition p1 values less than (20001), partition p_max values less than (maxvalue));
Query OK, 0 rows affected (0.60 sec)
Records: 0  Duplicates: 0  Warnings: 0

这种方法的优势很明显:

  1. 表插入入口可以很随机,INSERT语句、存储过程、导文件都行。
  2. 删除第一个分区是一个DROP操作,非常快。

但也有缺点:表记录不能有空隙,如果有空隙,就得改变分区表定义。比如把分区p1的最大值改为20001,那即使在这个分区里有一半的记录不连续,也不影响检索分区里的总记录数。

三、通用表空间解决方案
提前计算好这张表1W条记录需要多少磁盘空间,之后在磁盘上划分一个区专门来存放这张表的数据。

挂载划好的分区,添加为 InnoDB 表空间的备选目录(/tmp/mysql/)。

mysql:ytt_new>create tablespace ts1 add datafile '/tmp/mysql/ts1.ibd' engine innodb;
Query OK, 0 rows affected (0.11 sec)
mysql:ytt_new>alter table t1 tablespace ts1;
Query OK, 0 rows affected (0.12 sec)
Records: 0  Duplicates: 0  Warnings: 0

我大致算了下,不是很准确,所以记录上可能有点误差,不过意思已经很明确:等表报 “TABLE IS FULL” 后即可。

mysql:ytt_new>insert t1 (r1) values (200);
ERROR 1114 (HY000): The table 't1' is full

mysql:ytt_new>select count(*) from t1;
+----------+
| count(*) |
+----------+
|    10384 |
+----------+
1 row in set (0.20 sec)

表满后移除表空间,清空表,再插入新记录。

mysql:ytt_new>alter table t1 tablespace innodb_file_per_table;
Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql:ytt_new>drop tablespace ts1;
Query OK, 0 rows affected (0.13 sec)

mysql:ytt_new>truncate table t1;
Query OK, 0 rows affected (0.04 sec)
另外一个就是在应用端处理:
可以提前在应用端缓存表数据,达到限定的记录数后再批量写入数据库端,写入数据库前,先清空表即可。

举个例子: 表t1数据缓存到文件t1.csv,当t1.csv到达1W行时,数据库端清空表数据,导入t1.csv。

结语

之前 MySQL 在 MyISAM 时代,表属性 max_rows 来预估表的记录数,但也不是硬性规定,类似我上面写的使用通用表空间来达到限制表记录数的作用;到了 InnoDB 时代就没有一个直观的方法,更多是靠以上列出来的方法来解决这个问题,具体选哪个方案,还是得看需求。

点赞
收藏
评论区
推荐文章
待兔 待兔
4年前
postgresql和mysql哪个好
postgresql和mysql都是免费且功能强大的开源数据库,很多用户面对这两个库都会有一个问题,那就是哪一个才是最好的开源数据库,MySQL还是PostgreSQL呢?该选择哪一个开源数据库呢?postgresql和mysql哪个好一.PostgreSQL相对于MySQL的优势1、在SQL的标准实现上要比MySQL完善,而且功能实现比较严谨;
Wesley13 Wesley13
3年前
MYSQL监控调优
MySQL监控调优一、Mysql性能介绍1、什么是Mysql?它有什么优点?MySQL是一个关系型数据库管理系统,由瑞典MySQLAB公司开发,目前属于Oracle公司。MySQL是一种关联数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。Mysq
Wesley13 Wesley13
3年前
Mysqlbinlog解析工具
  Mysql的binlog日志作用是用来记录mysql内部增删改查等对mysql数据库有更新的内容的记录(对数据库的改动),对数据库的查询select或show等不会被binlog日志记录;主要用于数据库的主从复制以及增量恢复。mysql的binlog日志必须打开logbin功能才能生存binlog日志\rwrw1mysqlmy
Easter79 Easter79
3年前
TiDB 在 58 集团的应用与实践
作者介绍:刘春雷,58集团高级DBA,负责MySQL和TiDB的运维工作,TUGAmbassador。58集团业务种类繁多,目前包括的业务有58同城、赶集网、安居客、58金融公司、中华英才网、驾校一点通等,数据库种类包括MySQL、Redis、MongoDB、ES、TiDB。我们自己构建了“58云DB平台”,整合
Wesley13 Wesley13
3年前
linux下导入、导出mysql数据库命令
一、导出数据库用mysqldump命令(注意mysql的安装路径,即此命令的路径):1、导出数据和表结构:mysqldumpu用户名p密码数据库名数据库名.sql/usr/local/mysql/bin/mysqldumpurootpabcabc.sql敲回车后会提示输入密码2、只
Wesley13 Wesley13
3年前
mysql 迁移到5.7.22
1.备份数据库mysqldumpurootP3306p123456h172.28.17.26alldatabasesdatabases.sqlmysqlurootp123456 <databases.sql2.安装5.7解压方式安装参考: centos安装mysql5.7
Wesley13 Wesley13
3年前
MySQL高可用及相关解决方案​
主题:MySQL高可用及相关解决方案大纲:1. OracleCloud有哪些亮点2. 推荐什么方式,可以让用户很方便的上云到OracleCloud3. 企业或者用户在使用MySQL非常容易出现或者忽略什么问题4. MySQL有哪些功能可以帮助企业保护用户隐私数据5. MySQL的高可用方案有很多,推荐怎
Wesley13 Wesley13
3年前
MySQL必须搞定的3大问题:高可用、性能调优、底层原理
MySQL作为互联网中非常热门的数据库,在高并发业务场景下,一条好的MySQL语句能为企业节省大量的运作时间和成本,这也是为何互联网大厂面试官最爱考察数据库底层和性能调优的原因。因此,了解其底层原理和架构的设计非常重要,尤其是MySQL的存储引擎,很大程度上决定了MySQL整体的执行效率和工作性能。网上有不少关于MySQL调
Wesley13 Wesley13
3年前
thinkphp 基本配置
12returnarray(34//定义数据库连接信息5'DB\_TYPE''mysql',//指定数据库是mysql67'DB\_HOST''localhost',89'DB\_NAME''uchome',//数据库名1011'DB\_USER''root
3A网络 3A网络
2年前
容器化 | 在 Kubernetes 上部署 RadonDB MySQL 集群
容器化|在Kubernetes上部署RadonDBMySQL集群RadonDBMySQL是一款基于MySQL的开源、高可用、云原生集群解决方案。支持一主多从高可用架构,并具备安全、自动备份、监控告警、自动扩容等全套管理功能。目前已经在生产环境中大规模的使用,包含银行、保险、传统大企业等。RadonDBMySQLKubernetes
子桓 子桓
2年前
DBeaverEE Mac版
DBeaverEE一款功能强大的数据库管理工具,它支持多种数据库管理系统,包括MySQL、PostgreSQL、Oracle、SQLServer、SQLite等。DBeaverEE是DBeaver的企业版,提供了更多高级功能和技术支持。DBeaverEE具