生成整数自增ID之ID生成策略
使用整数作为索引ID是最优的方式,那么整数ID的生成在一些关联表或者集群环境中就要考虑怎么做了!
整数要求一直自增,且保证唯一性
Web服务器集群调用这个整数生成服务,然后根据各种规则,插入指定的数据库.
一般来说,整数自增可以通过几个方式实现:
1.MySQL 单独建一个表,使用Auto_increment特性.
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
如果需要生成ID,则Insert一个记录,然后获取last_insert_id()得到ID值
这种方式的优点是简单,而且比较快.
缺点是这个表会越来越大,需要定期进行清理.
2.Oracle 序列
优点很明显,足够快,而且不占用空间.
缺点..你需要有Oracle
3.mysql 单行更新自增
以上三种数据库方式的效率对比如下(都是测试的虚拟环境,作为趋势参考,数值是每秒生成的ID个数)
单线程
5线程
10线程
20线程
MySQL Auto_increment
340-390
277
229
178
Oracle序列
714
555
454
454
MySQL 单行更新
303
136
66
19
使用两个Redis实例,一个分发奇数ID,一个分发偶数ID
任何一个Redis损坏,都可以切换到另外一个Redis实例.
5.使用程序模拟序列
下面的ID生成服务,初始化先从数据库拿到一段ID,然后分发。
一旦ID耗尽,再从数据库获取一段ID。
可以启动多个ID生成服务,避免单点故障.
ID生成服务本身应该串行化,避免锁竞争.
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class SeqGenerator { private static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); private static int currentVal = -1; private static int maxVal = -1; private static int fetchSize = 10000;//每次生成一万个id static{ try { fetchFromDB(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static int getSeq() throws InterruptedException, ExecutionException { Callable<Integer> c = new Callable<Integer>() { @Override public Integer call() throws Exception { int result = currentVal; if (currentVal > maxVal) {//如果当前值>数据库最大值,重新生成id fetchFromDB(); result = currentVal; } currentVal++; return result; } }; Future<Integer> task = singleThreadExecutor.submit(c); return task.get().intValue(); } private static void fetchFromDB() throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "xx", "xx"); connection.setAutoCommit(false); Statement st = connection.createStatement(); ResultSet rs = st.executeQuery("select * from test for update"); rs.next(); currentVal = rs.getInt(1) + 1;//当前值 rs.close(); st.executeUpdate("update test set id=id+" + fetchSize);//更新db中最大值 rs = st.executeQuery("select * from test for update"); rs.next(); maxVal = rs.getInt(1);//最大值 connection.commit(); rs.close(); st.close(); connection.close(); } public static void main(String[] args) throws Exception { int i = 1000000; long start = System.currentTimeMillis(); while (i > 0) { System.out.println(SeqGenerator.getSeq()); i--; } long end = System.currentTimeMillis(); System.out.println(end - start); } }
补充
数据库表主键的知识点:
Generator 为每个 POJO 的实例提供唯一标识。
一般情况,我们使用“native”。class 表示采用由生成器接口net.sf.hibernate.id.IdentifierGenerator 实现的某个实例,其中包括:
assigned
主键由外部程序负责生成,在 save() 之前指定一个。
hilo
通过hi/lo 算法实现的主键生成机制,需要额外的数据库表或字段提供高位值来源。
seqhilo
与hilo 类似,通过hi/lo 算法实现的主键生成机制,需要数据库中的 Sequence,适用于支持 Sequence 的数据库,如Oracle。
increment
主键按数值顺序递增。此方式的实现机制为在当前应用实例中维持一个变量,以保存着当前的最大值,之后每次需要生成主键的时候将此值加1作为主键。这种方式可能产生的问题是:不能在集群下使用。
identity
采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL 中的主键生成机制。
sequence
采用数据库提供的 sequence 机制生成主键。如 Oralce 中的Sequence。
native
由 Hibernate 根据使用的数据库自行判断采用 identity、hilo、sequence 其中一种作为主键生成方式。
uuid.hex
由 Hibernate 基于128 位 UUID 算法生成16 进制数值(编码后以长度32 的字符串表示)作为主键。
uuid.string
与uuid.hex 类似,只是生成的主键未进行编码(长度16),不能应用在 PostgreSQL 数据库中。
Foreign
使用另外一个相关联的对象的标识符作为主键。
发表吐槽
你肿么看?
既然没有吐槽,那就赶紧抢沙发吧!