- 浏览: 640718 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
liuche20083736:
非常好
从问题看本质: 研究TCP close_wait的内幕 -
xiaopohai85707:
优化算法与原来需求不符
过滤字符的性能调优?挤一挤还是有的 -
kmy_白衣:
生成的area图有时候 标签的数值和图标上看上去的数值不一致。 ...
OpenFlashChart2之恶心文档 -
tom&jerry:
大神,请教一个问题,按名称排序为何无效,用的2.4.3 XPA ...
深入浅出jackrabbit之十三 查询之AST和QT -
jd2bs:
改成精确匹配可以了< filter-mapping &g ...
细谈Ehcache页面缓存的使用
[size=medium]/**
*作者:张荣华
*日期:2008-08-22
**/
在那遥远的过去,俺曾经写过一篇关于事务的文章,原文地址见: http://ahuaxuan.iteye.com/blog/95124.文章大意是这样的:在spring+hibernate的场景下,readonly的事务会有特别的优化.因为readonly的事务在提交的时候不会flush 一级缓存中的几个队列(包括,更新队列,插入队列等).看了那篇文章的同学会以为:ok,只读的时候我只要readonly就行了. 不过那篇文章中我并没有考虑到所有的场景,所有再写一篇文章,算是对整个概念(查询操作是否需要事务)的一个完善.
还是老路子,我先描述一下我遇到的一个项目的问题:
1 代码逻辑,下面是一段伪代码
这段代码逻辑非常简单,先从memcached中取数据,取不到就从db取,然后再放到memcached中,无可挑剔(不过要注意我的注释,这个方法上加了required的事务)
就是这段代码,放到tomcat中,我的同事william测出来的结果是,单线程请求(tomcat的thread pool中在同一时间只有一个处理请求的线程被调用):ab -c 1 –n 1000 http://xxxx.xxx.xxx/xxx
结果是每秒中只能处理20个请求, 我的天啊, 20个,改多线程呢,: ab -c 100 –n 1000 http://xxxx.xxx.xxx/xxx, 一百个线程请求1000次,晕倒,还是每秒只能处理20多个请求.
估计有些人会觉得很奇怪,这么简单的逻辑怎么会这么慢,我但是也很奇怪,不过突然大脑中一个概念闪过”连接池”(难道这就是传说中的灵感),打开配置一看,果然,连接池配置的连接数只有20.
“每秒钟处理20个请求,20个connection,加了事务”
“每秒钟处理20个请求,20个connection,加了事务”
“每秒钟处理20个请求,20个connection,加了事务”
多想了两遍之后(快分裂了),答案出来了:
在getObject方法上加上事务之后,所有的调用都会新建事务对象,然后放到当前线程中,而新建该事务对象的基础是connection,同时这个connection也会被保存在当前线程中,这样造成的结果是只有等到拥有connection的请求退出事务之后,connection才能重新回到线程池,换句话说,getObject方法是依赖于connection的,getObject能够被调用的次数取决于线程池中线程的数量
于是把线程池开到100,同样运行ab,结果果然好了很多,现在每秒能够处理的请求达到了120+,connection的数量变成原来的5倍,每秒处理的请求数也变成了原来的5倍.
看上去为这个get方法配置事务导致了该方法依赖于db connection是真正的原因,
从我们的逻辑上看该方法确实是不需要事务,但是由于我们的习惯,就顺利成章的给配置了一个,但是这个小小的配置确带来了巨大的影响.
不过有时候,我们以为找到了所有的问题,但是往往有更深层次的问题隐藏其中,比如说多次查询不加事务可能产生幻读的情况(不过如果你的应用对幻读的要求不高的话也没有什么问题).
那么也许我们可以这样结论,只要查询的操作在高性能需求的场景下千万不要加事务,即使是readonly的也不行,其他场景加上吧,不加可能会有些问题,比如说前面提到的幻读(这次我的结论好像底气不足,因为我还没有对它有过特别彻底的研究).
结论下来了,我们看看如何优化,其实在我举的这个场景中,是既可以保证事务,又能提高效率的,就是缩小事务的粒度,如果我在ObjectDao.getfromDB(params);加上事务的话,依赖于connection的只有ObjectDao.getfromDB(params);这个方法了,而这个方法只有o1 == null的时候才会被调用,绝大多数情况下它是不会被调用的.这样既一定程度上保证了事务又提高的程序的速度.
那么我们还可以下一个结论,就是某些场景下缩小我们事务的粒度能够很大程度提高程序的性能.
注明:由于ahuaxuan水平有限,文中不妥之处还望不吝指正,谢谢。
[/size]
请问那段伪代码中的业务逻辑错在哪里?
我希望你在指出别人有错误的同时能阐述错误在哪里,可以吗。
wym0291有没有说错大众自有评断,你仔细看我在上上楼对他的话的点评了吗,就这样还没错?我无语了
还有,他不是认为你的方法只使用了一个connection,他的意思是正确的方法(或者说正确的业务逻辑)应该只使用一个connection.
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
我把你们的两段话放在一起,你自己看吧,明明是事务粒度的问题,居然说到代码级别的错误上去了,我还能说什么呢。
看我红色标注的地方,尤其是“而你得出的结论确实有多个connection被使用”,但是事实上就是如此啊。这完全就是一个事务粒度的问题,我很期待你拿出你的正确的代码来证明你的观点
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
我很奇怪,你怎么不正面回答我的问题呢,我说你上面这段话是错误的,你怎么不响应,反而转移话题呢,搞不懂。
我之所以说错误,就是你的代码没有实现你的设计目的。我用建议只是因为你的帖子刚开头就让人看到一段有问题的代码,实在让人感觉有点草率。
这段话就更搞笑了,我在文章中说得非常清楚,这是一段伪代码,代表一段业务逻辑,你有见过这种java代码吗。
.....很明显,你对并发问题一点不敏感,从你回答中也可以看出,你对如何解决这种并发也是知之甚少。上面我已经提过了可以提供类似FutureTask来构建一种基于任务的阻塞,你估计连查都没查一下这种方法的使用,就来谈开销。还说最多就是多查两便数据库。。。。。你还是去实践一下,你就测下100个并发,看看到底查了几次数据库,看了结果我想你就不会说的这么轻松。
这段我就更无语了,不知道从哪里看出来我对并发问题不敏感了,还我对并发知之甚少,你至少也要知道我才能下结论吧,从一句话就看出来了,我看你比较喜欢妄结论。
先不说这段代码是否有锁的问题(因为它本身只是伪代码,代表我们的业务逻辑而已)即使直接在方法上加锁也是没有关系的,根本用不着futuretask,因为只有很少的线程会被阻塞,什么时候阻塞呢,就是在查db,放memcached的时候阻塞,一旦这个操作完成,其他请求的线程根本不需要等多长时间,所以直接在方法上加同步就行了,如果该类的其他方法用到这个锁,那么使用锁分离技术,在这个方法上加其他锁就可以了,问题其实很简单啊。
为什么说在这个方法上加同步是没有问题的,我详细阐述一下:
首先这个方法有一个getfromdb操作,耗时大概15毫秒,有一个getfromcache操作,耗时不到1毫秒,而getfromdb只需要一次,也就是说在其后的请求中,这个方法大概就是1-3毫秒可以搞定,这个方法时非常非常的快,阻塞其他请求线程的几率非常的小,为什么这么说,因为tomcat并发能力本来有限,比如我们预定是1000/s的话,我一个请求方法消耗在3毫秒,网络消耗在100毫秒,那么线程直接阻塞的情况几乎没有可能发生,即使发生一两次也根本没有问题,不会影响全局。所以说你说的futuretask是多次一举
还有一点,在你最后的回复中也体现了你对缓存使用的不明确。为什么你要使用缓存来避免数据库的操作?
我对缓存的使用不明确?唉,这一点让看的贴的人去评断吧。
补充:如果想知道我对缓存的使用是否明确,请看这个帖子:
最后的结果可以预料。开销大的数据操作,在很多并发的情况下,几乎同时执行了大量并发数据库操作,后果不用说了吧。。。。
我可以很负责任的告诉你,这段代码跑的很好,而且我很负责任的告诉你,这个系统并发很大,8台tomcat撑着呢,看看,是不是又妄下结论了?
-----------------------------------------------------
其实你还是不明白本文的主题是什么,本文主题是事务的问题,并不是锁的问题,你一个劲的在说锁,可见你不知道事务才是本文的主题,而恰恰你在这点上的观点是错误的,你居然认为这个方法在并发的事务环境中只用了一个connection
我之所以说错误,就是你的代码没有实现你的设计目的。我用建议只是因为你的帖子刚开头就让人看到一段有问题的代码,实在让人感觉有点草率。
.....很明显,你对并发问题一点不敏感,从你回答中也可以看出,你对如何解决这种并发也是知之甚少。上面我已经提过了可以提供类似FutureTask来构建一种基于任务的阻塞,你估计连查都没查一下这种方法的使用,就来谈开销。还说最多就是多查两便数据库。。。。。你还是去实践一下,你就测下100个并发,看看到底查了几次数据库,看了结果我想你就不会说的这么轻松。
还有一点,在你最后的回复中也体现了你对缓存使用的不明确。为什么你要使用缓存来避免数据库的操作?很多时候是因为数据库查询的开销很大。而你却忽视这种开销,说是选择拿掉锁。在真实环境中,面对这种开销很大的数据库操作,你的代码很可能就造成一次资源占用的峰值,带来的连锁反映也可能是致命的,对企业来说就是钱的问题。
最后的结果可以预料。开销大的数据操作,在很多并发的情况下,几乎同时执行了大量并发数据库操作,后果不用说了吧。。。。
楼主只是拿一段sample代码来说明一下查询事务的粒度问题,和并发的关系并不是很大。
你既然那么关心并发问题,那么请拿出代码来解决问题。莫非你所有的查询操作全都是做同步处理的?
高手,我理解你的意思了
你从什么地方看出来这个getObject方法本身是错误的????
错在哪里,我也挺佩服你的,大家都没有看出来,就你看出来了.
从你这段话里可以看出来你还不明白我在说什么,我很少用"建议"这个词,因为这让人有种据高凌下的感觉,但是我真的建议你再把文章看两遍
再看代码。因为没考虑到并发问题,你这个方法代码在多线程情况下,明显会造成多次重复的查询数据库,而这个查询数据库的动作原本只应该执行仅一次而已。
这里即使有产生并发,最多就是多查两遍数据库, 查完之后就不会再查了,显然这个是小问题,因为这并发的几次查询都是一样的数据.如果加同步,那么不管是查数据库还是缓存都有锁的开销,显然我的选择是在真正并发的时候拿掉锁,宁可多查两遍数据库
我之所以说错误,就是你的代码没有实现你的设计目的。我用建议只是因为你的帖子刚开头就让人看到一段有问题的代码,实在让人感觉有点草率。
.....很明显,你对并发问题一点不敏感,从你回答中也可以看出,你对如何解决这种并发也是知之甚少。上面我已经提过了可以提供类似FutureTask来构建一种基于任务的阻塞,你估计连查都没查一下这种方法的使用,就来谈开销。还说最多就是多查两便数据库。。。。。你还是去实践一下,你就测下100个并发,看看到底查了几次数据库,看了结果我想你就不会说的这么轻松。
还有一点,在你最后的回复中也体现了你对缓存使用的不明确。为什么你要使用缓存来避免数据库的操作?很多时候是因为数据库查询的开销很大。而你却忽视这种开销,说是选择拿掉锁。在真实环境中,面对这种开销很大的数据库操作,你的代码很可能就造成一次资源占用的峰值,带来的连锁反映也可能是致命的,对企业来说就是钱的问题。
最后的结果可以预料。开销大的数据操作,在很多并发的情况下,几乎同时执行了大量并发数据库操作,后果不用说了吧。。。。
你从什么地方看出来这个getObject方法本身是错误的????
错在哪里,我也挺佩服你的,大家都没有看出来,就你看出来了.
从你这段话里可以看出来你还不明白我在说什么,我很少用"建议"这个词,因为这让人有种据高凌下的感觉,但是我真的建议你再把文章看两遍
再看代码。因为没考虑到并发问题,你这个方法代码在多线程情况下,明显会造成多次重复的查询数据库,而这个查询数据库的动作原本只应该执行仅一次而已。
这里即使有产生并发,最多就是多查两遍数据库, 查完之后就不会再查了,显然这个是小问题,因为这并发的几次查询都是一样的数据.如果加同步,那么不管是查数据库还是缓存都有锁的开销,显然我的选择是在真正并发的时候拿掉锁,宁可多查两遍数据库
尽量细化事务粒度,深有同感,想问一下,这个测试是怎么做的,用什么工具?
apache ab工具
apache 安装目录bin下的
*作者:张荣华
*日期:2008-08-22
**/
在那遥远的过去,俺曾经写过一篇关于事务的文章,原文地址见: http://ahuaxuan.iteye.com/blog/95124.文章大意是这样的:在spring+hibernate的场景下,readonly的事务会有特别的优化.因为readonly的事务在提交的时候不会flush 一级缓存中的几个队列(包括,更新队列,插入队列等).看了那篇文章的同学会以为:ok,只读的时候我只要readonly就行了. 不过那篇文章中我并没有考虑到所有的场景,所有再写一篇文章,算是对整个概念(查询操作是否需要事务)的一个完善.
还是老路子,我先描述一下我遇到的一个项目的问题:
1 代码逻辑,下面是一段伪代码
/** * 该方法上加事务,传播途径为required * @param params * @return */ public List<object> getObject(Map<String, String> params) { //先从memcached中取得数据 List<object> o1 = memcachedClient.getFromCache(params); if (o1 == null) { ol = ObjectDao.getfromDB(params); memcachedClient.putToCache(params, o1); } return o1; }
这段代码逻辑非常简单,先从memcached中取数据,取不到就从db取,然后再放到memcached中,无可挑剔(不过要注意我的注释,这个方法上加了required的事务)
就是这段代码,放到tomcat中,我的同事william测出来的结果是,单线程请求(tomcat的thread pool中在同一时间只有一个处理请求的线程被调用):ab -c 1 –n 1000 http://xxxx.xxx.xxx/xxx
结果是每秒中只能处理20个请求, 我的天啊, 20个,改多线程呢,: ab -c 100 –n 1000 http://xxxx.xxx.xxx/xxx, 一百个线程请求1000次,晕倒,还是每秒只能处理20多个请求.
估计有些人会觉得很奇怪,这么简单的逻辑怎么会这么慢,我但是也很奇怪,不过突然大脑中一个概念闪过”连接池”(难道这就是传说中的灵感),打开配置一看,果然,连接池配置的连接数只有20.
“每秒钟处理20个请求,20个connection,加了事务”
“每秒钟处理20个请求,20个connection,加了事务”
“每秒钟处理20个请求,20个connection,加了事务”
多想了两遍之后(快分裂了),答案出来了:
在getObject方法上加上事务之后,所有的调用都会新建事务对象,然后放到当前线程中,而新建该事务对象的基础是connection,同时这个connection也会被保存在当前线程中,这样造成的结果是只有等到拥有connection的请求退出事务之后,connection才能重新回到线程池,换句话说,getObject方法是依赖于connection的,getObject能够被调用的次数取决于线程池中线程的数量
于是把线程池开到100,同样运行ab,结果果然好了很多,现在每秒能够处理的请求达到了120+,connection的数量变成原来的5倍,每秒处理的请求数也变成了原来的5倍.
看上去为这个get方法配置事务导致了该方法依赖于db connection是真正的原因,
从我们的逻辑上看该方法确实是不需要事务,但是由于我们的习惯,就顺利成章的给配置了一个,但是这个小小的配置确带来了巨大的影响.
不过有时候,我们以为找到了所有的问题,但是往往有更深层次的问题隐藏其中,比如说多次查询不加事务可能产生幻读的情况(不过如果你的应用对幻读的要求不高的话也没有什么问题).
那么也许我们可以这样结论,只要查询的操作在高性能需求的场景下千万不要加事务,即使是readonly的也不行,其他场景加上吧,不加可能会有些问题,比如说前面提到的幻读(这次我的结论好像底气不足,因为我还没有对它有过特别彻底的研究).
结论下来了,我们看看如何优化,其实在我举的这个场景中,是既可以保证事务,又能提高效率的,就是缩小事务的粒度,如果我在ObjectDao.getfromDB(params);加上事务的话,依赖于connection的只有ObjectDao.getfromDB(params);这个方法了,而这个方法只有o1 == null的时候才会被调用,绝大多数情况下它是不会被调用的.这样既一定程度上保证了事务又提高的程序的速度.
那么我们还可以下一个结论,就是某些场景下缩小我们事务的粒度能够很大程度提高程序的性能.
注明:由于ahuaxuan水平有限,文中不妥之处还望不吝指正,谢谢。
[/size]
评论
32 楼
xidaboy
2008-09-02
public List<object> getObject(Map<String, String> params) {
//先从memcached中取得数据
//假设A线程走到这里
List<object> o1 = memcachedClient.getFromCache(params);
//假设B线程走到这里
if (o1 == null) {
// 假设C线程走到这里
ol = ObjectDao.getfromDB(params);
// 假设D线程走到这里
memcachedClient.putToCache(params, o1);
}
return o1;
}
并发的情况下,如果实际情况A,B,C,D线程取同样的数据,多次读取数据库,不可避免
:)
//先从memcached中取得数据
//假设A线程走到这里
List<object> o1 = memcachedClient.getFromCache(params);
//假设B线程走到这里
if (o1 == null) {
// 假设C线程走到这里
ol = ObjectDao.getfromDB(params);
// 假设D线程走到这里
memcachedClient.putToCache(params, o1);
}
return o1;
}
并发的情况下,如果实际情况A,B,C,D线程取同样的数据,多次读取数据库,不可避免
:)
31 楼
wym0291
2008-09-02
呵呵,怪我多嘴,得罪之处还请谅解。至于你说的解决方法,其实在JDK5刚出来那时已经变成一种比较大众的方法了。
下面是不严谨的,简单的代码,只为了说明一下避免多次查询数据库的逻辑,没有考虑数据库更新后的缓存同步问题,这个问题的解决方法比较多,就不在代码里写了,免得班门弄斧。
ConcurrentHashMap<Map<String, String>, FutureTask<List<Object>>> map = new ConcurrentHashMap<Map<String, String>, FutureTask<List<Object>>>();
public List<Object> get(final Map<String, String> params) throws InterruptedException, ExecutionException {
FutureTask<List<Object>> result = map.get(params);
if(result == null){
FutureTask<List<Object>> task = new FutureTask<List<Object>>(new Callable<List<Object>>(){
@Override
public List<Object> call() throws Exception {
return ObjectDao.getfromDB(params);
}});
result = map.putIfAbsent(params, task);
if(result == null){
System.out.println("result = null");
result = task;
}
result.run();
}
return result.get();
}
在数据库数据更新后,考虑到缓存数据要重新从数据库里获得,可以利用这个任务的重置方法,使此任务可以重新执行。例如runAndReset()方法,在数据更新时,可以从任务MAP中获取此任务,使用此方法使任务重置。
下面是不严谨的,简单的代码,只为了说明一下避免多次查询数据库的逻辑,没有考虑数据库更新后的缓存同步问题,这个问题的解决方法比较多,就不在代码里写了,免得班门弄斧。
ConcurrentHashMap<Map<String, String>, FutureTask<List<Object>>> map = new ConcurrentHashMap<Map<String, String>, FutureTask<List<Object>>>();
public List<Object> get(final Map<String, String> params) throws InterruptedException, ExecutionException {
FutureTask<List<Object>> result = map.get(params);
if(result == null){
FutureTask<List<Object>> task = new FutureTask<List<Object>>(new Callable<List<Object>>(){
@Override
public List<Object> call() throws Exception {
return ObjectDao.getfromDB(params);
}});
result = map.putIfAbsent(params, task);
if(result == null){
System.out.println("result = null");
result = task;
}
result.run();
}
return result.get();
}
在数据库数据更新后,考虑到缓存数据要重新从数据库里获得,可以利用这个任务的重置方法,使此任务可以重新执行。例如runAndReset()方法,在数据更新时,可以从任务MAP中获取此任务,使用此方法使任务重置。
30 楼
terranhao
2008-09-02
貌似他是这个意思:正确的逻辑应该是无论如何怎么并发,都只需要从连接池获取一个connection.只用一个connection更新你的cache,然后所有的并发调用都是从cache里获取数据。
但是你的代码实际上在并发的时候获取了多个connection.这就是不对的。不知道我解释清楚了没有
他的意思就是,你实际上逻辑没有考虑清楚,才会犯后面的连接被耗尽的错误。
反正我就是这么理解的,错了请拍砖
我确实是看过了你们的帖子,要不我也不会来吹水,这不是我的风格。
但是你的代码实际上在并发的时候获取了多个connection.这就是不对的。不知道我解释清楚了没有
他的意思就是,你实际上逻辑没有考虑清楚,才会犯后面的连接被耗尽的错误。
反正我就是这么理解的,错了请拍砖
我确实是看过了你们的帖子,要不我也不会来吹水,这不是我的风格。
29 楼
ahuaxuan
2008-09-02
terranhao 写道
其实,楼主的逻辑确实有错误,wym0291说的也没错。
请问那段伪代码中的业务逻辑错在哪里?
我希望你在指出别人有错误的同时能阐述错误在哪里,可以吗。
wym0291有没有说错大众自有评断,你仔细看我在上上楼对他的话的点评了吗,就这样还没错?我无语了
terranhao 写道
还有,他不是认为你的方法只使用了一个connection,他的意思是正确的方法(或者说正确的业务逻辑)应该只使用一个connection.
wym0291 写道
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
我把你们的两段话放在一起,你自己看吧,明明是事务粒度的问题,居然说到代码级别的错误上去了,我还能说什么呢。
看我红色标注的地方,尤其是“而你得出的结论确实有多个connection被使用”,但是事实上就是如此啊。这完全就是一个事务粒度的问题,我很期待你拿出你的正确的代码来证明你的观点
28 楼
terranhao
2008-09-02
其实,楼主的逻辑确实有错误,wym0291说的也没错。
楼主这个例子虽然不是太恰当,但也确实说清楚了要表达的观点。
但是wym0291说的是有道理。至少理论上我这么认为。
而且你说的在方法上加锁可以,因为你的getFromDb方法只需要15毫秒。但是如果你这个getFromDb方法要抓的数据量很大呢,可能远不止15毫秒吧。不过只要你自己对自己这个方法了解,那加锁没有问题。
还有,他不是认为你的方法只使用了一个connection,他的意思是正确的方法(或者说正确的业务逻辑)应该只使用一个connection.
楼主这个例子虽然不是太恰当,但也确实说清楚了要表达的观点。
但是wym0291说的是有道理。至少理论上我这么认为。
而且你说的在方法上加锁可以,因为你的getFromDb方法只需要15毫秒。但是如果你这个getFromDb方法要抓的数据量很大呢,可能远不止15毫秒吧。不过只要你自己对自己这个方法了解,那加锁没有问题。
还有,他不是认为你的方法只使用了一个connection,他的意思是正确的方法(或者说正确的业务逻辑)应该只使用一个connection.
27 楼
ahuaxuan
2008-09-02
wym0291 写道
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
我很奇怪,你怎么不正面回答我的问题呢,我说你上面这段话是错误的,你怎么不响应,反而转移话题呢,搞不懂。
wym0291 写道
我之所以说错误,就是你的代码没有实现你的设计目的。我用建议只是因为你的帖子刚开头就让人看到一段有问题的代码,实在让人感觉有点草率。
这段话就更搞笑了,我在文章中说得非常清楚,这是一段伪代码,代表一段业务逻辑,你有见过这种java代码吗。
wym0291 写道
.....很明显,你对并发问题一点不敏感,从你回答中也可以看出,你对如何解决这种并发也是知之甚少。上面我已经提过了可以提供类似FutureTask来构建一种基于任务的阻塞,你估计连查都没查一下这种方法的使用,就来谈开销。还说最多就是多查两便数据库。。。。。你还是去实践一下,你就测下100个并发,看看到底查了几次数据库,看了结果我想你就不会说的这么轻松。
这段我就更无语了,不知道从哪里看出来我对并发问题不敏感了,还我对并发知之甚少,你至少也要知道我才能下结论吧,从一句话就看出来了,我看你比较喜欢妄结论。
先不说这段代码是否有锁的问题(因为它本身只是伪代码,代表我们的业务逻辑而已)即使直接在方法上加锁也是没有关系的,根本用不着futuretask,因为只有很少的线程会被阻塞,什么时候阻塞呢,就是在查db,放memcached的时候阻塞,一旦这个操作完成,其他请求的线程根本不需要等多长时间,所以直接在方法上加同步就行了,如果该类的其他方法用到这个锁,那么使用锁分离技术,在这个方法上加其他锁就可以了,问题其实很简单啊。
为什么说在这个方法上加同步是没有问题的,我详细阐述一下:
首先这个方法有一个getfromdb操作,耗时大概15毫秒,有一个getfromcache操作,耗时不到1毫秒,而getfromdb只需要一次,也就是说在其后的请求中,这个方法大概就是1-3毫秒可以搞定,这个方法时非常非常的快,阻塞其他请求线程的几率非常的小,为什么这么说,因为tomcat并发能力本来有限,比如我们预定是1000/s的话,我一个请求方法消耗在3毫秒,网络消耗在100毫秒,那么线程直接阻塞的情况几乎没有可能发生,即使发生一两次也根本没有问题,不会影响全局。所以说你说的futuretask是多次一举
wym0291 写道
还有一点,在你最后的回复中也体现了你对缓存使用的不明确。为什么你要使用缓存来避免数据库的操作?
我对缓存的使用不明确?唉,这一点让看的贴的人去评断吧。
补充:如果想知道我对缓存的使用是否明确,请看这个帖子:
引用
引用
最后的结果可以预料。开销大的数据操作,在很多并发的情况下,几乎同时执行了大量并发数据库操作,后果不用说了吧。。。。
我可以很负责任的告诉你,这段代码跑的很好,而且我很负责任的告诉你,这个系统并发很大,8台tomcat撑着呢,看看,是不是又妄下结论了?
-----------------------------------------------------
引用
建议你换个方法来诠释你的主题。
其实你还是不明白本文的主题是什么,本文主题是事务的问题,并不是锁的问题,你一个劲的在说锁,可见你不知道事务才是本文的主题,而恰恰你在这点上的观点是错误的,你居然认为这个方法在并发的事务环境中只用了一个connection
26 楼
downpour
2008-09-02
wym0291 写道
我之所以说错误,就是你的代码没有实现你的设计目的。我用建议只是因为你的帖子刚开头就让人看到一段有问题的代码,实在让人感觉有点草率。
.....很明显,你对并发问题一点不敏感,从你回答中也可以看出,你对如何解决这种并发也是知之甚少。上面我已经提过了可以提供类似FutureTask来构建一种基于任务的阻塞,你估计连查都没查一下这种方法的使用,就来谈开销。还说最多就是多查两便数据库。。。。。你还是去实践一下,你就测下100个并发,看看到底查了几次数据库,看了结果我想你就不会说的这么轻松。
还有一点,在你最后的回复中也体现了你对缓存使用的不明确。为什么你要使用缓存来避免数据库的操作?很多时候是因为数据库查询的开销很大。而你却忽视这种开销,说是选择拿掉锁。在真实环境中,面对这种开销很大的数据库操作,你的代码很可能就造成一次资源占用的峰值,带来的连锁反映也可能是致命的,对企业来说就是钱的问题。
最后的结果可以预料。开销大的数据操作,在很多并发的情况下,几乎同时执行了大量并发数据库操作,后果不用说了吧。。。。
楼主只是拿一段sample代码来说明一下查询事务的粒度问题,和并发的关系并不是很大。
你既然那么关心并发问题,那么请拿出代码来解决问题。莫非你所有的查询操作全都是做同步处理的?
25 楼
terranhao
2008-09-02
wym0291 写道
楼主,你没发现你这个getObject方法本身就是一个错误么?而你却把这个方法代码级的错误延伸到事务上去了,佩服佩服。。。。。
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
再看代码。因为没考虑到并发问题,你这个方法代码在多线程情况下,明显会造成多次重复的查询数据库,而这个查询数据库的动作原本只应该执行仅一次而已。
建议你换个方法来诠释你的主题。这个getObject方法明显不适合你这个主题。你要对这个方法里的ObjectDao.getfromDB方法进行封装,最好是封装成一个FutureTask,直接利用FutureTask的get方法来获得并发时的阻塞效果,防止多次重复执行查询数据库。
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
再看代码。因为没考虑到并发问题,你这个方法代码在多线程情况下,明显会造成多次重复的查询数据库,而这个查询数据库的动作原本只应该执行仅一次而已。
建议你换个方法来诠释你的主题。这个getObject方法明显不适合你这个主题。你要对这个方法里的ObjectDao.getfromDB方法进行封装,最好是封装成一个FutureTask,直接利用FutureTask的get方法来获得并发时的阻塞效果,防止多次重复执行查询数据库。
高手,我理解你的意思了
24 楼
wym0291
2008-09-01
ahuaxuan 写道
wym0291 写道
楼主,你没发现你这个getObject方法本身就是一个错误么?而你却把这个方法代码级的错误延伸到事务上去了,佩服佩服。。。。。
你从什么地方看出来这个getObject方法本身是错误的????
错在哪里,我也挺佩服你的,大家都没有看出来,就你看出来了.
wym0291 写道
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
从你这段话里可以看出来你还不明白我在说什么,我很少用"建议"这个词,因为这让人有种据高凌下的感觉,但是我真的建议你再把文章看两遍
wym0291 写道
再看代码。因为没考虑到并发问题,你这个方法代码在多线程情况下,明显会造成多次重复的查询数据库,而这个查询数据库的动作原本只应该执行仅一次而已。
这里即使有产生并发,最多就是多查两遍数据库, 查完之后就不会再查了,显然这个是小问题,因为这并发的几次查询都是一样的数据.如果加同步,那么不管是查数据库还是缓存都有锁的开销,显然我的选择是在真正并发的时候拿掉锁,宁可多查两遍数据库
我之所以说错误,就是你的代码没有实现你的设计目的。我用建议只是因为你的帖子刚开头就让人看到一段有问题的代码,实在让人感觉有点草率。
.....很明显,你对并发问题一点不敏感,从你回答中也可以看出,你对如何解决这种并发也是知之甚少。上面我已经提过了可以提供类似FutureTask来构建一种基于任务的阻塞,你估计连查都没查一下这种方法的使用,就来谈开销。还说最多就是多查两便数据库。。。。。你还是去实践一下,你就测下100个并发,看看到底查了几次数据库,看了结果我想你就不会说的这么轻松。
还有一点,在你最后的回复中也体现了你对缓存使用的不明确。为什么你要使用缓存来避免数据库的操作?很多时候是因为数据库查询的开销很大。而你却忽视这种开销,说是选择拿掉锁。在真实环境中,面对这种开销很大的数据库操作,你的代码很可能就造成一次资源占用的峰值,带来的连锁反映也可能是致命的,对企业来说就是钱的问题。
最后的结果可以预料。开销大的数据操作,在很多并发的情况下,几乎同时执行了大量并发数据库操作,后果不用说了吧。。。。
23 楼
ahuaxuan
2008-09-01
wym0291 写道
楼主,你没发现你这个getObject方法本身就是一个错误么?而你却把这个方法代码级的错误延伸到事务上去了,佩服佩服。。。。。
你从什么地方看出来这个getObject方法本身是错误的????
错在哪里,我也挺佩服你的,大家都没有看出来,就你看出来了.
wym0291 写道
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
从你这段话里可以看出来你还不明白我在说什么,我很少用"建议"这个词,因为这让人有种据高凌下的感觉,但是我真的建议你再把文章看两遍
wym0291 写道
再看代码。因为没考虑到并发问题,你这个方法代码在多线程情况下,明显会造成多次重复的查询数据库,而这个查询数据库的动作原本只应该执行仅一次而已。
这里即使有产生并发,最多就是多查两遍数据库, 查完之后就不会再查了,显然这个是小问题,因为这并发的几次查询都是一样的数据.如果加同步,那么不管是查数据库还是缓存都有锁的开销,显然我的选择是在真正并发的时候拿掉锁,宁可多查两遍数据库
22 楼
wym0291
2008-09-01
楼主,你没发现你这个getObject方法本身就是一个错误么?而你却把这个方法代码级的错误延伸到事务上去了,佩服佩服。。。。。
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
再看代码。因为没考虑到并发问题,你这个方法代码在多线程情况下,明显会造成多次重复的查询数据库,而这个查询数据库的动作原本只应该执行仅一次而已。
建议你换个方法来诠释你的主题。这个getObject方法明显不适合你这个主题。你要对这个方法里的ObjectDao.getfromDB方法进行封装,最好是封装成一个FutureTask,直接利用FutureTask的get方法来获得并发时的阻塞效果,防止多次重复执行查询数据库。
首先,你这个方法的正确逻辑应该是第一次执行时,缓存中没有数据,然后从数据库中去查数据并放入缓存。之后的方法执行都会直接从缓存中获得数据。也就是说,应该只有一个connection对象会被使用一次。而你得出的结论确实有多个connection被使用。你不觉得这首先就是你方法的代码级别出现了错误,造成了这个后果么?
再看代码。因为没考虑到并发问题,你这个方法代码在多线程情况下,明显会造成多次重复的查询数据库,而这个查询数据库的动作原本只应该执行仅一次而已。
建议你换个方法来诠释你的主题。这个getObject方法明显不适合你这个主题。你要对这个方法里的ObjectDao.getfromDB方法进行封装,最好是封装成一个FutureTask,直接利用FutureTask的get方法来获得并发时的阻塞效果,防止多次重复执行查询数据库。
21 楼
neptune
2008-08-27
apache 装上就有了
20 楼
vv0885
2008-08-27
ab -c 100 –n 1000 http://xxxx.xxx.xxx/xxx
这个测试是怎么做的,用工具吗?
在回复中找到了!
这个测试是怎么做的,用工具吗?
在回复中找到了!
19 楼
neptune
2008-08-27
如果你使用spring,最好的方法还是不要使用的声明式事务了(或只对更新数据库的方法声明式事务),最好使用TransactionTemplate对象手工完成,例如,以下针对hibernate的应用:
Object o = cacheClient.get(key);
if(o==null){
readOnlyTransactionTemplate.execute(
new transactionCallback(TransactionStatus transactionStatus){
o = dao.get(key);
}
}
return o;
代码把cache代码和事务代码分隔到不同的方法,然后再做一个外壳方法,这种只使用于复杂的业务方法。如果只是一个简单的读取,我觉的还是上面的代码会好一些
Object o = cacheClient.get(key);
if(o==null){
readOnlyTransactionTemplate.execute(
new transactionCallback(TransactionStatus transactionStatus){
o = dao.get(key);
}
}
return o;
代码把cache代码和事务代码分隔到不同的方法,然后再做一个外壳方法,这种只使用于复杂的业务方法。如果只是一个简单的读取,我觉的还是上面的代码会好一些
18 楼
mycybyb
2008-08-26
个人感觉稍微复杂一点就不行。
比如如果一个表,只有一块代码维护,那么可以用aop来作缓存。如果多个地方维护,或者是多表连查,别的表的数据变了,缓存就失效了。
比如如果一个表,只有一块代码维护,那么可以用aop来作缓存。如果多个地方维护,或者是多表连查,别的表的数据变了,缓存就失效了。
17 楼
fireflyc
2008-08-26
是这样的,做缓存的时候是缓存DAO层的内容而不是service层的内容。
我现在的问题是把hibernate的二级缓存禁用掉,使用spring的aop来做缓存,会不会带来什么副作用?
我现在的问题是把hibernate的二级缓存禁用掉,使用spring的aop来做缓存,会不会带来什么副作用?
16 楼
mycybyb
2008-08-25
前几天看到过AOP缓存,也觉得应用起来比较困难。
15 楼
ericxu131
2008-08-25
NicholasBugs 写道
ahuaxuan 写道
/**
*作者:张荣华
*日期:2008-08-22
**/
就是这段代码,放到tomcat中,我的同事william测出来的结果是,单线程请求(tomcat的thread pool中在同一时间只有一个处理请求的线程被调用):ab -c 1 –n 1000 http://xxxx.xxx.xxx/xxx
结果是每秒中只能处理20个请求, 我的天啊, 20个,改多线程呢,: ab -c 100 –n 1000 http://xxxx.xxx.xxx/xxx, 一百个线程请求1000次,晕倒,还是每秒只能处理20多个请求.
*作者:张荣华
*日期:2008-08-22
**/
就是这段代码,放到tomcat中,我的同事william测出来的结果是,单线程请求(tomcat的thread pool中在同一时间只有一个处理请求的线程被调用):ab -c 1 –n 1000 http://xxxx.xxx.xxx/xxx
结果是每秒中只能处理20个请求, 我的天啊, 20个,改多线程呢,: ab -c 100 –n 1000 http://xxxx.xxx.xxx/xxx, 一百个线程请求1000次,晕倒,还是每秒只能处理20多个请求.
尽量细化事务粒度,深有同感,想问一下,这个测试是怎么做的,用什么工具?
apache ab工具
apache 安装目录bin下的
14 楼
zby.110
2008-08-25
呵呵,我们项目都不用事务
13 楼
ahuaxuan
2008-08-25
downpour说得没错:
缓存在不同得层次上使用的效果确实是不一样的,不过一般来说它们有一下规律:
越靠近用户(浏览器或者其他客户端)的地方,性能效果越明显,但是局限性越高
越远离用户(浏览器或者其他客户端)的地方,局限性越小(可以加入更多的逻辑),但是性能显然没有前者高
所以程序员要做的就是在缓存和用户的距离之间做权衡,做权衡的时候要考虑的因素很多,本文描述的也是要考虑的一个方面
而对于基于aop的methodCache,是我们最经常用的缓存方法之一,该方法最早是04年有人提出来的,那时候spring才正式立项不久,到现在4年过去了,methodcache也成为了最常用的技术之一,
不过即使使用methodcache,我们同样要考虑是否关联到事务的问题
缓存在不同得层次上使用的效果确实是不一样的,不过一般来说它们有一下规律:
越靠近用户(浏览器或者其他客户端)的地方,性能效果越明显,但是局限性越高
越远离用户(浏览器或者其他客户端)的地方,局限性越小(可以加入更多的逻辑),但是性能显然没有前者高
所以程序员要做的就是在缓存和用户的距离之间做权衡,做权衡的时候要考虑的因素很多,本文描述的也是要考虑的一个方面
而对于基于aop的methodCache,是我们最经常用的缓存方法之一,该方法最早是04年有人提出来的,那时候spring才正式立项不久,到现在4年过去了,methodcache也成为了最常用的技术之一,
不过即使使用methodcache,我们同样要考虑是否关联到事务的问题
发表评论
-
过滤字符的性能调优?挤一挤还是有的
2010-05-29 05:54 3616/* *auth ... -
Master-Slave,Spring,Hibernate,故事曲折离奇,情结跌宕起伏
2009-02-05 13:49 8705/** *作者:张荣华 *日期 ... -
弃成见,反省,并重新认识struts.i18n.encoding
2008-12-24 15:42 3900[size=medium]之前和大家讨论了struts2.0中 ... -
看看mina和memcached的联姻(适合不同语言客户端,高并发?)
2008-07-21 17:06 7998[size=medium]/** * 作者:张荣华 * 日 ... -
如何解决mysql的master-slave模式中ReplicationDriver的使用问题
2008-06-19 18:23 8229/** * 作者:张荣华 * 日期:2008-6-19 ... -
别装了,难道你们不想把properties直接注入到object中去(spring-plugin)?
2008-04-09 18:01 3664[size=small]/** *作者:张荣华(ahuaxu ... -
用jamon来监控你的sql执行效率
2008-02-25 15:48 3722/** *作者:张荣华 *日期:2008-2-25 ... -
java同msn的通信,大家想想用途吧
2007-11-24 17:14 2520程序员的生活真是单调,除了编程还是编程,工作日 ... -
EAI企业应用集成场景及解决方案
2007-09-21 18:21 3161/** *作者:张荣华(ahuaxuan) *2007-9 ... -
quartz和应用的集群问题
2007-08-21 18:36 12831之前看到很多关于quartz的讨论,尤其是关于quar ... -
优化程序之前,可用Jamon来监测你的Spring应用
2007-08-14 18:14 8149/** *作者:张荣华(ahuaxuan) *2007-8-1 ... -
请问责任链真的是一种设计模式吗
2007-07-26 18:12 9442坛子上讨论设计模式的也挺多的,但是关于这个责任链模式还没有人提 ... -
把ActiveMQ的控制台整合到你的web程序中
2007-07-19 12:06 8856在使用ActiveMQ的时候把ActiveMQ的控制台整 ... -
设计模式之:解剖观察者模式
2007-07-17 16:12 6872[size=9] 论坛上很多人都 ... -
java邮件:在简单和复杂之间的方案
2007-07-11 18:07 7589/** *作者:张荣华(ahuaxu ... -
强强连手, 在模板中分页,看Freemarker和displaytag的结合
2007-07-09 09:22 6931/** *作者:张荣华(ahuaxuan) *2007-0 ... -
解惑:在spring+hibernate中,只读事务是如何被优化的。
2007-06-28 18:22 7626/** *作者:张荣华(ahuaxuan) *2007- ... -
让webwork零配置 第二章(实现)(实例已放出,大家可以下载运行)
2007-06-25 09:23 5715/** *作者:张荣华(ahuaxuan) *2007-0 ... -
让webwork2零配置,第一章(主贴再次更新)
2007-06-18 15:41 13401/** *作者:张荣华(ahuaxuan) *2007-0 ... -
Spring声明式事务管理源码解读之事务提交
2007-06-11 09:19 7290/** *作者:张荣华(ahuaxuan) *2007-0 ...
相关推荐
函数的定义包括`RETURNS`关键字,声明返回类型,以及`BEGIN`和`END`之间的函数主体。 - **调用函数**:创建完函数后,可以在查询中通过函数名和括号内的参数来调用。例如,`dbo.fn_mathtutor (5,6)`返回30。 - **...
总的来说,AspectJ的`target`、`this`和`within`关键字是构建切面逻辑的关键元素,它们提供了对通知应用范围的细粒度控制。理解并掌握这些关键字的使用,有助于提升AOP编程的效率和质量。对于阅读本文的开发者来说,...
3. Lock接口与实现:Java提供Lock接口(如ReentrantLock可重入锁)及其子类,提供了更细粒度的控制,可以实现公平锁、非公平锁、可中断锁和读写锁等。 4. Condition接口:配合Lock使用,可以实现线程间的条件等待,...
### 游标、事务、锁知识点详解 #### 一、游标 **1.1 游标概述** 游标是一种数据库对象,它提供了一种机制,使得开发人员能够逐行处理查询结果集中的数据,而不仅仅是批量处理。游标允许用户在结果集上向前或向后...
这可能涉及到数据库事务、锁机制以及线程局部存储(ThreadLocal)等。 总的来说,理解并熟练掌握Java中的单线程和多线程编程是开发高效客户端-服务器应用的基础。通过合理设计和利用线程,可以显著提升应用程序的...
- Lock接口及其实现:ReentrantLock、ReadWriteLock等提供更细粒度的锁控制。 5. **Spring MVC工作流程**: - 用户请求到达前端控制器DispatcherServlet。 - DispatcherServlet根据请求映射找到对应的...
1. **去除重复列值**:在SQL查询中,使用`DISTINCT`关键字可以去除结果集中重复的列值,确保返回的每一行都是唯一的。 2. **快照(Snapshot)**:快照是数据库在特定时间点的完整状态记录,主要用于备份和恢复。它...
- 使用`EXPLAIN`关键字查看查询执行计划,了解查询的实际执行路径。 - 根据执行计划调整查询语句或索引策略,提高查询效率。 - **性能剖析(Profiling)**: - 设置`profiling=1`开启性能剖析功能。 - 使用`SHOW...
-事务管理:正确配置事务边界,防止事务丢失和脏读。 8. **性能优化** - 内存优化:避免内存泄漏,合理设置对象缓存,控制对象生命周期。 - 资源释放:及时关闭数据库连接、网络连接等资源,防止资源泄露。 - ...
- Final关键字:final用于声明变量不可变,类不可继承,方法不可覆盖。 - 内部类和外部类互访:内部类可以访问外部类的所有成员,包括私有成员;外部类访问内部类需通过实例化内部类对象。 2. **Spring框架**: ...
MySQL是世界上最受欢迎的关系型数据库管理系统之一,其底层原理对于数据库管理员和开发人员来说至关重要,能够帮助他们优化查询性能,理解事务处理,以及更好地管理数据存储。以下是对标题和描述中涉及知识点的详细...
- **索引覆盖**:查询结果只需要索引即可获取,无需访问实际的数据行。 **4.4 索引有哪几种类型?** - **唯一索引**:索引列的值必须唯一。 - **全文索引**:用于全文搜索。 - **组合索引**:基于多个列构建的索引...
-事务管理:正确设置事务隔离级别,防止脏读、不可重复读、幻读等问题。 - ORM框架:如MyBatis、Hibernate等,注意避免N+1查询和笛卡尔积问题。 6. **安全控制**: - 输入验证:对用户输入进行校验,防止SQL注入...
10. 意向锁:意向锁是多粒度锁定的一种形式,包括意向共享锁(IS)和意向排他锁(IX),用于在执行复杂查询时提供锁的兼容性。 单项选择题涉及的数据库基础知识包括数据库的定义、数据库的层次、数据一致性、数据...
对于更细粒度的控制,可以使用同步代码块,只锁定必要的代码区域: ```java synchronized (object) { // 临界区 } ``` 在这里,`object`是监视器锁的对象,只有获取到这个对象的锁的线程才能进入代码块。 3....
- 数据库事务:ACID特性,以及事务的隔离级别。 12. **Spring框架** - DI(Dependency Injection)依赖注入,通过XML配置或注解实现。 - AOP(Aspect Oriented Programming)面向切面编程,实现日志记录、权限...
2. **使用EXPLAIN分析查询**:在SELECT语句前添加EXPLAIN关键字,了解MySQL如何执行查询,识别性能瓶颈,比如未充分利用的索引或不必要的全表扫描。 3. **LIMIT 1提升效率**:当只需要一条数据时,添加LIMIT 1,让...
- **查询与表表达式**:查询是SQL中最常用的部分之一,通过SELECT语句来检索数据;表表达式则涉及对多个表进行联接操作以获取所需信息。 - **DB2 Call Level Interface (CLI) 和 Open Database Connectivity (ODBC)*...