来自:http://www.cnblogs.com/leafyoung/archive/2005/10/10/251436.html
对于数据库应用而言, 一个合适的序列号生成器是非常有用的, 用它我们可以方便地给新增的记录的id字段(主键)赋值, 当然我们同样可以用Identity类型主键, 但是假如同时存在父子关系的记录, 那么给子记录赋parent_id就极为不便, 我们必须先将父记录插入到数据库, 然后从数据库中取得自动生成的id, 然后才成给子记录的parent_id字段赋值. 毫无疑问, 假如我们可以给父记录的id直接赋值的话, 问题就简单得多了.但是, 对于一个分布式应用而言, 不经过周密的考虑往往会带来id重复的问题. 在我们公司既有的产品中采用段号来解决这个问题, 就是说为每个用户分配一个足够长的区间(比如10000000)作为可选id, 每个用户使用独立的区间的id一般不会带来问题, 但是作为一个完美主义者来说, 不能不考虑到一个极端情况, 假如区间重叠了怎么办? 而且这样的分配方式无疑极大地浪费了id资源, 是否有更好的解决方案呢?
前几天阅读Martin Fowler的大作<Patterns of Enterprise Application Architecture>, 其中提到了一种不错的解决方案, 即使利用 键表(key table), 典型的键表有两列: 名字(name)和下一个有效id(nextID). 如果使用数据库唯一键的话, 这个表只有一条记录, 如"DB, 1"; 如果使用表唯一键, 那么对于每个数据库表都有一行数据与之对应, 比如: "Users, 11", "Roles, 31"等等. 我们要获得一个新的可用id, 只需要从一个合适的行中获得nextID值, 然后把nextID + incrementBy存入数据库, 这里每次加上一个incrementBy是为了减少数据库的操作, 避免浪费宝贵的数据库连接资源, 减少冲突发生的可能行.
下面看看我在.net下的实现(模仿Martin Fowler的大作):
using
System;
using System.Diagnostics;
using System.Data;
using System.Data.Common;
namespace MyProject.Core
{
public class KeyGenerator
{
#region private members
private IDbConnection _connection = null ;
private string _keyName;
private int _incrementBy;
private long _nextId = 0 ;
private long _maxId = 0 ;
#endregion
public KeyGenerator(IDbConnection conn, string keyName, int incrementBy)
{
_connection = conn;
_keyName = keyName;
_incrementBy = incrementBy;
}
public long NextKey
{
get
{
if (_nextId == _maxId)
{
reserveIds();
}
return _nextId ++ ;
}
}
private void reserveIds()
{
IDbTransaction tx = _connection.BeginTransaction();
IDbCommand cmd = _connection.CreateCommand();
cmd.Transaction = tx;
try
{
cmd.CommandText = string .Format(
" update keys set nextID = nextID + {0} where name = \'{1}\' " ,
_incrementBy, _keyName);
int result = cmd.ExecuteNonQuery();
Debug.Assert(result == 1 , _keyName + " not exists " );
cmd.CommandText = string .Format( " select nextID from keys where name = \'{0}\' " ,
_keyName);
_maxId = Convert.ToInt64(cmd.ExecuteScalar());
_nextId = _maxId - _incrementBy;
tx.Commit();
}
catch (Exception ex)
{
tx.Rollback();
throw ex;
}
finally
{
cmd.Dispose();
}
}
}
}
using System.Diagnostics;
using System.Data;
using System.Data.Common;
namespace MyProject.Core
{
public class KeyGenerator
{
#region private members
private IDbConnection _connection = null ;
private string _keyName;
private int _incrementBy;
private long _nextId = 0 ;
private long _maxId = 0 ;
#endregion
public KeyGenerator(IDbConnection conn, string keyName, int incrementBy)
{
_connection = conn;
_keyName = keyName;
_incrementBy = incrementBy;
}
public long NextKey
{
get
{
if (_nextId == _maxId)
{
reserveIds();
}
return _nextId ++ ;
}
}
private void reserveIds()
{
IDbTransaction tx = _connection.BeginTransaction();
IDbCommand cmd = _connection.CreateCommand();
cmd.Transaction = tx;
try
{
cmd.CommandText = string .Format(
" update keys set nextID = nextID + {0} where name = \'{1}\' " ,
_incrementBy, _keyName);
int result = cmd.ExecuteNonQuery();
Debug.Assert(result == 1 , _keyName + " not exists " );
cmd.CommandText = string .Format( " select nextID from keys where name = \'{0}\' " ,
_keyName);
_maxId = Convert.ToInt64(cmd.ExecuteScalar());
_nextId = _maxId - _incrementBy;
tx.Commit();
}
catch (Exception ex)
{
tx.Rollback();
throw ex;
}
finally
{
cmd.Dispose();
}
}
}
}
使用的方法也很简单, 如下所示:
OleDbConnection conn
=
new
OleDbConnection();
conn.ConnectionString =
" Provider=SQLOLEDB;Data Source=(local);Initial Catalog=gcs;Trusted_Connection=Yes " ;
conn.Open();
KeyGenerator gen = new KeyGenerator(conn, " Users " , 10 );
for ( int i = 0 ; i < 10 ; i ++ )
{
Console.WriteLine(gen.NextKey);
}
conn.Close();
conn.ConnectionString =
" Provider=SQLOLEDB;Data Source=(local);Initial Catalog=gcs;Trusted_Connection=Yes " ;
conn.Open();
KeyGenerator gen = new KeyGenerator(conn, " Users " , 10 );
for ( int i = 0 ; i < 10 ; i ++ )
{
Console.WriteLine(gen.NextKey);
}
conn.Close();
附: 创建键表的sql
CREATE TABLE [keys] (
[name] [varchar] ( 32 ) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[nextID] [ int ] NULL ,
PRIMARY KEY CLUSTERED
(
[name]
) ON [PRIMARY]
) ON [PRIMARY]
[name] [varchar] ( 32 ) COLLATE Chinese_PRC_CI_AS NOT NULL ,
[nextID] [ int ] NULL ,
PRIMARY KEY CLUSTERED
(
[name]
) ON [PRIMARY]
) ON [PRIMARY]