如何选择使用MySQL的第一个可用id?

如何选择使用MySQL的第一个可用id?,mysql,sql,performance,select,Mysql,Sql,Performance,Select,有一个表table有一列column和另一列userId。 TableTable可以有任意数量的具有相同用户ID的行。但是,在SELECT column,userId FROM table的集合中,不应该有重复(column,userId)行的列。 通常会创建、读取、更新、删除和创建这些行。 我希望每个用户id都有它的本地列id,如下所示: +--------+--------+ | column | userId | +--------+--------+ | 1 | 1

有一个表
table
有一列
column
和另一列
userId
。 Table
Table
可以有任意数量的具有相同用户ID的行。但是,在
SELECT column,userId FROM table
的集合中,不应该有重复(column,userId)行的列。 通常会创建、读取、更新、删除和创建这些行。 我希望每个用户id都有它的本地
id,如下所示:

+--------+--------+
| column | userId |
+--------+--------+
|      1 |      1 |
|      2 |      1 |
|      3 |      1 |
|      4 |      1 |
|      5 |      1 |
|    ... |    ... |
|      1 |      2 |
|      2 |      2 |
|      3 |      2 |
|      4 |      2 |
|      5 |      2 |
|    ... |    ... |
+--------+--------+
当删除一行时,我想获取一些
userId
的第一个可用列
column
id。我想:

SELECT AVAILABLE_ID(column)
 FROM table WHERE userId = 1 ORDER BY column ASC LIMIT 1

所以,如果我们看到table
table
的这种状态:

+--------+--------+
| column | userId |
+--------+--------+
|      1 |      1 |
|      2 |      1 |
|      3 |      1 |
|      5 |      1 |
+--------+--------+
我想收到:

+--------+
| column |
+--------+
|      4 |
+--------+
如果我为某个用户ID插入第一行,我希望该列为:

+--------+
| column |
+--------+
|      1 |
+--------+
如果两者之间没有遗漏的间隙,我只想选择下一个可用的
。
另外,表
中包含大量的创建、更新、删除操作,因此我希望任何解决方案能够快速处理数千行或数百万行。
我认为这个问题没有得到优化:

SELECT * FROM (
    SELECT t1.column+1 AS Id
    FROM table t1
    WHERE userId = 1 AND NOT EXISTS(SELECT * FROM table t2 WHERE userId = 1 AND t2.column = t1.column + 1 )
    UNION 
    SELECT 1 AS column
    WHERE userId = 1 AND NOT EXISTS (SELECT * FROM table t3 WHERE userId = 1 AND t3.column = 1)) ot
ORDER BY 1 LIMIT 1
现在,为了更详细地解释我为什么需要这个: 原因纯粹是装腔作势。 我正在做一个战略游戏,玩家可以有军队。部队可以有两种状态:分组或不分组。如果对它们进行分组,则几行将具有相同的组id。然后,我将它们全部相加为一行,并与查询结果集中的其他行合并,其中一些行可以分组,也可以不分组。如果他们是分组的,我希望每个玩家的团队部队相对于玩家的其他部队有唯一的组号。 所以我可以向他们展示:

第一军

第二军

第三集团军

第一百军

等等


这对应用程序的功能来说并不重要,但我发现,有了这样的编号系统,军队更容易记忆和识别,然后,比如说,显示一些“随机”长id数据库,可以很好地跟踪存在的数据,但不能很好地跟踪丢失的数据

您可以通过以下方式找到差距:

select t1.col+1 as avail_col
from mytable as t1
left outer join mytable as t2 
  on t1.userid = t2.userid and t1.col+1 = t2.col
where t1.userid = 1234 /* whatever userid you search for */ 
  and t2.col is null
order by avail_col limit 1;
您需要(userid,col)上的索引来优化它

这个解决方案非常简单,但它有两个缺陷,即当您为给定的userid创建第一行时,它不起作用(除非它不返回任何行,您知道位置1可用),并且随后它将永远不会告诉您位置1是否是第一个可用的间隙

另外,要小心。您的查询可能会找到一个间隙,但在代码插入新行以使用间隙之前,另一个并发请求可能正在执行相同的操作,找到相同的间隙,然后填充它。防止这种情况的唯一方法是:

  • 保证一次处理给定用户ID的数据的请求不超过一个
  • 在选择间隙时,使用锁定指定用户标识的所有行
不清楚为什么需要填补这些空白。在大多数情况下,当我看到类似的问题时,应用程序需要更改其设计以避免填补空白


您在问题中添加了详细信息,希望使用此信息为军队指定名称:

第一陆军、第二陆军、第三陆军

<>你可以考虑创建另一个表“unsidyMyyyNeX”或别的什么。在游戏开始时,每个用户id填写100行

当用户创建陆军时,执行锁定读取以从该表中选取第一个条目,并在插入时将其从表中删除

START TRANSACTION;

INSERT INTO armies (army_name, user_id)
SELECT @army_name := army_name, user_id
FROM unused_army_names 
WHERE user_id = 1234 
ORDER BY army_name LIMIT 1
FOR UPDATE;

DELETE FROM unused_army_names 
WHERE user_id = 1234 AND army_name = @army_name;

COMMIT;
因为我使用
进行更新
,这会在读取行时锁定我选择的行,因此如果另一个并发请求尝试相同的操作,它将停止并等待获取自己的锁。一旦我的第一个事务提交,它将释放锁,而另一个事务将继续。到那时,我已经从未使用的陆军表中删除了陆军4,另一个事务将读取下一个可用的陆军名称

我用a来记住军队的名字,这样我就可以删除它。您还可以通过三个步骤完成此操作:选择以获取陆军名称、插入陆军表、从未使用的陆军名称表中删除

通过使用事务来包装这两个更改(假设您使用支持事务的InnoDB),可以保证它们在其他客户机上显示为单个原子更改。没有人可以看到处于部分完成状态的数据

然后当一支军队丢失时,把它放回去:

START TRANSACTION;

DELETE FROM armies 
WHERE user_id = 1234 AND army_name = ?;

INSERT INTO unused_army_names (army_name, user_id) VALUES (?, 1234);

COMMIT;

我假设在代码的这一点上,您知道哪支军队丢失了,您可以将军队名称作为参数传递给这两个查询。

您可能会得到一个快速查询来完成您想要的任务,但是,您确定您的业务逻辑真的需要这种功能吗?也许您可以告诉我们为什么您真的需要实现这种功能。这将是一个相当复杂的问题,需要进行大量的构建和维护。@TimBiegeleisen,Oksy。@TimBiegeleisen我更新了它来回答您。一个类似的问题:谢谢,我会检查这个。因此,请检查我的更新。
START TRANSACTION;

DELETE FROM armies 
WHERE user_id = 1234 AND army_name = ?;

INSERT INTO unused_army_names (army_name, user_id) VALUES (?, 1234);

COMMIT;