日期:2014-05-16  浏览次数:20415 次

Hibernate的increment主键生成机制带来的问题

在网站运行在apache和tomcat的负载均衡之后,总是出现一些奇怪的问题。开始有一些Duplicate?entry的错误,但没在意。
今天又看了程序运行的错误信息,发现几乎都是Duplicate?entry错误,集中出现在insert数据库的时候,insert?user,insert?message。看了Message类的主键生成是increment类型,看了看Hibernate的源代码?,发现对应increment主键生成器的org.hibernate.id.IncrementGenerator类里面,是使用select?max(?columnName?)?from?tableName的方式来获取。原来的程序一直运行很好,但是在用两个Tomcat来负载均衡后却出现问题。为什么??IncrementGenerator类里面的generate()方法虽然被声明成了synchronized,但现在两个Tomcat分别运行在两台服务器的两个独立的Java虚拟机里,显然问题在这里,synchronized只能在一个独立的Java虚拟机内部有效。所以,在两个Tomcat中用?select?max同时取主键,就相当于在没有synchronized的保护下,并发时就会取出相同的值,再insert就会发生dumplicate?entry的错误。
后来查看Hibernate的Reference中也提到,increment不要再集群下使用。
问题找到了,解决的办法也很简单,就是使用MySQL自己的auto_increment功能来产生主键。只需将所有Hibernate映射文件中的?increment改为native或者identity。并将数据库的主键加上auto_increment属性。
查看MySQL?5.0的文档,在14.2.6.3中介绍说:
14.2.6.3.?How?AUTO_INCREMENT?Columns?Work?in?InnoDB
If?you?specify?an?AUTO_INCREMENT?column?for?an?InnoDB?table,?the?table?handle?in?the?InnoDB?data?dictionary?contains?a?special?counter?called?the?auto-increment?counter?that?is?used?in?assigning?new?values?for?the?column.?This?counter?is?stored?only?in?main?memory,?not?on?disk.
InnoDB?uses?the?following?algorithm?to?initialize?the?auto-increment?counter?for?a?table?T?that?contains?an?AUTO_INCREMENT?column?named?ai_col:?After?a?server?startup,?for?the?first?insert?into?a?table?T,?InnoDB?executes?the?equivalent?of?this?statement:

SELECT?MAX(ai_col)?FROM?T?FOR?UPDATE;

看来在给数据库主键加上auto_increment的属性后,还要重新启动一下MySQL,这样才能保证所有的auto_increment被初始化正确。
好了,开始修改,别忘了先关掉所有的Tomcat!
问题解决后的感想:

Hibernate的主键生成虽然支持很多种数据库独有的increment方式,还有他自己的select?max实现的increment方式,其实这些都不是很好,假如将来真的要切换数据库,并且是在集群下运行程序,某种数据库独有的increment和?select?max方式的increment都会带来问题。