实现mysql的sequence

本文介绍了在MySQL中因缺少sequence语法,而通过创建表和函数来模拟实现序列的方法,包括两种方案,并提到了并发量大时可能存在的锁表问题。

背景

因为做oracle迁移mysql的工作,mysql并不具有sequence语法,所以需要自己想办法模拟实现一个sequence。

方案1

  1. 建一张表e_sys_sequence用来记录序列名称和值
CREATE TABLE `e_sys_sequence` (
    `sequence_name`  varchar(64) NOT NULL COMMENT '序列名称' ,
    `value`  bigint NOT NULL DEFAULT 0 COMMENT '当前值' ,
    PRIMARY KEY (`sequence_name`)
)ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通用序列表' ROW_FORMAT = Dynamic;
  1. 创建一个mysql函数,对序列做增加操作
DELIMITER  $$
CREATE FUNCTION `e_sys_sequence.nextval`(name varchar(50))
RETURNS BIGINT 
BEGIN
    UPDATE e_sys_sequence t, (SELECT @current_val:=`value` FROM e_sys_sequence t2 WHERE t2.sequence_name=sequence_name) t3 SET t.value = t.value + 1 WHERE t.sequence_name =sequence_name AND @current_val=t.value;
    RETURN @current_val+1;
END $$
DELIMITER ;
  1. 往e_sys_sequence表里插入一条初始化数据
<think>我们正在讨论MySQL实现序列(Sequence)功能的方法。根据引用内容,MySQL本身不直接支持序列,但我们可以通过创建表和使用自定义函数来模拟序列的行为。 主要步骤包括: 1. 创建一个序列表,用于存储序列的名称、当前值、步长等信息。 2. 创建函数来获取下一个值(NEXTVAL)和当前值(CURRVAL)。 参考引用[2]和[3],我们可以设计如下: 步骤1:创建序列表 我们可以创建一个表来存储序列的信息,例如: ```sql CREATE TABLE t_sequence ( seq_name VARCHAR(100) NOT NULL COMMENT '序列名称(主键)', current_val BIGINT NOT NULL COMMENT '当前值', increment_val INT NOT NULL DEFAULT 1 COMMENT '步长(增量)', PRIMARY KEY (seq_name) ) COMMENT='序列表'; ``` 步骤2:创建获取下一个值的函数(NEXTVAL) 我们需要一个函数,当传入序列名称时,将当前值增加步长并返回新的值。注意,为了避免并发问题,我们需要使用锁(例如使用FOR UPDATE)来确保操作的原子性。 参考引用[1]中提到的“delimiter”的使用,因为函数体内有分号,我们需要改变定界符。 创建函数`nextval`: ```sql DELIMITER // CREATE FUNCTION nextval(seq_name VARCHAR(100)) RETURNS BIGINT BEGIN DECLARE current_val BIGINT; -- 使用FOR UPDATE加锁,确保在事务中更新时不会出现并发问题 SELECT current_val INTO current_val FROM t_sequence WHERE t_sequence.seq_name = seq_name FOR UPDATE; -- 更新当前值 UPDATE t_sequence SET current_val = current_val + increment_val WHERE t_sequence.seq_name = seq_name; -- 返回更新后的值(即增加后的值) RETURN current_val + (SELECT increment_val FROM t_sequence WHERE t_sequence.seq_name = seq_name); END // DELIMITER ; ``` 注意:上述函数中,我们先查询当前值并加锁,然后更新当前值(加上步长),然后返回新的当前值(即原当前值+步长)。但是,这里有一个问题:在更新之前,我们查询得到的current_val可能已经被其他事务修改?但因为我们使用了FOR UPDATE,所以当前事务会持有锁直到事务结束,因此可以保证安全。 然而,上述函数在事务中才能确保安全(因为FOR UPDATE需要在事务中)。另外,函数中我们返回的是查询到的当前值加上步长,而更新语句也是将当前值更新为原当前值+步长,所以是一致的。 步骤3:创建获取当前值的函数(CURRVAL) 这个函数相对简单,只需查询当前值即可。但是要注意,如果当前会话还没有调用过nextval,那么可能没有当前值(这里我们不考虑这个情况,因为我们的序列是持久化的,只要存在就可以查询到)。 ```sql DELIMITER // CREATE FUNCTION currval(seq_name VARCHAR(100)) RETURNS BIGINT BEGIN DECLARE current_val BIGINT; SELECT current_val INTO current_val FROM t_sequence WHERE t_sequence.seq_name = seq_name; RETURN current_val; END // DELIMITER ; ``` 步骤4:初始化序列 在使用之前,我们需要向序列表中插入序列的初始记录。例如,创建一个名为'user_id_seq'的序列,初始值为0,步长为1: ```sql INSERT INTO t_sequence(seq_name, current_val, increment_val) VALUES ('user_id_seq', 0, 1); ``` 步骤5:使用序列 在插入数据时,我们可以使用nextval函数来获取下一个值: ```sql INSERT INTO users (id, name) VALUES (nextval('user_id_seq'), 'John'); ``` 如果要获取当前序列值(不增加): ```sql SELECT currval('user_id_seq'); ``` 注意:上述实现中,nextval函数在每次调用时都会更新序列表,因此在高并发场景下可能会成为瓶颈。另外,我们使用了FOR UPDATE,这会导致锁竞争。为了优化性能,可以考虑使用连接池或者调整事务的隔离级别。 另外,引用[3]中提到了SETVAL函数,我们可以类似地实现一个设置序列值的函数: ```sql DELIMITER // CREATE FUNCTION setval(seq_name VARCHAR(100), value BIGINT) RETURNS BIGINT BEGIN UPDATE t_sequence SET current_val = value WHERE t_sequence.seq_name = seq_name; RETURN value; END // DELIMITER ; ``` 这样,我们就可以通过setval来设置序列的当前值。 总结: 通过以上步骤,我们在MySQL实现了序列功能。但是需要注意,在高并发环境下,序列表可能成为性能瓶颈,因此需要谨慎使用。另外,也可以考虑使用MySQL的自增列(AUTO_INCREMENT)来满足大部分需求,但序列功能提供了更灵活的控制(例如跨表使用同一个序列)。 相关问题: 1. 如何优化MySQL序列在高并发下的性能? 2. 除了使用序列表,MySQL还有哪些方法可以实现类似序列的功能? 3. 在分布式系统中如何实现全局唯一的序列?</think>### MySQL 实现序列(Sequence)功能的方法 在 MySQL实现序列功能可通过**创建序列表 + 自定义函数**完成,以下是完整实现步骤: --- #### 1. **创建序列表** 存储序列的核心信息: ```sql CREATE TABLE t_sequence ( seq_name VARCHAR(100) NOT NULL PRIMARY KEY COMMENT '序列名称', current_val BIGINT NOT NULL COMMENT '当前值', increment_val INT NOT NULL DEFAULT 1 COMMENT '步长', update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT='序列表'; ``` --- #### 2. **创建关键函数** ##### (1) 获取下一个值 (`nextval`) ```sql DELIMITER // CREATE FUNCTION nextval(seq_name VARCHAR(100)) RETURNS BIGINT BEGIN UPDATE t_sequence SET current_val = current_val + increment_val WHERE seq_name = seq_name; RETURN (SELECT current_val FROM t_sequence WHERE seq_name = seq_name); END // DELIMITER ; ``` ##### (2) 获取当前值 (`currval`) ```sql DELIMITER // CREATE FUNCTION currval(seq_name VARCHAR(100)) RETURNS BIGINT BEGIN RETURN (SELECT current_val FROM t_sequence WHERE seq_name = seq_name); END // DELIMITER ; ``` ##### (3) 设置序列值 (`setval`) ```sql DELIMITER // CREATE FUNCTION setval(seq_name VARCHAR(100), value BIGINT) RETURNS BIGINT BEGIN UPDATE t_sequence SET current_val = value WHERE seq_name = seq_name; RETURN value; END // DELIMITER ; ``` --- #### 3. **初始化序列** ```sql -- 创建名为 'order_id_seq' 的序列,初始值=1000,步长=2 INSERT INTO t_sequence(seq_name, current_val, increment_val) VALUES ('order_id_seq', 1000, 2); ``` --- #### 4. **使用序列** ```sql -- 获取下一个订单ID (返回1002) SELECT nextval('order_id_seq'); -- 插入数据时使用序列 INSERT INTO orders (id, product) VALUES (nextval('order_id_seq'), 'Laptop'); -- 查询当前序列值 (返回1002) SELECT currval('order_id_seq'); ``` --- ### 关键注意事项 1. **线程安全** `nextval` 函数中的 `UPDATE` 语句自带行级锁,确保高并发下不会重复取值[^1]。 2. **性能优化** - 对 `t_sequence` 表的 `seq_name` 字段建立索引 - 使用缓存层(如 Redis)暂存序列值,减少数据库压力 3. **替代方案** - 自增列 (`AUTO_INCREMENT`):适用于单表主键 - UUID:全局唯一但无序 - 分布式ID生成器(如 Snowflake) --- ### 典型应用场景 1. 跨多表的唯一业务ID(如订单号) 2. 需要灵活重置的编号系统 3. 分库分表环境下的全局唯一ID ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值