2011-10-12 27 views
3

我使用Rails 3.1和PostgreSQL 8.4。假设我想/需要使用GUID主键。一个潜在的缺点是索引碎片。在MS SQL中,推荐的解决方案是使用特殊的顺序GUID。一个approach到顺序GUID是COMBination GUID,它将6字节时间戳替换为GUID末尾的MAC地址部分。这有一些主流的采用:COMBs本地在NHibernate中可用(NHibernate/Id/GuidCombGenerator.cs)。如果我为主键使用GUID,COM​​B GUID是Rails 3.1的一个好主意吗?

我想我已经想通了如何在Rails的COMB的GUID(与UUIDTools 2.1.2宝石的帮助下),但它留下的一些悬而未决的问题:从索引碎片

  • PostgreSQL的是否患PRIMARY KEY是什么类型的UUID?
  • 如果GUID的低6字节是连续的,是否可以避免碎片化?
  • COMB GUID是否以可接受,可靠的方式实现,以在Rails中创建顺序GUID?

感谢您的想法。


create_contacts.rb迁移

class CreateContacts < ActiveRecord::Migration 
    def up 
    create_table :contacts, :id => false do |t| 
     t.column :id, :uuid, :null => false # manually create :id with underlying DB type UUID 
     t.string :first_name 
     t.string :last_name 
     t.string :email 

     t.timestamps 
    end 
    execute "ALTER TABLE contacts ADD PRIMARY KEY (id);" 
    end 

    # Can't use reversible migration because it will try to run 'execute' again 
    def down 
    drop_table :contacts # also drops primary key 
    end 
end 

/app/models/contact.rb

class Contact < ActiveRecord::Base 
    require 'uuid_helper' #rails 3 does not autoload from lib/* 
    include UUIDHelper 

    set_primary_key :id 
end 

/lib/uuid_tools.rb

require 'uuidtools' 

module UUIDHelper 
    def self.included(base) 
    base.class_eval do 
     include InstanceMethods 
     attr_readonly :id  # writable only on a new record 
     before_create :set_uuid 
    end 
    end 

    module InstanceMethods 
    private 
    def set_uuid 
     # MS SQL syntax: CAST(CAST(NEWID() AS BINARY(10)) + CAST(GETDATE() AS BINARY(6)) AS UNIQUEIDENTIFIER) 

     # Get current Time object 
     utc_timestamp = Time.now.utc 

     # Convert to integer with milliseconds: (Seconds since Epoch * 1000) + (6-digit microsecond fraction/1000) 
     utc_timestamp_with_ms_int = (utc_timestamp.tv_sec * 1000) + (utc_timestamp.tv_usec/1000) 

     # Format as hex, minimum of 12 digits, with leading zero. Note that 12 hex digits handles to year 10889 (*). 
     utc_timestamp_with_ms_hexstring = "%012x" % utc_timestamp_with_ms_int 

     # If we supply UUIDTOOLS with a MAC address, it will use that rather than retrieving from system. 
     # Use a regular expression to split into array, then insert ":" characters so it "looks" like a MAC address. 
     UUIDTools::UUID.mac_address = (utc_timestamp_with_ms_hexstring.scan /.{2}/).join(":") 

     # Generate Version 1 UUID (see RFC 4122). 
     comb_guid = UUIDTools::UUID.timestamp_create().to_s 

     # Assign generted COMBination GUID to .id 
     self.id = comb_guid 

     # (*) A note on maximum time handled by 6-byte timestamp that includes milliseconds: 
     # If utc_timestamp_with_ms_hexstring = "FFFFFFFFFFFF" (12 F's), then 
     # Time.at(Float(utc_timestamp_with_ms_hexstring.hex)/1000).utc.iso8601(10) = "10889-08-02T05:31:50.6550292968Z". 
    end 
    end 
end 
+0

回复:“rails 3不会从lib/*自动加载”;它确实如果你做'config.autoload_paths + =%W(#{config.root}/lib)'。 – qerub

回答

4
  • 当PRIMARY KEY是UUID类型时,PostgreSQL是否遭受索引碎片化?

是的,这是可以预料的。但是如果你打算使用不会发生的COMB战略。行将总是按顺序(这不完全正确,但忍受着我)。

此外,原生pgsql UUID与VARCHAR之间的性能为not all that different。还有一点需要考虑。

  • 是不成避免的,如果低6个字节的GUID是连续的?

在我的测试,我发现,UUID1(RFC 4122)是连续的,有已经在生成的UUID添加时间戳。但是,是的,在最后6个字节中添加时间戳会保证排序。无论如何,这就是我所做的,因为显然已经存在的时间戳并不能保证顺序。更多关于COMB here

  • 是梳GUID低于可接受的,可靠的方式来创建Rails的顺序的GUID的实施?

我不使用轨道,但我会告诉你我是如何在Django做的:

import uuid, time 

def uuid1_comb(obj): 
    return uuid.uuid1(node=int(time.time() * 1000)) 

node是一个48位的正整数标识硬件地址。

关于您的实现,使用uuid的主要优点之一是您可以安全地在数据库之外生成它们,因此,使用助手类是实现它的一种有效方法。您始终可以使用外部服务来生成类似snowflake的uuid代码,但此时可能会提前进行优化。

+0

这个问题一直没有答案,我得到了“风滚草”徽章!所以我非常感谢您的关注和答复,这确实有助于我对COMB GUID概念的信心。 (有趣的是,我们都参考了相同的[Informit文章](http://www.informit.com/articles/article.aspx?p=25862)。)你的django实现可能很棒,但它和helper完全不同正在使用。首先,我不记得提供UUID1选项的UUIDTools帮助程序。我仍然想从使用Rails的人那里得到一些确认或更正。 –

+0

我使用了重新排列的这段Python代码的一个变体,因此当第一部分是连续的,而不是最后一部分时,Postgres插入效果更好,因此纪元的有序段位于UUID的开头。另外,如果您在不同服务器上创建GUID,则需要注意其时钟同步以避免冲突。感谢您的有用答案! – mVChr