and*_*ieb 2 sql postgresql database-design auto-increment
我想创建一个user_widgets表,该表以user_id和user_widget_id为主键,其中user_widget_id的工作方式类似于串行,只是每个用户的起始地址为1。
是否有一个通用或实用的解决方案?我正在使用PostgreSQL,但也欢迎使用不可知的解决方案。
表格示例:user_widgets
| user_id | user_widget_id | user_widget_name |
+-----------+------------------+----------------------+
| 1 | 1 | Andy's first widget |
+-----------+------------------+----------------------+
| 1 | 2 | Andy's second widget |
+-----------+------------------+----------------------+
| 1 | 3 | Andy's third widget |
+-----------+------------------+----------------------+
| 2 | 1 | Jake's first widget |
+-----------+------------------+----------------------+
| 2 | 2 | Jake's second widget |
+-----------+------------------+----------------------+
| 2 | 3 | Jake's third widget |
+-----------+------------------+----------------------+
| 3 | 1 | Fred's first widget |
+-----------+------------------+----------------------+
Run Code Online (Sandbox Code Playgroud)
我只想包括这种设计的一些原因。
1.信息披露较少,而不仅仅是“默默无闻的安全”
在一个系统中,用户之间不应该彼此了解,他们也不应该知道彼此的widget_id。如果这是库存表,怪异的商业秘密,发票或更敏感的东西,那么他们可以为这些小部件开始拥有自己不受影响的ID集。除了显而易见的例行安全检查之外,这还添加了一个隐式安全层,该表必须同时由小部件ID 和用户ID 过滤。
2.数据导入
应该允许用户从其他系统导入数据,而不必丢弃所有旧ID(如果它们具有整数ID)。
3.清洁度
与我的第一点并没有很大不同,但是我认为创建内容少于其他内容的用户可能会因其小部件ID的大幅变化而感到困惑或烦恼。这当然比功能更肤浅,但仍然可能有价值。
可能的解决方案
答案之一表明应用程序层可以处理此问题。我可以在要增加的用户表上存储next_id列。或者,甚至只计算每个用户的行数,而不允许删除记录(改为使用Delete / deactivated标志)。可以使用触发功能甚至存储过程来完成此操作,而不是在应用程序层中完成吗?
如果您有一张桌子:
CREATE TABLE user_widgets (
user_id int
,user_widget_name text --should probably be a foreign key to a look-up table
PRIMARY KEY (user_id, user_widget_name)
)
Run Code Online (Sandbox Code Playgroud)
您可以user_widget_id动态分配并查询:
WITH x AS (
SELECT *, row_number() OVER (PARTITION BY user_id
ORDER BY user_widget_name) AS user_widget_id
FROM user_widgets
)
SELECT *
FROM x
WHERE user_widget_id = 2;
Run Code Online (Sandbox Code Playgroud)
user_widget_id 在这种情况下,按字母顺序对每个用户应用,并且没有空格,添加,更改或删除条目显然会导致更改。
更稳定(但不是完全稳定):
CREATE TABLE user_widgets (
user_id int
,user_widget_id serial
,user_widget_name
PRIMARY KEY (user_id, user_widget_id)
)
Run Code Online (Sandbox Code Playgroud)
和:
WITH x AS (
SELECT *, row_number() OVER (PARTITION BY user_id
ORDER BY user_widget_id) AS user_widget_nr
FROM user_widgets
)
SELECT *
FROM x
WHERE user_widget_nr = 2;
Run Code Online (Sandbox Code Playgroud)
您可以实施一种机制来计算每个用户的现有小部件。但是您将很难使它对并发写入具有防弹性。您将必须锁定整个表或使用SERIALIZABLE事务处理模式 -两者都是降低性能的真正选择,并且需要其他代码。
但是,如果您保证不删除任何行,则可以采用第二种方法- user_widget_id在表中使用一个序列,该序列为您提供“原始” ID。序列是并发负载的行之有效的解决方案,可以保持相对顺序user_widget_id并快速。您可以使用一个视图来提供对表的访问,该视图user_widget_id用相应的动态替换“ raw” ,user_widget_nr例如上面的查询。
您可以(另外)user_widget_id通过user_widget_nr在下班时间或由您选择的事件触发替换来“实现”无间隙。
为了提高性能,我会user_widget_id从一个很高的数字开始。似乎每个用户只能有几个小部件。
SELECT setval(user_widgets_user_widget_id_seq', 100000);
Run Code Online (Sandbox Code Playgroud)
如果没有足够高的数字可以保证安全,请添加一个标志。使用条件WHERE user_widget_id > 100000可以快速识别“原始” ID。如果表很大,则可能需要使用条件(会很小)添加部分索引。在CASE声明中用于上述视图。并在此语句中“实现” ID:
UPDATE user_widgets w
SET user_widget_id = u.user_widget_nr
FROM (
SELECT user_id, user_widget_id
,row_number() OVER (PARTITION BY user_id
ORDER BY user_widget_id) AS user_widget_nr
FROM user_widgets
WHERE user_widget_id > 100000
) u
WHERE w.user_id = u.user_id
AND w.user_widget_id = u.user_widget_id;
Run Code Online (Sandbox Code Playgroud)
可能REINDEX会VACUUM FULL ANALYZE user_widgets在a 甚至下班时间进行跟进。考虑FILLFACTOR低于100,因为列将至少更新一次。
我当然不会将它留给应用程序。这会引入多个额外的故障点。
| 归档时间: |
|
| 查看次数: |
338 次 |
| 最近记录: |