如何确保唯一ID在Rails中确实是唯一的?

时间:2022-11-25 12:58:56

I want to replace an object's url from /object/123 to /object/f8a3b2, so instead of using the id column I'm generating an uid column manually when the object is created.

我想将对象的url从/ object / 123替换为/ object / f8a3b2,因此我不是使用id列而是在创建对象时手动生成uid列。

I can use the uuid libraries available to generate uid, but how do I make sure that the generated value is always unique?

我可以使用可用的uuid库来生成uid,但是如何确保生成的值始终是唯一的?

I have a unique index set on the uid column, so if it's not unique, it should throw back an error - can I catch that on the model level, create another uid, and try again?

我在uid列上有一个唯一的索引集,所以如果它不是唯一的,它应该抛出一个错误 - 我可以在模型级别捕获它,创建另一个uid,然后再试一次吗?

4 个解决方案

#1


3  

There's probably better ways to do this, but the first thing that popped in to my head was making use of the validates_uniqueness_of method on your active record model, as you were suggesting.

可能有更好的方法来做到这一点,但是我想到的第一件事就是在你的活动记录模型上使用validates_uniqueness_of方法。

In your model:

在你的模型中:

class MyUniqueModel < ActiveRecord::Base
    validates_uniqueness_of :uuid_column
end

In your controller

在你的控制器中

@uniqueObject = MyUniqueModel.new(modelParams)
@uniqueObject.uuid_column = generate_uuid() # whatever method you use for generating your uuid
while !uniqueObject.save # if at first it didn't save...
    uniqueObject.uuid_column = generate_uuid() # try, try again
end # should break out of the loop if the save succeeded.

If you have other validation on your model, then the problem you'll encounter with my suggestion is determining whether the save failure was caused by your apparently non-unique uuid, or some other validation error. This means you'll have to filter the loop body by the failed column to figure out whether you should bother re-generating another UUID.

如果您对模型进行了其他验证,那么您在我的建议中遇到的问题是确定保存失败是由您显然非唯一的uuid还是其他一些验证错误引起的。这意味着您必须通过失败的列过滤循环体,以确定是否应该重新生成另一个UUID。

#2


1  

Regarding the uuid gem

If you're using the uuid gem you don't have to worry about uniqueness, as this has been taken care of for you. However, uuid will generate long, 128-bit unique IDs. So, if you want something that's shorter, you might not want to use it.

如果你使用的是uuid gem,你不必担心它的独特性,因为这已经为你服务了。但是,uuid将生成长128位的唯一ID。所以,如果你想要更短的东西,你可能不想使用它。

A simpler way, if your needs are simple

If you want, say, 8-digit unique codes which are also random, you can see an implementation of such a system in my cortex project under the new_unique_code method.

如果你想要8位唯一代码也是随机的,你可以在new_unique_code方法下的cortex项目中看到这样一个系统的实现。

What this does is generate an 8-digit alpha-numeric code to represent a resource in the app. After generating a given code I try to do a .find_by_code, and if that returns nil I know that it's NOT already in use.

这样做是生成一个8位字母数字代码来表示应用程序中的资源。在生成给定代码后,我尝试执行.find_by_code,如果返回nil,我知道它还没有被使用。

In my particular case, 8-digit alpha numeric codes give me a keyspace of some many millions of possible codes (I forget how many). Based on the use case of my app, the chances of a code collision (the percentage of used codes vs. available codes) are small enough to risk possibly needing to generate a new code more than once for a given resource.

在我的特殊情况下,8位字母数字代码给我一个数百万可能代码的密钥空间(我忘了多少)。根据我的应用程序的使用情况,代码冲突的可能性(使用的代码与可用代码的百分比)足够小,可能需要为给定资源多次生成新代码。

The result of this is that needing to generate a new code more than once for a given resource is extremely rare, and as such, takes up almost no additional time.

结果是需要为给定资源多次生成新代码是非常罕见的,因此几乎不占用额外的时间。

What about multiple app servers/threads in the app?

One limitation of my solution is that if you have multiple app servers, but a single DB server, it's possible to have two independent app servers generate the same code before either of them creates the new resource record in the centralized DB. This creates a situation where it's possible to have code collisions and all but the first record saved to the DB will be marked invalid (due to a uniqueness constraint on the code column). This particular app only runs (and will probably only ever run) on a single app server instance. This is a known tradeoff that I'm aware of, so I can risk it.

我的解决方案的一个限制是,如果您有多个应用服务器,但只有一个数据库服务器,则可以让两个独立的应用服务器生成相同的代码,然后才能在集中式数据库中创建新的资源记录。这会产生一种情况,即可能存在代码冲突,除了保存到数据库的第一条记录之外的所有记录都将被标记为无效(由于代码列上的唯一性约束)。此特定应用程序仅在单个应用服务器实例上运行(并且可能只会运行)。这是我所知道的已知权衡,所以我可以冒险。

But, if I wanted to solve this problem, I would limit each app server's range of random codes such that they could never accidentally overlap. For example, let's say I'm generating random codes that come out as 8-digit numeric codes between the values 00000000-99999999. If I had, say, three app servers running this app, I would simply limit server #1 to generating codes between 00000000-33333333, server #2 to 33333334-66666666, and server #3 to 66666667-99999999. This way they would be guaranteed never to overlap codes, and you could still generate unique, random keys all day long.

但是,如果我想解决这个问题,我会限制每个应用服务器的随机代码范围,以便它们永远不会意外重叠。例如,假设我生成的随机代码在值00000000-99999999之间以8位数字代码形式出现。如果我有三个运行此应用程序的应用服务器,我只需将服务器#1限制为生成代码00000000-33333333,服务器#2到33333334-66666666,服务器#3到66666667-99999999。这样他们就可以保证永远不会重叠代码,你仍然可以整天生成独特的随机密钥。

How exactly you would limit a given server to a given range is a bit off topic, but some of the available options are:

如何将给定服务器限制在给定范围内有点偏离主题,但有些可用选项是:

  1. Set a config setting within each app server manually. This is manual (and therefore brittle), but if you have a stable number of app servers that won't be changing, this is probably fine.
  2. 手动设置每个应用服务器中的配置设置。这是手动的(因此很脆弱),但如果你有一定数量的app服务器不会改变,这可能就好了。

  3. Get the app servers to talk to one another and negotiate what their random number ranges should be.
  4. 让应用服务器相互通信并协商其随机数范围应该是什么。

  5. Give up, realize that this is a complex problem that has been solved by very smart people, that you don't need to recreate the wheel, and just use UUID.
  6. 放弃,意识到这是一个非常聪明的人已经解决的复杂问题,你不需要重新创建*,只需使用UUID。

  7. Consider using a centralized unique ID generator that runs as a network service, such as Twitter Snowflake
  8. 考虑使用作为网络服务运行的集中式唯一ID生成器,例如Twitter Snowflake

#3


0  

Using Rails Validations to validate the uniqueness:

使用Rails验证来验证唯一性:

class MyModel < ActiveRecord::Base
  validates :id, uniqueness: true
end

#4


0  

You should use

你应该用

validates :uid, :uniqueness => true

I must add a warning to this advice since there is still a scenario where you will get an error from your DB for duplicate uid column value. You can find more about it in Michael Hartl's RoR tutorial

我必须在此建议中添加警告,因为仍有一种情况会导致数据库因重复的uid列值而出错。您可以在Michael Hartl的RoR教程中找到更多相关信息

In order to catch and retry saving upon duplicate uid you could do something like:

为了捕获并重试在重复的uid上保存,您可以执行以下操作:

model = Model.create(:uid => generate_uid)
if model.errors[:uid].any?
  model.create(:uid => generate_uid)
end

#1


3  

There's probably better ways to do this, but the first thing that popped in to my head was making use of the validates_uniqueness_of method on your active record model, as you were suggesting.

可能有更好的方法来做到这一点,但是我想到的第一件事就是在你的活动记录模型上使用validates_uniqueness_of方法。

In your model:

在你的模型中:

class MyUniqueModel < ActiveRecord::Base
    validates_uniqueness_of :uuid_column
end

In your controller

在你的控制器中

@uniqueObject = MyUniqueModel.new(modelParams)
@uniqueObject.uuid_column = generate_uuid() # whatever method you use for generating your uuid
while !uniqueObject.save # if at first it didn't save...
    uniqueObject.uuid_column = generate_uuid() # try, try again
end # should break out of the loop if the save succeeded.

If you have other validation on your model, then the problem you'll encounter with my suggestion is determining whether the save failure was caused by your apparently non-unique uuid, or some other validation error. This means you'll have to filter the loop body by the failed column to figure out whether you should bother re-generating another UUID.

如果您对模型进行了其他验证,那么您在我的建议中遇到的问题是确定保存失败是由您显然非唯一的uuid还是其他一些验证错误引起的。这意味着您必须通过失败的列过滤循环体,以确定是否应该重新生成另一个UUID。

#2


1  

Regarding the uuid gem

If you're using the uuid gem you don't have to worry about uniqueness, as this has been taken care of for you. However, uuid will generate long, 128-bit unique IDs. So, if you want something that's shorter, you might not want to use it.

如果你使用的是uuid gem,你不必担心它的独特性,因为这已经为你服务了。但是,uuid将生成长128位的唯一ID。所以,如果你想要更短的东西,你可能不想使用它。

A simpler way, if your needs are simple

If you want, say, 8-digit unique codes which are also random, you can see an implementation of such a system in my cortex project under the new_unique_code method.

如果你想要8位唯一代码也是随机的,你可以在new_unique_code方法下的cortex项目中看到这样一个系统的实现。

What this does is generate an 8-digit alpha-numeric code to represent a resource in the app. After generating a given code I try to do a .find_by_code, and if that returns nil I know that it's NOT already in use.

这样做是生成一个8位字母数字代码来表示应用程序中的资源。在生成给定代码后,我尝试执行.find_by_code,如果返回nil,我知道它还没有被使用。

In my particular case, 8-digit alpha numeric codes give me a keyspace of some many millions of possible codes (I forget how many). Based on the use case of my app, the chances of a code collision (the percentage of used codes vs. available codes) are small enough to risk possibly needing to generate a new code more than once for a given resource.

在我的特殊情况下,8位字母数字代码给我一个数百万可能代码的密钥空间(我忘了多少)。根据我的应用程序的使用情况,代码冲突的可能性(使用的代码与可用代码的百分比)足够小,可能需要为给定资源多次生成新代码。

The result of this is that needing to generate a new code more than once for a given resource is extremely rare, and as such, takes up almost no additional time.

结果是需要为给定资源多次生成新代码是非常罕见的,因此几乎不占用额外的时间。

What about multiple app servers/threads in the app?

One limitation of my solution is that if you have multiple app servers, but a single DB server, it's possible to have two independent app servers generate the same code before either of them creates the new resource record in the centralized DB. This creates a situation where it's possible to have code collisions and all but the first record saved to the DB will be marked invalid (due to a uniqueness constraint on the code column). This particular app only runs (and will probably only ever run) on a single app server instance. This is a known tradeoff that I'm aware of, so I can risk it.

我的解决方案的一个限制是,如果您有多个应用服务器,但只有一个数据库服务器,则可以让两个独立的应用服务器生成相同的代码,然后才能在集中式数据库中创建新的资源记录。这会产生一种情况,即可能存在代码冲突,除了保存到数据库的第一条记录之外的所有记录都将被标记为无效(由于代码列上的唯一性约束)。此特定应用程序仅在单个应用服务器实例上运行(并且可能只会运行)。这是我所知道的已知权衡,所以我可以冒险。

But, if I wanted to solve this problem, I would limit each app server's range of random codes such that they could never accidentally overlap. For example, let's say I'm generating random codes that come out as 8-digit numeric codes between the values 00000000-99999999. If I had, say, three app servers running this app, I would simply limit server #1 to generating codes between 00000000-33333333, server #2 to 33333334-66666666, and server #3 to 66666667-99999999. This way they would be guaranteed never to overlap codes, and you could still generate unique, random keys all day long.

但是,如果我想解决这个问题,我会限制每个应用服务器的随机代码范围,以便它们永远不会意外重叠。例如,假设我生成的随机代码在值00000000-99999999之间以8位数字代码形式出现。如果我有三个运行此应用程序的应用服务器,我只需将服务器#1限制为生成代码00000000-33333333,服务器#2到33333334-66666666,服务器#3到66666667-99999999。这样他们就可以保证永远不会重叠代码,你仍然可以整天生成独特的随机密钥。

How exactly you would limit a given server to a given range is a bit off topic, but some of the available options are:

如何将给定服务器限制在给定范围内有点偏离主题,但有些可用选项是:

  1. Set a config setting within each app server manually. This is manual (and therefore brittle), but if you have a stable number of app servers that won't be changing, this is probably fine.
  2. 手动设置每个应用服务器中的配置设置。这是手动的(因此很脆弱),但如果你有一定数量的app服务器不会改变,这可能就好了。

  3. Get the app servers to talk to one another and negotiate what their random number ranges should be.
  4. 让应用服务器相互通信并协商其随机数范围应该是什么。

  5. Give up, realize that this is a complex problem that has been solved by very smart people, that you don't need to recreate the wheel, and just use UUID.
  6. 放弃,意识到这是一个非常聪明的人已经解决的复杂问题,你不需要重新创建*,只需使用UUID。

  7. Consider using a centralized unique ID generator that runs as a network service, such as Twitter Snowflake
  8. 考虑使用作为网络服务运行的集中式唯一ID生成器,例如Twitter Snowflake

#3


0  

Using Rails Validations to validate the uniqueness:

使用Rails验证来验证唯一性:

class MyModel < ActiveRecord::Base
  validates :id, uniqueness: true
end

#4


0  

You should use

你应该用

validates :uid, :uniqueness => true

I must add a warning to this advice since there is still a scenario where you will get an error from your DB for duplicate uid column value. You can find more about it in Michael Hartl's RoR tutorial

我必须在此建议中添加警告,因为仍有一种情况会导致数据库因重复的uid列值而出错。您可以在Michael Hartl的RoR教程中找到更多相关信息

In order to catch and retry saving upon duplicate uid you could do something like:

为了捕获并重试在重复的uid上保存,您可以执行以下操作:

model = Model.create(:uid => generate_uid)
if model.errors[:uid].any?
  model.create(:uid => generate_uid)
end