序列自增步长

设置自增 ID 随机步长,此方式使用触发器实现,每次获取自增 id 后,再随机多获取几个自增值扔掉。

以下仅仅是从数据库的角度考虑如何解决问题,并不是最优解决方案,也不推荐使用。

优点

  • 资源消耗小
  • 用户无法通过注册新账号获取自增 ID 来猜测数据总量 ( 使用固定步长,也容易被用户发现规律 )
  • 避免爬虫自增,拉取大量用户数据 ( 通过网关设置,判断相同 ip 连续打空,封 IP 地址 )

当你需要更安全,或自增 ID 已不满足你的需求时,应该考虑 雪花 ID 和 UUID。

方法

创建表

1
2
3
CREATE TABLE test (
	id SERIAL  -- 主键。自增序列,默认名序列名  test_id_seq
)

设置序列开始值,建议 6 位数开始

或者从 任意自然数 开始,建议预留一定的位置,用于数据库管理员塞入默认数据。

1
SELECT setval('test_id_seq',100000,FALSE)

查询一下,下一个 ID 是不是设定的值

1
SELECT nextval('test_id_seq'::regclass)

设置触发器函数,当你的主键名并非 id 时,需要修改一下函数内 id 为你的主键名

执行此函数会随机扔掉 1-9 个自增数值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
CREATE OR REPLACE FUNCTION seq_func() RETURNS TRIGGER AS $body$
	DECLARE
		-- 获取随机数
		r INTEGER :=  (random()*8)::INTEGER+1;
		t INTEGER := 1;
		-- 获取序列名
		seq_name VARCHAR := pg_get_serial_sequence(TG_TABLE_NAME,'id');
	BEGIN
		-- 	raise notice 'name:%s',seq_name;
		WHILE t<=r LOOP
			PERFORM  nextval(seq_name::regclass);
			t=t+1;
		END LOOP;
		RETURN NEW;
	END;
$body$ LANGUAGE plpgsql;

创建触发器,在插入数据完成后执行。 此处加了一个条件,当插入 ID 小于序列自增起始值时,不触发。小于自增起始值时,一般为数据库管理员手动插入默认数据。

sql 内的 1000000 需要修改为起始值。

1
2
3
4
5
6
7
8
9
-- 创建触发器
CREATE TRIGGER test_id_seq_insert_trigger
	AFTER INSERT ON test
	FOR EACH ROW
	WHEN (NEW.id > 100000)
	EXECUTE FUNCTION seq_func();
	
-- 删除触发器
DROP TRIGGER IF EXISTS test_id_seq_insert ON test;

如果有多个表,仅需要改动 触发器名称表名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
-- 创建第二张表
create table example(
	id SERIAL
)
-- 设置序列起始值,注意此处已更换 序列名称
SELECT setval('temp_id_seq',100000,FALSE)
-- 设置触发器,注意此处已更换 触发器名 和 表名
CREATE TRIGGER example_id_seq_insert_trigger
	AFTER INSERT ON example
	FOR EACH ROW
	WHEN (NEW.id > 100000)
	EXECUTE FUNCTION seq_func();
-- 相关测试,检查是否设置成功

并发测试

 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
func TestInsertTest(t *testing.T) {
	var (
		wg  sync.WaitGroup
		ids = make([]int, 100)
	)
	
  // 并发插入 100 条数据,获取 100 个自增 ID
  for i := 0; i < 100; i++ {
		wg.Add(1)
		go func(i int) {
			defer wg.Done()
			r := new(Test)
			r.K = strconv.Itoa(i)
			_, err := db.InsertOne(r)
			if err != nil {
				require.NoError(t, err)
			}
			ids[i] = r.ID
		}(i)
	}
	wg.Wait()

  // 排序后,输出
	sort.Ints(ids)
	for _, v := range ids {
		fmt.Println("id : ", v)
	}
}

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
id :  1000000
id :  1000001
id :  1000011
id :  1000012
id :  1000013
id :  1000014
id :  1000019
id :  1000036
id :  1000048
id :  1000057
id :  1000068
id :  1000069
......
本文阅读量 次, 总访问量 ,总访客数
Built with Hugo .   Theme Stack designed by Jimmy