【问题标题】:Auto incremented column scoped to user id范围为用户 ID 的自动递增列
【发布时间】:2021-06-04 22:30:53
【问题描述】:

我真的很挣扎如何实现将通过示例进行最佳描述的要求。 尽管我对 postgres 的解决方案感兴趣,但请考虑将以下所有内容都用伪代码编写。

id id_for_user note created_by
1 1 Buy milk 1
1 2 Winter tyres 1
1 3 Read for 1h 1
2 1 Clean dishes 2
2 2 Learn how magnets work 2
INSERT INTO notes VALUES (note: 'Learn icelandic', created_by: 1);
id id_for_user note created_by
1 1 Buy milk 1
2 2 Winter tyres 1
3 3 Read for 1h 1
4 1 Clean dishes 2
5 2 Learn how magnets work 2
6 4 Learn Icelandic 1
INSERT INTO notes VALUES (note: 'Are birds real?', created_by: 2);
id id_for_user note created_by
1 1 Buy milk 1
2 2 Winter tyres 1
3 3 Read for 1h 1
4 1 Clean dishes 2
5 2 Learn how magnets work 2
6 4 Learn Icelandic 1
7 3 Are birds real? 2

我想实现这样的目标:

CREATE TABLE notes (
    id SERIAL,
    id_for_user INT DEFAULT nextval(created_by) -- Dynamic name for sequence so every user gets its own,
    note VARCHAR,
    created_by INT,
    PRIMARY KEY(id, id_for_user),
    CONSTRAINT fk_notes_created_by
      FOREIGN KEY(created_by) 
        REFERENCES users(created_by)
);

让用户1 看到(注意id_for_user 在前端只是id

id note
1 Buy milk
2 Winter tyres
3 Read for 1h
4 Learn Icelandic

还有用户2

id note
1 Clean dishes
2 Learn how magnets work
3 Are birds real?

基本上我想为每个用户设置自动递增的字段。 然后我也可能会根据哪个用户提出请求,通过id_for_user 在后端填充create_by 来查询记录。

这样的事情可能吗?我有哪些选择?我真的很想在数据库级别上有这个逻辑。

https://www.db-fiddle.com/f/6eBvq4VCQPTmmR3W6fCnEm/2

【问题讨论】:

  • 这不是自动递增列的工作方式。
  • @GordonLinoff 很高兴知道如果您忽略该问题,您会推荐什么解决方案?
  • 您可以有一个单独的表来存储“每个用户的最后一个 ID”。然后触发器可以填充值并增加相关表。插入是多线程的吗?如果是这种情况,您需要添加一些隔离(悲观或乐观锁定)。
  • @TheImpaler 我很难相信没有简单的解决方案,但我不是数据库专家。难道我们不能利用我忘记提及的created_at 专栏吗?您知道插入记录的顺序并以某种方式计算新的id_for_user 吗?
  • @Hnus 在查询表时动态计算值很容易:一个简单的ROW_NUMBER() 窗口函数就可以解决问题。我以为您想在插入时保留新 ID。

标签: sql postgresql


【解决方案1】:

尝试使用序列,此对象将控制 ID 的自动数字

示例:

CREATE SEQUENCE sequence_notes1
  INCREMENT BY 1
  MINVALUE 1
  MAXVALUE 100;

  CREATE SEQUENCE sequence_notes2
  INCREMENT BY 1
  MINVALUE 1
  MAXVALUE 100;

CREATE TABLE notes (
  id SERIAL,
  id_for_user INT,
  note VARCHAR,
  created_by INT,
  PRIMARY KEY(id)
);


INSERT INTO notes (id_for_user, note, created_by) VALUES (nextval('sequence_notes1'),'Foo', 1);
INSERT INTO notes (id_for_user, note, created_by) VALUES (nextval('sequence_notes1'),'Moo', 1);
INSERT INTO notes (id_for_user, note, created_by) VALUES (nextval('sequence_notes2'),'Boo', 2);
INSERT INTO notes (id_for_user, note, created_by) VALUES (nextval('sequence_notes2'),'Loo', 2);

【讨论】:

  • 如果它的 postgres 适用于自动增量,我在数据库中使用了这种类型的配置
  • 我提供了您的建议的可运行示例,但它显然不起作用。
  • 我明白了,我的例子是一般的自动数字,但你需要用户,让我想想另一个建议
  • 如果输入带有代码和变量,您可以使用两个序列并使用取决于用户的序列,在示例中,我将序列“sequence_notes1”用于用户 1,“sequence_notes2”用于de user 2,然后为每个案例设置 secuance。这种情况是手动的,但如果使用代码运行可能是动态的变量
【解决方案2】:

您可以有一个单独的表来存储“每个用户的下一个序数值”。然后触发器可以填充值并增加相关表。

例如:

create table usr (
  id int primary key,
  next_ordinal int default 1
);

create table note (
  id int primary key,
  note varchar(100),
  created_by int references usr (id),
  user_ord int
);

create or replace function add_user_ord() returns trigger as $$
begin
  select next_ordinal into new.user_ord from usr where id = new.created_by;
  update usr set next_ordinal = next_ordinal + 1 where id = new.created_by;
  return new;
end;
$$ language plpgsql;
//

create trigger trg_note1 before insert on note 
for each row execute procedure add_user_ord();

然后,触发器将在INSERTs 期间在幕后自动添加正确的序数:

insert into usr (id) values (10), (20);

insert into note (id, note, created_by) values (1, 'Hello', 10);
insert into note (id, note, created_by) values (2, 'Lorem', 20);
insert into note (id, note, created_by) values (3, 'World', 10);
insert into note (id, note, created_by) values (4, 'Ipsum', 20);

结果:

id  note   created_by  user_ord
--  -----  ----------  --------
 1  Hello          10         1
 2  Lorem          20         1
 3  World          10         2
 4  Ipsum          20         2

注意:此解决方案不涉及多线程插入。如果您的应用程序需要这个,您需要为其添加一些隔离(或悲观或乐观锁定)。

【讨论】:

  • 与我现在正常工作的触发器相比如何?当信息已经存在时,我不知道为什么我应该将这些数据存储在新表中,只需要计算它?我不知道多线程插入是否仍然相关,但我可能会选择dbfiddle.uk/…
  • @Hnus 您的解决方案有效。但是,它有两个潜在的缺点,可能对您来说可能很重要,也可能不重要:1)您的解决方案和我的解决方案不处理多线程插入;如果您插入了多个线程,则需要解决此问题。 2)随着表格的增长,您的解决方案可能会逐渐变慢;我建议您添加索引(created_by, id_for_user),以便触发器运行得更快。
【解决方案3】:

在 MySQL 中,MyISAM 存储引擎支持此功能。

https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html 说:

对于 MyISAM 表,您可以在辅助表上指定 AUTO_INCREMENT 多列索引中的列。在这种情况下,生成的值 对于 AUTO_INCREMENT 列计算为 MAX(auto_increment_column) + 1 WHERE 前缀=给定前缀。这是 当您想将数据放入有序组时很有用。

CREATE TABLE animals (
    grp ENUM('fish','mammal','bird') NOT NULL,
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    name CHAR(30) NOT NULL,
    PRIMARY KEY (grp,id)
) ENGINE=MyISAM;

INSERT INTO animals (grp,name) VALUES
    ('mammal','dog'),('mammal','cat'),
    ('bird','penguin'),('fish','lax'),('mammal','whale'),
    ('bird','ostrich');

SELECT * FROM 动物 ORDER BY grp,id;返回:

+--------+----+---------+
| grp    | id | name    |
+--------+----+---------+
| fish   |  1 | lax     |
| mammal |  1 | dog     |
| mammal |  2 | cat     |
| mammal |  3 | whale   |
| bird   |  1 | penguin |
| bird   |  2 | ostrich |
+--------+----+---------+

这在 MyISAM 中起作用的原因是 MyISAM 只支持表级锁定。

在具有行级锁定的存储引擎中,如果您尝试使用像这样工作的主键,则会遇到竞争条件。这就是为什么该线程上的其他人评论说使用触发器实现这一点需要一些悲观的锁定。您必须使用锁定来确保一次只有一个客户端插入,因此它们不会分配相同的值。

这将在高流​​量应用程序中受到限制。 InnoDB 的自动增量的实现方式是允许多个客户端线程同时执行插入的应用程序。

因此您可以使用 MyISAM 或者您可以使用 InnoDB 并发明自己的方式为每个用户分配新的 id,但无论哪种方式都会严重限制您应用的可扩展性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-01
    • 2011-09-17
    • 1970-01-01
    • 1970-01-01
    • 2016-06-16
    • 2010-10-04
    相关资源
    最近更新 更多