These issues should generally be handled at the lowest possible level, i.e. in the database.
Your changes to the database should be atomic, so the process of changing the database is non-interruptible and either completely succeeds or completely fails. In SQL databases, you can use
START TRANSACTION statements to do this. E.g. you can use a transaction to make a table read-only for other connections for the duration of the transaction. Since transactions involve locks, they do limit performance. But in most scenarios, it is more important to have consistent data than to have corrupted data more quickly.
If you cannot use transactions (e.g. because there are HTTP requests between requesting the number and changing the number), then you can do a test-and-set update: guard the
UPDATE statement by adding a
WHERE clause that asserts that row is in the expected state. For example, you could manually verify that it still contains the previous value, or add a timestamp or version column for more complex data. You must then make sure that you update/increment that column on each change to the row. If this column changed, the update fails.
Related to this is the idea of never updating rows but only adding new records. When querying the table, only the most recent result is selected. The table is now more like a transaction log. However, this is rather close to implementing your own database engine, and there are databases (possibly NoSQL databases) that implement this behaviour directly.
In some scenarios, it is not important that each change is applied and applied in order, but only that the final result is correct. It is then possible to simply let the last update win. But this requires that each update provides the complete data for the record, and doesn't just update one or two fields. This is often the most desirable solution in distributed scenarios where you have to synchronise multiple databases, e.g. data on a server and cached data on a device that may perform changes while offline.