设置自增 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
......
|