我想我会 发表于 2016-11-26 13:04

求java数据库大神乱入!

本帖最后由 我想我会 于 2016-11-26 14:59 编辑

工作中碰到一个难题,求java数据库高手和大神解惑!

任务需求:

1. 数据库:

数据库里有一个表,表里只有一个列,这个列是主键,同时是自增的

每次存入一个entity的时候,主键自动增加1

2. 业务层:

每当用户打开一个业务的时候,就会有一个entity 被保存到数据库里,然后立即返回这个entity的主键,应该是当前主键最大值,发送给客户,类似下面的情形:


....

speichereAktenziechen(entity);//存入数据库,主键自增1

int zahl = ermittleAktenzeichenZahl(); // 从数据库里取出该entity的主键值,也就是当前最大主键值

publischerEvent.schicke(zhal); // 发送回客户 ... 这个不重要

....

一句话概括就是: 每次打开业务用户都要获得一个当前最大的ankenzeichenzhal!

3. 这里不关心具体使用哪种数据库,Oracle or mysql or db2 or serversql , 因为上面的操作都是在 上层 ORM 层进行(ORM框架使用的是 apache cayenne,使用哪种orm框架也不重要), 所以这个问题不能通过具体的数据库操作和设置解决

4. 项目要求高并发,可能同1秒有几百到几千个用户在执行上面的操作

5. 问题:

1. 数据库只有一个列,该列是主键,而且是自增的,这个可行吗? 如何插入entity呢?

insert 直接 插入 空的 entity,总报错说sql 的 insert 格式不正确,出现这种情况: insert into GWA_AKTENZEICHEN values();values() 里面是空的,所以报错.

entity 应该带有一个属性 (attribute) id ?这个属性id 是否是主键? 主键自增的话,怎么能随便插入值?


2.


int zahl = ermittleAktenzeichenZahl(); // 从数据库里取出该entity的主键值,也就是当前最大主键值

如何取出最后一次插入的主键值呢(自动新增的主键值,last insert)? 不在数据库层面如何解决这个问题?


3.

speichereAktenziechen(entity);//存入数据库,主键自增1

int zahl = ermittleAktenzeichenZahl(); // 从数据库里取出该entity的主键值,也就是当前最大主键值

这两个操作在高并发的情况下,如何保证得到正确的 aktenzeichenZahl ?

保证一个entity存入数据库之后,立即从数据库里得到这个entity的主键值? 这个Transaktion 的问题怎样解决呢?

大家都懂的,在高并发的情况下,int zahl = ermittleAktenzeichenZahl();不一定能取得正确的最后一次插入值 last insert, ,那么分配给用户的 zahl 可能是错误的!

两个操作直接写在一起应该不对吧?应该如何解决呢?


感觉这问题挺有难度的,不知道如何解决,工版藏龙卧虎,能让无数,所以求java数据库高手和大神指点一下!




我想我会 发表于 2016-11-26 13:16

本帖最后由 我想我会 于 2016-11-26 12:18 编辑

保存和取出在高并情况下可能出现的问题:

1.

java 方面已经执行了 3 次 speichen 操作

假设数据库方面 自增操作能快点,已经完成3 speichen, 但 ermittle 取出操较慢,才完成第一次取出操作,开始第二次取出,那么第2个用户得到的 aktenzeichenzhal 就不是2 而是错误的 3!

2. 另外一个更显而易见的错误

保存1个 entity,数据库方面自增较慢,还没来的及存入,java 方面已经开始执行 ermittleAktenzeichenZahl 方法, 数据库开始 取出操作,这时候取出的可能是0!

晕啊,这个问题到底应该如何解决呢????头疼求高手和大神指点!

Lucky2013 发表于 2016-11-26 13:28

阿卵。

leelight 发表于 2016-11-26 19:40

本帖最后由 leelight 于 2016-11-26 19:46 编辑

这种情况下,你用什么数据库区别非常的大:
1.如果是mysql,那么LAST_INSERT_ID满足上述的高并发条件,因为mysql是根据每个连接来返回相应的值

For LAST_INSERT_ID(), the most recently generated ID is maintained in the server on a per-connection basis. It is not changed by another client. It is not even changed if you update another AUTO_INCREMENT column with a nonmagic value (that is, a value that is not NULL and not 0). Using LAST_INSERT_ID() and AUTO_INCREMENT columns simultaneously from multiple clients is perfectly valid. Each client will receive the last inserted ID for the last statement that client executed.

2.如果是mssql,放狗 scope_identity

3. oracle 的获取sequence的确有些隐患,可以预先自己给出id,放狗twitter的IdWorker(snowflake),生成同一时空下唯一的自增id.
当然snowflake算法适合所有非自增数据结构

论坛里有其它db大神,等其他人

我想我会 发表于 2016-11-26 22:16

Lucky2013 发表于 2016-11-26 12:28
阿卵。

一起呼唤卵哥 {:5_383:}

我想我会 发表于 2016-11-26 22:28

leelight 发表于 2016-11-26 18:40
这种情况下,你用什么数据库区别非常的大:
1.如果是mysql,那么LAST_INSERT_ID满足上述的高并发条件, ...

谢谢回复!

我狗狗了一下,看到过这个LAST_INSERT_ID,但需求上说,数据库类型是不确定的,可能是任何一种数据库,问题要在上一层解决,通过设置具体的数据库或者数据库操作,好像不行的

能不能在上一层通过简单的Transaktion设置解决呢? 把两个操作设置成一个transaktion内,保证每次两步操作作为一个单元执行,执行完了再处理下一个 entity? 这个transaktion应该在orm层上设置?



leelight 发表于 2016-11-27 00:44

本帖最后由 leelight 于 2016-11-27 00:46 编辑

我想我会 发表于 2016-11-26 22:28
谢谢回复!

我狗狗了一下,看到过这个LAST_INSERT_ID,但需求上说,数据库类型是不确定的,可能是任何 ...
上层解决, 那就预给id,你们上层框架无非ssh或ssm
transaction atom不能保证多连接唯一性。

gaokx 发表于 2016-11-27 23:01

1:数据库一张表里面只有定义为key的行能设置为自增.一张表你可以定义多个key,但是一张表只能设置一个子增列.一般是设置到id(默认设置为primary key)上. 如果一张表只有一个列主键列id,先不说这么设计表有没有意义,你每次可以插入一个空项,id每次递增.你说数据库报错,那么需要更多信息,数据库版本,测试环境,具体报的什么错来进一步分析.
2:既然是用的orm框架,那么基本上在保存数据的时候会有返回值的吧,一般用法就是在程序里面创建一个对象,比如你想像1里面这样创建一个表,对应的对象实体比如档案编号只带一个值 id .那么你需要保存一个新的就可以建立一个新的空档案编号对象,然后保存以后返回的就是带当前id值的了.看了看apache cayenne文档,新增一条更简单,大概就是这么个写法 Artist artist = (Artist) context.newObject(Artist.class);不过既然你们是用java,那么java规范的jpa不是更好用么.还有看你提到主键插入值,你可以创建一个实体带你想要的id值,当然这个id是数据库里面没有分配过的,然后保存,但是这样的话下一个自动插入的对象,获得的id是从你刚刚保存的值开始自增的,不会自动填满系统原来最大id值和你刚刚保存的那个之间的部分.
3:同上,既然是用orm框架,那么一次persitent操作就是原子的啊,既然可以保存对象以后在返回的值里面拿到自动生成的id值,没必要再另外去尝试读取了.

另外你们这个设计谁做的啊,你们做项目有没有安排一个架构师跟进呢.看你问题里面描述的,又要高并发,高可用,那么把这么一个关键操作放到这样一张表里面来,这张表就存一个id,越简单那么其它表对它的依赖就越强,以后新增业务带来的复杂度增加有估算过没有呢.还有你们的系统部署是单实例的么,现在就在考虑每秒几k的访问操作,发展下去很快一台主机就抗不住了吧,有计划然后怎么办么,如果演变成多主机,那么数据库之间的同步还有协调准备怎么做,系统必须相应更新吧,到那时候再来做的话,业务量和发布新版本时间的压力就是山大了啊

我想我会 发表于 2016-11-27 23:32

leelight 发表于 2016-11-26 23:44
上层解决, 那就预给id,你们上层框架无非ssh或ssm
transaction atom不能保证多连接唯一性。
谢谢回复,我们不用SS,这两个框架或者相似功能部分用的是自己开发的,orm用的cayenne

头说不能用预给id,比如在上层 id + 1, 就用主键自增,然后还不让在数据库层设置,我真不知道怎么弄了

我想我会 发表于 2016-11-27 23:59

本帖最后由 我想我会 于 2016-11-27 23:04 编辑

gaokx 发表于 2016-11-27 22:01
1:数据库一张表里面只有定义为key的行能设置为自增.一张表你可以定义多个key,但是一张表只能设置一个子增列 ...
谢谢回复

1. 我测试用的数据库是Oracle, 我试过1. 插入空entity,entity没有任何属性, 出现上面的错误, 2. entity带一个id 属性,结果出现 数据库运行期错误,但是好像跟orm框架有关, 3. entity 带一个 localDate属性,文件号对于的年份,成功插入,主键自增,但需求要求entity应该是空的,不带任何属性!

2. 我们orm用的是 cayenne, cayenne 这个东西很烦,用的少所以文档和资料非常少,cayenne 使用起来远没那么简单, 它们文档提供的存取命令一点用没有,实际中必须用 map.xml 和 mapper (需要自己为每一个java Objekt写mapper, mapper里实现各种具体的映射)来映射存取数据, 非常繁琐和恶劣,而且出现问题跟本狗不到答案! 如通过cayenne设置主键自增需要在 map.xml里给主键分配属性 isGenerade=true, 1里的问题,我都不知道到底是数据库的原因,还是cayenne的问题?

我们不用jpa,直接用cayenne, 狗了一下,这个问题如果在数据库层面挺好解决的,oralce 用 sequence: Select CASE_SEQ.currval from dual, mysql 用 last_insert_id, 都能得到最新插入的自增主键, 而且能保证多用户高并发条件下反回正确ID,不同用户不同ID

而且像oracle 设置了自增主键以后,即便你插入一个id mit einem beliebigen wert,或者 id = null, 都不会破坏主键自增,因为主键自增来自于sequence,而用了cayenne 之后,我给entity中设置一个属性id, 无论插入null或者一个值,都发生 1里 2 的错误! 我不知道到底是谁的原因?

所以明天我只能去问问头,这问题能不能放到数据库层解决

我们的头就是架构,不知道他怎么想的,把这么一个本来属于他的任务让我做,有故意为难我的感觉?!{:5_383:}目前的任务就是这个,其它的我不清楚,也不关心,那些本来是他该做的!



页: [1] 2 3 4
查看完整版本: 求java数据库大神乱入!