在某些特定的场景下,我们可能需要利用数据的自增id特性来作为数据集的自增排序查找等操作。或者相同的数据分布在多份存储上。
并且存在对条数据需要通过这个id进行关联,类似于外键特性,这就需要将id和数据分离。
流程:
1 2 3 4 5
| graph LR id-->DB1 id-->DB2 DB1-->合并数据 DB2-->合并数据
|
这里列举了mysql、oracle、mongodb三种数据的自增序列实现方式。
1、mysql的自增id
mysql的自增id是和数据绑定在一起的,无法分开,等于你id的增加是伴随着数据库的数据新增而来。
那有没有办法分开呢?
也是有的,需要借助数据库函数来实现。但是一般不建议使用,需要对函数进行分析加参数修饰,如函数里加关键字READS SQL DATA
(只查询时使用)、DETERMINISTIC
(存在更新时使用),不然数据库会告警,会一定程度影响dml性能。
其原理就是每查询一次更新sequence的值。
具体实现步骤如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| drop table if exists test_sequence; create table test_sequence ( seq_name VARCHAR(50) NOT NULL, current_val bigint NOT NULL, increment_val bigint NOT NULL DEFAULT 1, PRIMARY KEY (seq_name));
INSERT INTO test_sequence VALUES ('user_seq', '0', '1'); INSERT INTO test_sequence VALUES ('class_seq', '0', '1'); INSERT INTO test_sequence VALUES ('school_seq', '0', '1'); INSERT INTO test_sequence VALUES ('data_seq', '0', '1');
create function seq_currval(v_seq_name VARCHAR(50)) return bigint(20) begin declare value integer; set value = 0; select current_val into value from test_sequence where seq_name = v_seq_name; return value; end;
select seq_currval('test_user_seq');
create function seq_nextval (v_seq_name VARCHAR(50)) return bigint(20) begin update test_sequence set current_val = current_val + increment_val where seq_name = v_seq_name; return seq_currval(v_seq_name); end;
select seq_nextval('user_seq');
select * from test_sequence;
|
使用
2、oracle的自增sequence
oracle的自增sequence是独立于数据的,对于迁移维护比较方便。具体使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
create table TEST ( NID int PRIMARY KEY, test1 varchar2(20), test2 varchar2(20), test3 varchar2(20), test4 varchar2(20), test5 varchar2(20) )
create sequence SEQ_TEST minvalue 1 nomaxvalue start with 1 increment by 1 nocycle nocache;
|
然后在项目的mybatis中使用下面的语句赋值到数据id
上即可;
1
| SELECT SEQ_TEST FROM DUAL
|
就是这么的简单,so easy!
3、mongo实现自动增长sequence
严格来说mongodb本身没有自增序列相关的功能,但是我们了解到mongodb提供了原子操作的函数$inc
,既然是原子操作,那么就可以在这上面做文章,自增id也是全局唯一,那么有了原子操作,我们的自增操作就能够满足。在每次进行插入操作的时候,序列值加1,作为本次操作的id。
具体实现如下:
1、定义sequence对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import org.springframework.data.annotation.Id;
public class MongoSequence { @Id private String id; private long nextVal;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public long getNextVal() { return nextVal; }
public void setNextVal(long nextVal) { this.nextVal = nextVal; } }
|
2、创建自增序列工具类
取号工具类MongoAutoIdUtil.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Component public class MongoAutoIdUtil {
@Autowired private MongoTemplate mongoTemplate;
public long getNextSequenceByCol(String collectionName) { MongoSequence sequence = mongoTemplate.findAndModify(Query.query(where("_id").is(collectionName)), new Update().inc("nextVal", 1L), options().upsert(true).returnNew(true), MongoSequence.class); return sequence.getNextVal(); } }
|
有没有发现这里的update操作并没有集合名称,那么具体是更新哪个集合呢?会不会失败?
别担心,如果不设置集合的话,执行的时候mongodb自己会新增一个默认集合名字叫mongoSequence
,所以我们只需要调用这个方法,传入我们想要的序列名即可。然后在mongodb控制台中就能看到mongoSequence
集合中存在了我们想要的需要,每次只要调用这个方法获取的就是自增的id。
3、函数性能
那这个函数的性能怎样呢?
性能的话,我稍微测试了下,50~200个并发取id没有出现id重复的情况,并且自增无误。再高的并发本机4核cpu太弱,测试就不再具有参考性,就作罢了。
官方的解释是inc
与set
性能相当,比$push
快。
在实际运用过程当中也没有出现重复取和自增出错的情况,运行良好。
不过既然选择了mongodb,那么最好利用非关系数据库的特性,发挥它最大的性能,不要将自增id这样的场景放在mongodb上操作。
比如它本身的_id
其实就是个很好的全局唯一并且支持排序的序列,当然我们也可以对_id
进行赋值,设置我们自己的数据类型,比如设置为上面的自增id的数字,但是不推荐。
官方针对_id
的有解释,少量数据量的情况下,对_id
进行赋值和不赋值性能差别不大,但达到一定数据量的时候,_id
自动赋值比手动赋值性能要好,因为需要额外对我们手动赋的值做一次多分片全局唯一性的检查操作,这没有mongodb自己的生成算法来得快。