问题
In my db application I have a requirement for a unique, 4-digit number field for each customer. Up until 9999 I can just use autoincrements, but after that I will have to reuse numbers of customers that have been deleted (there won't be more than 5000 customers at a given time but there may be more than 9999 customers over the lifetime of the system).
Question 1: Is there a (My)SQL statement to find the next reusable free number?
Question 2: If I get the number, assign it to a new customer and save the customer all in one transaction, similar transactions taking place at the same time will be sequentialized by the database so the numbers won't collide, right?
回答1:
You'd be better off storing a table with all 10,000 possible values defined, and an "in-use" flag on each. That way, releasing the number for re-use is a simple update to set "inuse=false".
Also makes finding the lowest available value a simple
SELECT idstring
FROM idstringtable
ORDER BY idstring ASC
WHERE (available = 1)
LIMIT 1
Doing that with appropriate locks/transactions would prevent two or more requests getting the same ID, and since it's a small table, doing a global table lock would not significantly impact performance.
Otherwise, you'd be stuck rummaging around your users table, trying to find the first "gap" in the numbering sequence.
回答2:
If you MUST use this model (and I would recommend against it) then I would create a pool of "available" numbers and when creating the account, just grab the TOP 1 from that. Then, when a user is deleted return the number to the pool.
回答3:
This is to find the first available slot:
select i1.id + 1 as FirstAvailable
from issues i1 left join issues i2 on (i1.id = i2.id - 1)
where i2.id is null
limit 1
This was run against a production Redmine instance to find the first missing id. Adjust accordingly to your needs.
回答4:
The recommendations to use a separate table to track the IDs that are in use will work, but if you do not want to use a separate table to track used IDs you could do a self join to find a gap in the id numbers. The self join is pretty simple:
select top 1 t1.id + 1
from table t1
left join table t2 on t1.id = t2.id - 1
where t1.id < 10000
and t2.id is null
In MS SQL Server I use TOP 1 to get the topmost result, but it may be different syntax in MySQL.
回答5:
The above answer (by Adrian Carneiro) is fantastic, and works unless the table uses a different field as primary key and does NOT have a key for 'id'.
Given a table with a primary key of userid :-
MariaDB [unixua]> select userid, uid from accounts;
+---------+----------+
| userid | uid |
+---------+----------+
| acc0001 | 89814678 |
| acc0002 | 38000474 |
| acc0005 | 38000475 |
| acc0017 | 38000478 |
+---------+----------+
4 rows in set (0.00 sec)
We'd expect the lowest free number to be 38000476.
MariaDB [unixua]> SELECT t1.uid +1 FROM accounts t1
LEFT JOIN accounts t2 ON (t1.uid +1 = t2.uid)
WHERE t2.uid IS NULL AND t1.uid>38000474 LIMIT 1;
+-----------+
| t1.uid +1 |
+-----------+
| 89814679 |
+-----------+
1 row in set (0.00 sec)
But, because MySQL / MariaDB is selecting them in primary key order, this fails, and gives the next highest after "acc001". By adding a key to the uid column and only performing the SELECT on the "uid" column, MySQL/MariaDB will use the index to retrieve data (instead of reading the table). Since the index is "ordered", the result is different :-
MariaDB [unixua]> alter table accounts add unique index (uid);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
MariaDB [unixua]> SELECT t1.uid +1 FROM accounts t1
LEFT JOIN accounts t2 ON (t1.uid +1 = t2.uid)
WHERE t2.uid IS NULL AND t1.uid>38000474 LIMIT 1;
+-----------+
| t1.uid +1 |
+-----------+
| 38000476 |
+-----------+
1 row in set (0.00 sec)
Make sure your table has a key for the Customer ID field (and that customer ID field is numeric).
This works because the optimiser can retrieve all necessary data for the select from the index (aka accounts.myi, not accounts.myd), not the table data.
来源:https://stackoverflow.com/questions/7827649/how-to-find-next-free-unique-4-digit-number