冰箱的摆放风水禁忌

摆置冰箱有哪些风水禁忌
  1、冰箱不宜正对炉火:会导致家中女性身体多疾病与夫妻争执丶对立。有这种情况,冰箱要离炉火远一点,降低影响。
2、冰箱不宜对电磁炉或微波炉:会带来争执与破财。有这种状况,要把电磁炉和微波炉另外放。
3、冰箱不宜正对大门:容易犯小人是非,与人相处容易产生误解与对立。有这种状况请移动冰箱开啓位置,或者安放门帘。
4、冰箱不宜正对厕所:容易发生家中人疾病与开刀丶血光事宜。有这种状况请移动冰箱开啓位置,或者安放门帘。
5、冰箱不宜正对房间门:容易使居住房间之人不好相处与经常争吵。有这种状况请移动冰箱开啓位置,或者安放门帘。
6、冰箱不宜正对神桌:会影响学业/工作发展与事业发展。有这种状况请移动冰箱开啓位置。
7、冰箱不宜正对水族箱:会时常遭致莫名破财厄运。有这种状况请移动冰箱开啓位置,或者移动水族箱。
8、冰箱不宜放置在房间:会导致房间的人个性极端丶思考偏激丶工作财运皆不顺利。
9、冰箱不宜安置於客厅:会让全家人感情冷淡、互动不佳、财运不顺。
冰箱五行属金丶属水,如果有以上冲煞的情况,也可以在冰箱旁安放大型观赏盆栽,吸纳过剩的水气。或者随身佩带黑曜石,改善身体状况与小人作祟。

冰箱的风水禁忌
1、冰箱不能为空
冰箱与家人的饮食息息相关,冰箱中的食物充足有衣食无忧的寓意;而空空的冰箱则有经济状况不佳的不良诱导。所以一定要经常保持冰箱的充足状态。
2、冰箱门不宜与门相对
不管冰箱在哪里摆放,都要尽量避开冰箱门与门相对的情形。否则气流的相冲容易造成财运不稳定的情况,甚至有破财的隐忧。
3、冰箱上不宜摆放其他电器
有的朋友把微波炉、烤箱、榨汁机、豆浆机等小电器放在了冰箱上。从风水的角度来讲,各种电器发射出的电磁等气流会搅动周围的气场,对主人的健康带来不利的影响。
4、冰箱上面不要堆放杂物
冰箱和财运有关,冰箱上面堆放杂物不利于聚财。建议大家要做好清洁工作,保持冰箱顶部的清洁。
5、冰箱不宜与灶台太近
灶台属火,冰箱属金,五行相克;而且灶台工作时散发出来的是热气,冰箱敞开时则放出冷气,冷热相激。对女主人的肠胃健康有影响,还可能引发家人之间的争执。

冰箱放在什么位置好
  虽然说厨房是一个非常拥挤的地方,但是冰箱的入驻却也不怎么会影响其整体空间的规划,因为厨房中蚕蛹的都是内建式的橱柜,冰箱通常是贴着一面墙摆放,但是各种家电和橱柜将厨房的每面墙都占据后,厨房也就只剩下一点点空间了,在风水上这是拥挤而不吉的典型布局,其中烤箱、火炉与冰箱及水槽,不是一个挨著一个放置,就是相互对冲。这种设计下常常会出现问题无法解决的情形,这时能做的只有把能换的换掉,并尽量保持整个空间的清洁、整齐、明亮,空气也要流通。
而通常情况下,为了使用上的便利,我们都会将冰箱摆放在厨房中。厨房是火旺之地,以厨房之火克冰箱之金,在不知不觉中就消耗了厨房的火性,对厨房的火性有所减弱,有利于厨房的五行平衡。但是需要注意的是冰箱不能太靠近炉灶或正对,因炉灶油烟太多,这样容易使冰箱被污染,直接影响主人的健康。但冰箱放在厨房内,可以减低厨房的火性,降低发生火灾的可能,因此冰箱在厨房内尽量离炉灶远一点就行了。
有些人认为冰箱可放在家中的凶位,用冰箱的冰冷沉重来镇压凶星;有些人则认为冰箱不能摆在凶位,说冰箱二十四小时不停运行,如果放在凶位,会惊动凶星,刺激它肆虐逞威,与风水中“凶方宜静不宜动”相矛盾。而家居中所谓的吉凶方位并不是那么绝对,而是相对的。所以,也不能绝对地说哪里是凶位,绝对不适合冰箱的摆放,而应该结合家中的具体情形,家人的命理喜忌,这才能找到家中最适合冰箱摆放的位置。
在决定冰箱摆放位置时,还有一件事必须考虑到:理想的状况下,不应该让冰箱对著厨房的门。风水大师认为,这种摆置会让来自门口及电冰箱的两股能量相衝突,导致家庭失和以及财务发生问题。

 

from:http://www.azg168.cn/jiajufengshui/2014-03-22/home69020.html

PowerDesigner更改数据库类型

工具:powerdesigner

数据库:sql server2000 --> mysql 5.0

1、更改数据库

14207159_1

将DBMS里的数据库(原为sqlserver2000)改为mysql5.0

2、生成mysql脚本

Database --> Generate Database

在directory里选择生成的sql脚本存放的路径;

在file name里修改sql脚本的名字;

one file only 勾选后,只生成一个sql脚本;不勾选,按照表的个数生成多个脚本;

由于建模时是按sqlserver标准设计的,转成mysql会有错误,取消掉check model前面的勾可避免生成sql脚本时发生如下的错误:

Generation aborted due to errors detected during the verification of the model.

其他错误见结尾

为了避免错误,在此将create primary key改为inside

format选项卡里主要修改文本格式,如果有中文,改成UTF-8,避免生成的脚本有乱码;

在这个选项卡里选择workspace,点击旁边的include sub_objects,在下面的列表里会显示该工程下的所有表。点击select all,列表中的所有表前面会出现勾(也可以只选择几个表)。

点击确定,完成脚本生成。
———————————————————--

常见问题

自增问题: 解决方法如下:

在你所要设为自增型的键上(比如你的 id )双击,弹出一个 Column Properties 对话框,右下角有一个 Identify 的选择框,选中它 OK ,就可以了。

再去查看 Preview ,就能看到用大写标识出来的 AUTO_INCREMENT 。

右键表属性在physical options中可设置表类型MYISAM

设置字段默认值 :双击表,出现 column 列表,双击要设置的列的左边的灰色框,应该会弹出新的窗口,然后在新窗口上选择 standard   checks   ,里面有 default 的默认值

SQL Server2008附加数据库失败 错误号:5120

附加数据库时,显示错误,错误信息为

一种解决方法为,设置mdf文件所在文件夹的权限(有些资料说只设置mdf文件的权限就好,但我试了不管用),在文件夹上右击——属性——安全,如图所示:

选择组或用户名中的Authenticated Users,点击编辑修改权限,选中允许完全控制,确定即可。

另一种解决方法,查了资料说不要用sa账户登录,用windows身份验证登录就可以附加成功,但是我的正好相反,用windows身份验证登录不能成功,用sa账户登录就可以附加成功。(真是无比神奇呀~~如果有大牛知道为啥麻烦告知一下,不胜感激呀)

from:http://blog.163.com/zhang_ying112/blog/static/19826008020128308257856/

Membership的配置文件

童安格

童安格1959年7月26日生于台湾高雄的著名歌手、音乐人,原籍浙江杭州,是华语乐坛较早的实力派歌手。集作曲、作词、演唱于一身,是典型的浪漫主义爱情故事的发言人。与王杰周华健齐秦并称台湾四大天王(1985—1999年)

人物经历

1959年7月,出生于台北。1964年入松江幼稚园,参加“松江天使合唱团”。[1]
1965年,就读台北市长春国民小学,参加该校合唱团。[1]
1966年,有一天和同学玩耍的时候,左手食指不小心被门夹断,担心指头会少一截, 哭个不停。[1]
1967年,因为手受伤可以不用写字,减少了许多功课上的负担,每天都过得很快乐。但也造就成以后国文程度的低落。[1]
1968年,代表学校参加高雄国小独唱组比赛,得第二名。
1969年,受洗为基督徒。
1970年,入介寿国中。参加学校乐队及童子军。
1971年,因成绩不佳,留级重读一年。
1972年,参加学校举行的“行的安全”漫画比赛,得第三名。
1973年,参加救国团活动表现优良,得“团结互助奖”。
1974年顺利毕业。
1975年,考上复兴商工美工科,画得一手好素描。担任军乐队指挥,并参加歌咏社,当教唱员。
1976年,是学校的锋头人物,初恋开始。
1977年,组成“旅行者三重唱”。此时正值毕业及当兵前夕,开始在外做小型演唱,迷糊中毕业了。
1978年,与银霞合录“兰花草”唱片与电视专辑。灌录《旅行者》三重唱专辑。
1979年,徽召入伍,旅行者也渐渐销声匿迹。
1980年,到金门服役,开始人生的一段磨练,军职是“师令部号兵” 并教军官唱军歌。
1981年,从军中退伍,心情并不愉快,因为将面临另一个新生活考验,唯一乐观的是两年军中生活写了三十几首创作歌曲。
1982年,遇到了很多困难的事,找不到工作,朋友也各奔西东。最后找到一家卡通公司,从事卡通的工作。同年发表了《此情永不留》及《夜色》两首歌曲。
1983年,幸运作出几首受欢迎的歌曲,引导创作的另一个开始。同时在“宝丽金”唱片公司担任助理制作。
1984年,开始策划自己的专辑唱片。
1985年,出版首张个人专辑《想你》。
1985年,出版第二张个人专辑《女人》,第一次接触戏剧,参加中视单元剧《风筝》担任男主角。
1986年,出版第三张个人专辑《我曾经爱过》跃上大银幕,参加导演陈坤厚所导的电影《流浪少年路》,和刘瑞琪分饰男女主角,童安格并担任电影配乐制作。[3]
1987年,出版第四张个人专辑《跟我来》[4] 。同年度得到台湾票选最受欢迎男歌星奖,并为由朱延平导演的电影《好小子》担任配乐制作工作。
1988年,演出第一部连续剧《新不了情》由李岳峰导演,担任男主角。
1989年,出版第五张个人专辑《其实你不懂我的心》这张专辑在不到一年的时间红遍了全台湾,成为本年度全台湾男歌手唱片销售量冠军,更深得内地人民的喜爱,此时为童安格全盛时期。
1989年,出版第六张个人专辑《梦开始的地方》以本专辑得到IFPI的白金唱片奖。
1990年,出版第七张个人专辑《花瓣雨》开始将自己推向国际化音乐创作者的地位,同时因与梅艳芳合唱《明天你是否依然爱我》而打入香港市场,占有一席之地后,香港许多艺人纷纷邀歌,或是翻唱童安格专辑中的歌曲,他还担任谭咏麟演唱会上的特别嘉宾。
1990年,以《忘不了》精选集得到IFPI的白金唱片奖。同年推出第八张个人专辑《真爱是谁》。
1990年,以《耶利亚女郎》得到香港无线电台(TVB)十大劲歌金曲“国语歌曲银奖”。
1990年,在香港电台第十三届十大中文金曲,以《夕阳醉了》得到“最佳作曲奖”,以《把根留住》得到“优秀国语歌曲奖”。
1991年,出版第九张个人专辑《一世情缘》。
1991年,第一次在内地举办十场个人巡回演唱会,造成轰动一票难求,同年得到内地广东首届金唱片的“最受欢迎男歌星奖”及内地“最受欢迎流行音乐生力奖”。
1992年,入围台湾第三届金曲奖“最佳国语男演唱人奖”。
1993年,出版第十张个人专辑《爱与哀愁》同年得到内地中国唱片公司的“金唱片奖”,这是除了内地歌手外,第一位台湾歌手得奖。同时以第一到第三张专辑获得内地中国唱片公司的“演唱金唱片奖”。
1993年,以《把根留住》一曲,再度吸引成千上万的同胞喜爱,并得到“中国十大受欢迎歌手奖”。同年第二次在内地举办十场个人巡回演唱会,更是欲罢不能。
1993年,参加由李岳峰导演的连续剧《秦俑》,担任男主角及配乐工作。
1994年,受邀到美国拉斯维加斯MGM饭店,举行个人演唱会。演出场地是MGM GRAND开幕时芭芭拉.史翠珊演出的地点,童安格是台湾第一位在此场地演出的歌手,深感荣幸。
1994年,出版第十一张个人专辑《现在以后》蝉联得到“中国十大最受欢迎歌手奖”。
1994年,七月在台北国父纪念馆及台中举办三场个人演唱会。
1994年,担任第三十一届金马奖“最佳电影歌曲”颁奖人。
1995年,出版第十二张个人专辑《听海的歌》同年七月加盟点将唱片公司。
1995年,出版个人第十三张专辑《看未来有什么不一样》,专辑中收录一首上海电视台连续剧《上海一家人》插曲《永远不再》。
1996年,出版个人第十四张专辑《收留》。拍摄韩国健康饮料广告。移民加拿大。
1997年,参与美加地区之演唱会。组织太阳星系棒球队赴加拿大进行跨国友谊赛。拍摄电视连续剧“战国红颜”。
1998年,于美国拉斯维加斯及大西洋城举行个人演唱会。加拿大温哥华中文电台主持“童话安格”广播节目。
1999年,参与中国各地演唱会演出。拍摄电视连续剧《西螺七崁》。
2000年,参与中国各地演唱会演出。拍摄电视连续剧《卧虎藏龙》。
2001年,开始整理早年创作,为新专辑做准备。为中国品牌商品担任代言。
2002年,完成新专辑之音乐创作。赴意大利宣慰侨胞。
2003年,拍摄电视连续剧《梦里花开》,并负责剧中主题曲之词曲制作及演唱。出版第十五张个人专辑《青春手卷》。
2006年6月26日晚,“成龙好朋友慈善演唱会”在哈尔滨举行,童安格助阵,为哈尔滨人民献上一场精彩的表演,所得收入将全部捐献给哈尔滨慈善机构。
2006年12月1日到2日童安格在北京展览馆剧场举办个唱会,在全力备战演唱会的同时,他还不忘热心公益事业,成为11月北京交通安全月的形象大使。
2008年12月31日,童安格“把歌留住”个人演唱会火爆开场,一首首经典老歌不仅在寒冷的冬夜给人以无限的温暖,而且给整个津门献上了一份隆重的新春贺礼。

个人生活

在童安格个人简介中写着他的最大心愿:做好的音乐、做好的歌手、做好的老公。可是前两点都实现了,但他的婚姻却失败了。2004年,童安格与妻子陈美馨正式离婚,而两人在此之前感情就亮起红灯,已经分居一年。
其实,在歌坛有“情歌王子”和“绝种好男人”双重美誉的童安格鲜有情感方面的新闻,但其18年婚姻失败却着实令歌迷大感意外。而童安格曾透露了自己心境:“说心里话,我现在真的感到很不开心,有一种失落的感觉。毕竟18年的感情和生活,我们中国人有一句名言‘一日夫妻百日恩’,何况我们之间在一起那么久了,从一个家庭生活的状态回到相当久以前的单身状态,的确很难接受,在短时间内蛮难适应的……”
前妻陈美馨曾长期为童安格担任演艺事业的经理人,两人在工作中的思路常常无法达成一致也是导致他们在生活中矛盾加深的根源之一。两个人在工作的想法上难免会有分歧,如果他们不是夫妻关系,可能问题会更好解决。
虽然两人已分手,但童安格还是非常感激前妻:“事实证明有她给我打理事业,使我有更多的精力放在业务上。我要发自内心地谢谢她,带给我许多欢乐和幸福的时光,在我烦恼不顺心的时候默默地安慰陪伴我,这些我都是忘不了的,结局虽然是令人失望的,但美好的过程终究应当成为永远难忘的记忆中的片段。”

Lucene的IndexWriter初始化时报Lock obtain timed out: NativeFSLock

有一次更新完代码启动系统,系统报如下异常:

 

经查,IndexWriter的构造函数在试图获取另外一个IndexWriter已经加锁的索引目录时就会抛出一个LockObtainFailedException。但是我们系统中并没有多个IndexWriter去指定同一个目录,走查代码才发现,原来是同一个初始化方法调用了两次,一次是spring监听器里调用的,另一个是另一同事测试方便,在static模块里调用的,导致同一个lucene上下文初始化方法被调用两次,建议static模块要慎用,因为它会悄悄神不知鬼不知自己悄悄被调用。

from:http://www.tuicool.com/articles/2UBVnq7

影响Lucene索引速度原因以及提高索引速度技巧

在网上看了一篇外文文章,里面介绍了提高Lucene索引速度的技巧,分享给大家。
先来看下影响索引的主要因素:

MaxMergeDocs
该参数决定写入内存索引文档个数,到达该数目后就把该内存索引写入硬盘,生成一个新的索引segment文件。
所以该参数也就是一个内存buffer,一般来说越大索引速度越快。
MaxBufferedDocs这个参数默认是disabled的,因为Lucene中还用另外一个参数(RAMBufferSizeMB)控制这个bufffer的索引文档个数。
其实MaxBufferedDocs和RAMBufferSizeMB这两个参数是可以一起使用的,一起使用时只要有一个触发条件满足就写入硬盘,生成一个新的索引segment文件。

RAMBufferSizeMB
控制用于buffer索引文档的内存上限,如果buffer的索引文档个数到达该上限就写入硬盘。当然,一般来说也只越大索引速度越快。
当我们对文档大小不太确定时,这个参数就相当有用,不至于outofmemory error.

MergeFactor
这个参数是用于子索引(Segment)合并的。
Lucene中索引总体上是这样进行,索引现写到内存,触发一定限制条件后写入硬盘,生成一个独立的子索引-lucene中叫Segment。一般来说这些子索引需要合并成一个索引,也就是optimize(),否则会影响检索速度,而且也可能导致open too many files。
MergeFactor 这个参数就是控制当硬盘中有多少个子索引segments,我们就需要现把这些索引合并冲一个稍微大些的索引了。
MergeFactor这个不能设置太大,特别是当MaxBufferedDocs比较小时(segment 越多),否则会导致open too many files错误,甚至导致虚拟机外面出错。

Note: Lucene 中默认索引合并机制并不是两两合并,好像是多个segment 合并成最终的一个大索引,所以MergeFactor越大耗费内存越多,索引速度也会快些,但我的感觉太大譬如300,最后合并的时候还是很满。Batch indexing 应 MergeFactor>10

加快索引的一些技巧:

• 确认你在使用最新的Lucene版本。

• 尽量使用本地文件系统

远程文件系统一般来说都会降低索引速度。如果索引必须分布在远程服务器,请尝试先在本地生成索引,然后分发到远程服务器上。
• 使用更快的硬件设备,特别是更快的IO设备

• 在索引期间复用单一的IndexWriter实例

• 使用按照内存消耗Flush代替根据文档数量Flush
在Lucene 2.2之前的版本,可以在每次添加文档后调用ramSizeInBytes方法,当索引消耗过多的内存时,然后在调用flush()方法。这样做在索引大量小文档或者文档大小不定的情况下尤为有效。你必须先把maxBufferedDocs参数设置足够大,以防止writer基于文档数量flush。但是注意,别把这个值设置的太大,否则你将遭遇Lucene-845号BUG。不过这个BUG已经在2.3版本中得到解决。

在Lucene2.3之后的版本。IndexWriter可以自动的根据内存消耗调用flush()。你可以通过writer.setRAMBufferSizeMB()来设置缓存大小。当你打算按照内存大小flush后,确保没有在别的地方设置MaxBufferedDocs值。否则flush条件将变的不确定(谁先符合条件就按照谁)。

• 在你能承受的范围内使用更多的内存
在flush前使用更多的内存意味着Lucene将在索引时生成更大的segment,也意味着合并次数也随之减少。在Lucene-843中测试,大概48MB内存可能是一个比较合适的值。但是,你的程序可能会是另外一个值。这跟不同的机器也有一定的关系,请自己多加测试,选择一个权衡值。

• 关闭复合文件格式
调用setUseCompoundFile(false)可以关闭复合文件选项。生成复合文件将消耗更多的时间(经过Lucene-888测试,大概会增加7%-33%的时间)。但是请注意,这样做将大大的增加搜索和索引使用的文件句柄的数量。如果合并因子也很大的话,你可能会出现用光文件句柄的情况。

• 重用Document和Field实例
在lucene 2.3中,新增了一个叫setValue的方法,可以允许你改变字段的值。这样的好处是你可以在整个索引进程中复用一个Filed实例。这将极大的减少GC负担。

最好创建一个单一的Document实例,然后添加你想要的字段到文档中。同时复用添加到文档的Field实例,通用调用相应的SetValue方法改变相应的字段的值。然后重新将Document添加到索引中。

注意:你不能在一个文档中多个字段共用一个Field实例,在文档添加到索引之前,Field的值都不应该改变。也就是说如果你有3个字段,你必须创建3个Field实例,然后再之后的Document添加过程中复用它们。

• 在你的分析器Analyzer中使用一个单一的Token实例
在分析器中共享一个单一的token实例也将缓解GC的压力。

• 在Token中使用char[]接口来代替String接口来表示数据
在Lucene 2.3中,Token可以使用char数组来表示他的数据。这样可以避免构建字符串以及GC回收字符串的消耗。通过配合使用单一Token实例和使用char[]接口你可以避免创建新的对象。

• 设置autoCommit为false
在Lucene 2.3中对拥有存储字段和Term向量的文档进行了大量的优化,以节省大索引合并的时间。你可以将单一复用的IndexWriter实例的autoCommit设置为false来见证这些优化带来的好处。注意这样做将导致searcher在IndexWriter关闭之前不会看到任何索引的更新。如果你认为这个对你很重要,你可以继续将autoCommit设置为true,或者周期性的打开和关闭你的writer。

• 如果你要索引很多小文本字段,如果没有特别需求,建议你将这些小文本字段合并为一个大的contents字段,然后只索引contents。(当然你也可以继续存储那些字段)

• 加大mergeFactor合并因子,但不是越大越好
大的合并因子将延迟segment的合并时间,这样做可以提高索引速度,因为合并是索引很耗时的一个部分。但是,这样做将降低你的搜索速度。同时,你有可能会用光你的文件句柄如果你把合并因子设置的太大。值太大了设置可能降低索引速度,因为这意味着将同时合并更多的segment,将大大的增加硬盘的负担。

• 关闭所有你实际上没有使用的功能
如果你存储了字段,但是在查询时根本没有用到它们,那么别存储它们。同样Term向量也是如此。如果你索引很多的字段,关闭这些字段的不必要的特性将对索引速度提升产生很大的帮助。

• 使用一个更快的分析器
有时间分析文档将消耗很长的时间。举例来说,StandardAnalyzer就比较耗时,尤其在Lucene 2.3版本之前。你可以尝试使用一个更简单更快但是符合你需求的分析器。

• 加速文档的构建时间
在通常的情况下,文档的数据来源可能是外部(比如数据库,文件系统,蜘蛛从网站上的抓取等),这些通常都比较耗时,尽量优化获取它们的性能。

• 在你真的需要之前不要随意的优化optimize索引(只有在需要更快的搜索速度的时候)

• 在多线程中共享一个IndexWriter
最新的硬件都是适合高并发的(多核CPU,多通道内存构架等),所以使用多线程添加文档将会带来不小的性能提升。就算是一台很老的机器,并发添加文档都将更好的利用IO和CPU。多测试并发的线程数目,获得一个临界最优值。

• 将文档分组在不同的机器上索引然后再合并
如果你有大量的文本文档需要索引,你可以把你的文档分为若干组,在若干台机器上分别索引不同的组,然后利用writer.addIndexesNoOptimize来将它们合并到最终的一个索引文件中。

• 运行性能测试程序
如果以上的建议都没有发生效果。建议你运行下性能检测程序。找出你的程序中哪个部分比较耗时。这通常会给你想不到的惊喜。
本翻译属于原创,转载时请注明出处,英文原版请查看:

http://wiki.apache.org/jakarta-lucene/ImproveIndexingSpeed

from:http://www.cnblogs.com/gaoweipeng/archive/2009/10/16/1584503.html

mysql in 子查询 效率慢 优化(转)

现在的CMS系统、博客系统、BBS等都喜欢使用标签tag作交叉链接,因此我也尝鲜用了下。但用了后发现我想查询某个tag的文章列表时速度很慢,达到5秒之久!百思不解(后来终于解决),我的表结构是下面这样的,文章只有690篇。

文章表article(id,title,content)
标签表tag(tid,tag_name)
标签文章中间表article_tag(id,tag_id,article_id)
其中有个标签的tid是135,我帮查询标签tid是135的文章列表
用以下语句时发现速度好慢,我文章才690篇
select id,title from article where id in(
select article_id from article_tag where tag_id=135
)
其中这条速度很快:select article_id from article_tag where tag_id=135
查询结果是五篇文章,id为428,429,430,431,432
我用写死的方式用下面sql来查文章也很快
select id,title from article where id in(
428,429,430,431,432
)
我在SqlServer中好像不会这样慢,不知MySQL怎样写好点,也想不出慢在哪里。

后来我找到了解决方法:

select id,title from article where id in(
select article_id from (select article_id from article_tag where tag_id=135) as tbt
)

 

 

其它解决方法:(举例)

mysql> select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');

为了节省篇幅,省略了输出内容,下同。

67 rows in set (12.00 sec)

只有67行数据返回,却花了12秒,而系统中可能同时会有很多这样的查询,系统肯定扛不住。用desc看一下(注:explain也可)

mysql> desc select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');
+—-+——————--+——————+——--+—————--+——-+———+————+———+————————--+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+——————--+——————+——--+—————--+——-+———+————+———+————————--+
| 1 | PRIMARY | abc_number_prop | ALL | NULL | NULL | NULL | NULL | 2679838 | Using where |
| 2 | DEPENDENT SUBQUERY | abc_number_phone | eq_ref | phone,number_id | phone | 70 | const,func | 1 | Using where; Using index |
+—-+——————--+——————+——--+—————--+——-+———+————+———+————————--+
2 rows in set (0.00 sec)

从上面的信息可以看出,在执行此查询时会扫描两百多万行,难道是没有创建索引吗,看一下

mysql>show index from abc_number_phone;
+——————+————+————-+————--+—————--+———--+————-+———-+——--+——+————+———+—————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+——————+————+————-+————--+—————--+———--+————-+———-+——--+——+————+———+—————+
| abc_number_phone | 0 | PRIMARY | 1 | number_phone_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 0 | phone | 1 | phone | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 0 | phone | 2 | number_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | number_id | 1 | number_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | created_by | 1 | created_by | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | modified_by | 1 | modified_by | A | 36879 | NULL | NULL | YES | BTREE | | |
+——————+————+————-+————--+—————--+———--+————-+———-+——--+——+————+———+—————+
6 rows in set (0.06 sec)

mysql>show index from abc_number_prop;
+—————--+————+————-+————--+—————-+———--+————-+———-+——--+——+————+———+—————+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+—————--+————+————-+————--+—————-+———--+————-+———-+——--+——+————+———+—————+
| abc_number_prop | 0 | PRIMARY | 1 | number_prop_id | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | number_id | 1 | number_id | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | created_by | 1 | created_by | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | modified_by | 1 | modified_by | A | 311268 | NULL | NULL | YES | BTREE | | |
+—————--+————+————-+————--+—————-+———--+————-+———-+——--+——+————+———+—————+
4 rows in set (0.15 sec)

从上面的输出可以看出,这两张表在number_id字段上创建了索引的。

看看子查询本身有没有问题。

mysql> desc select number_id from abc_number_phone where phone = '82306839';
+—-+————-+——————+——+—————+——-+———+——-+——+————————--+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+——————+——+—————+——-+———+——-+——+————————--+
| 1 | SIMPLE | abc_number_phone | ref | phone | phone | 66 | const | 6 | Using where; Using index |
+—-+————-+——————+——+—————+——-+———+——-+——+————————--+
1 row in set (0.00 sec)

没有问题,只需要扫描几行数据,索引起作用了。查询出来看看

mysql> select number_id from abc_number_phone where phone = '82306839';
+———--+
| number_id |
+———--+
| 8585 |
| 10720 |
| 148644 |
| 151307 |
| 170691 |
| 221897 |
+———--+
6 rows in set (0.00 sec)

直接把子查询得到的数据放到上面的查询中

mysql> select * from abc_number_prop where number_id in (8585, 10720, 148644, 151307, 170691, 221897);

67 rows in set (0.03 sec)

速度也快,看来MySQL在处理子查询的时候是不够好。我在MySQL 5.1.42 和 MySQL 5.5.19 都进行了尝试,都有这个问题。

搜索了一下网络,发现很多人都遇到过这个问题:

参考资料1:使用连接(JOIN)来代替子查询(Sub-Queries) mysql优化系列记录
http://blog.csdn.net/hongsejiaozhu/article/details/1876181
参考资料2:网站开发日记(14)-MYSQL子查询和嵌套查询优化
http://dodomail.iteye.com/blog/250199

根据网上这些资料的建议,改用join来试试。

修改前:select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');

修改后:select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';

mysql> select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';

67 rows in set (0.00 sec)

效果不错,查询所用时间几乎为0。看一下MySQL是怎么执行这个查询的

mysql>desc select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';
+—-+————-+——-+——+—————--+———--+———+—————--+——+————————--+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+—-+————-+——-+——+—————--+———--+———+—————--+——+————————--+
| 1 | SIMPLE | b | ref | phone,number_id | phone | 66 | const | 6 | Using where; Using index |
| 1 | SIMPLE | a | ref | number_id | number_id | 4 | eap.b.number_id | 3 | |
+—-+————-+——-+——+—————--+———--+———+—————--+——+————————--+
2 rows in set (0.00 sec)

小结:当子查询速度慢时,可用JOIN来改写一下该查询来进行优化。

网上也有文章说,使用JOIN语句的查询不一定总比使用子查询的语句快。

参考资料3:改变了对Mysql子查询的看法
http://hi.baidu.com/yzx110/blog/item/e694f536f92075360b55a92b.html

 

 

mysql手册也提到过,具体的原文在mysql文档的这个章节:

I.3. Restrictions on Subqueries

13.2.8. Subquery Syntax

摘抄:

1)关于使用IN的子查询:

Subquery optimization for IN is not as effective as for the = operator or for IN(value_list) constructs.

A typical case for poor IN subquery performance is when the subquery returns a small number of rows but the outer query returns a large number of rows to be compared to the subquery result.

The problem is that, for a statement that uses an IN subquery, the optimizer rewrites it as a correlated subquery. Consider the following statement that uses an uncorrelated subquery:

The optimizer rewrites the statement to a correlated subquery:

If the inner and outer queries return M and N rows, respectively, the execution time becomes on the order of O(M×N), rather than O(M+N) as it would be for an uncorrelated subquery.

An implication is that an IN subquery can be much slower than a query written using an IN(value_list) construct that lists the same values that the subquery would return.

2)关于把子查询转换成join的:

The optimizer is more mature for joins than for subqueries, so in many cases a statement that uses a subquery can be executed more efficiently if you rewrite it as a join.

An exception occurs for the case where an IN subquery can be rewritten as a SELECT DISTINCT join. Example:

That statement can be rewritten as follows:

But in this case, the join requires an extra DISTINCT operation and is not more efficient than the subquery

from:http://www.cnblogs.com/xh831213/archive/2012/05/09/2491272.html

lucene、lucene.NET详细使用与优化详解

1 lucene简介
1.1 什么是lucene
Lucene是一个全文搜索框架,而不是应用产品。因此它并不像www.baidu.com 或者google Desktop那么拿来就能用,它只是提供了一种工具让你能实现这些产品。

1.2 lucene能做什么
要 回答这个问题,先要了解lucene的本质。实际上lucene的功能很单一,说到底,就是你给它若干个字符串,然后它为你提供一个全文搜索服务,告诉你 你要搜索的关键词出现在哪里。知道了这个本质,你就可以发挥想象做任何符合这个条件的事情了。你可以把站内新闻都索引了,做个资料库;你可以把一个数据库 表的若干个字段索引起来,那就不用再担心因为“%like%”而锁表了;你也可以写个自己的搜索引擎……

1.3 你该不该选择lucene
下面给出一些测试数据,如果你觉得可以接受,那么可以选择。
测试一:250万记录,300M左右文本,生成索引380M左右,800线程下平均处理时间300ms。
测试二:37000记录,索引数据库中的两个varchar字段,索引文件2.6M,800线程下平均处理时间1.5ms。

2 lucene的工作方式
lucene提供的服务实际包含两部分:一入一出。所谓入是写入,即将你提供的源(本质是字符串)写入索引或者将其从索引中删除;所谓出是读出,即向用户提供全文搜索服务,让用户可以通过关键词定位源。

2.1写入流程
源字符串首先经过analyzer处理,包括:分词,分成一个个单词;去除stopword(可选)。
将源中需要的信息加入Document的各个Field中,并把需要索引的Field索引起来,把需要存储的Field存储起来。
将索引写入存储器,存储器可以是内存或磁盘。

2.2读出流程
用户提供搜索关键词,经过analyzer处理。
对处理后的关键词搜索索引找出对应的Document。
用户根据需要从找到的Document中提取需要的Field。

3 一些需要知道的概念
lucene用到一些概念,了解它们的含义,有利于下面的讲解。

3.1 analyzer
Analyzer 是分析器,它的作用是把一个字符串按某种规则划分成一个个词语,并去除其中的无效词语,这里说的无效词语是指英文中的 “of”、 “the”,中文中的“的”、“地”等词语,这些词语在文章中大量出现,但是本身不包含什么关键信息,去掉有利于缩小索引文件、提高效率、提高命中率。
分词的规则千变万化,但目的只有一个:按语义划分。这点在英文中比较容易实现,因为英文本身就是以单词为单位的,已经用空格分开;而中文则必须以某种方法将连成一片的句子划分成一个个词语。具体划分方法下面再详细介绍,这里只需了解分析器的概念即可。

3.2 document
用户提供的源是一条条记录,它们可以是文本文件、字符串或者数据库表的一条记录等等。一条记录经过索引之后,就是以一个Document的形式存储在索引文件中的。用户进行搜索,也是以Document列表的形式返回。

3.3 field
一个Document可以包含多个信息域,例如一篇文章可以包含“标题”、“正文”、“最后修改时间”等信息域,这些信息域就是通过Field在Document中存储的。
Field有两个属性可选:存储和索引。通过存储属性你可以控制是否对这个Field进行存储;通过索引属性你可以控制是否对该Field进行索引。这看起来似乎有些废话,事实上对这两个属性的正确组合很重要,下面举例说明:
还 是以刚才的文章为例子,我们需要对标题和正文进行全文搜索,所以我们要把索引属性设置为真,同时我们希望能直接从搜索结果中提取文章标题,所以我们把标题 域的存储属性设置为真,但是由于正文域太大了,我们为了缩小索引文件大小,将正文域的存储属性设置为假,当需要时再直接读取文件;我们只是希望能从搜索解 果中提取最后修改时间,不需要对它进行搜索,所以我们把最后修改时间域的存储属性设置为真,索引属性设置为假。上面的三个域涵盖了两个属性的三种组合,还 有一种全为假的没有用到,事实上Field不允许你那么设置,因为既不存储又不索引的域是没有意义的。

3.4 term
term是搜索的最小单位,它表示文档的一个词语,term由两部分组成:它表示的词语和这个词语所出现的field。

3.5 tocken
tocken是term的一次出现,它包含trem文本和相应的起止偏移,以及一个类型字符串。一句话中可以出现多次相同的词语,它们都用同一个term表示,但是用不同的tocken,每个tocken标记该词语出现的地方。

3.6 segment
添加索引时并不是每个document都马上添加到同一个索引文件,它们首先被写入到不同的小文件,然后再合并成一个大索引文件,这里每个小文件都是一个segment。

4 lucene的结构
lucene包括core和sandbox两部分,其中core是lucene稳定的核心部分,sandbox包含了一些附加功能,例如highlighter、各种分析器。
Lucene core有七个包:analysis,document,index,queryParser,search,store,util。
4.1 analysis
Analysis包含一些内建的分析器,例如按空白字符分词的WhitespaceAnalyzer,添加了stopwrod过滤的StopAnalyzer,最常用的StandardAnalyzer。
4.2 document
Document包含文档的数据结构,例如Document类定义了存储文档的数据结构,Field类定义了Document的一个域。
4.3 index
Index 包含了索引的读写类,例如对索引文件的segment进行写、合并、优化的IndexWriter类和对索引进行读取和删除操作的 IndexReader类,这里要注意的是不要被IndexReader这个名字误导,以为它是索引文件的读取类,实际上删除索引也是由它完成, IndexWriter只关心如何将索引写入一个个segment,并将它们合并优化;IndexReader则关注索引文件中各个文档的组织形式。
4.4 queryParser
QueryParser 包含了解析查询语句的类,lucene的查询语句和sql语句有点类似,有各种保留字,按照一定的语法可以组成各种查询。 Lucene有很多种Query类,它们都继承自Query,执行各种特殊的查询,QueryParser的作用就是解析查询语句,按顺序调用各种 Query类查找出结果。
4.5 search
Search包含了从索引中搜索结果的各种类,例如刚才说的各种Query类,包括TermQuery、BooleanQuery等就在这个包里。
4.6 store
Store包含了索引的存储类,例如Directory定义了索引文件的存储结构,FSDirectory为存储在文件中的索引,RAMDirectory为存储在内存中的索引,MmapDirectory为使用内存映射的索引。
4.7 util
Util包含一些公共工具类,例如时间和字符串之间的转换工具。
5 如何建索引
5.1 最简单的能完成索引的代码片断

IndexWriter writer = new IndexWriter(“/data/index/”, new StandardAnalyzer(), true);
Document doc = new Document();
doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));
doc.add(new Field("content", "lucene works well", Field.Store.YES, Field.Index.TOKENIZED));
writer.addDocument(doc);
writer.optimize();
writer.close();

下面我们分析一下这段代码。
首先我们创建了一个writer,并指定存放索引的目录为“/data/index”,使用的分析器为StandardAnalyzer,第三个参数说明如果已经有索引文件在索引目录下,我们将覆盖它们。
然后我们新建一个document。
我们向document添加一个field,名字是“title”,内容是“lucene introduction”,对它进行存储并索引。
再添加一个名字是“content”的field,内容是“lucene works well”,也是存储并索引。
然后我们将这个文档添加到索引中,如果有多个文档,可以重复上面的操作,创建document并添加。
添加完所有document,我们对索引进行优化,优化主要是将多个segment合并到一个,有利于提高索引速度。
随后将writer关闭,这点很重要。

对,创建索引就这么简单!
当然你可能修改上面的代码获得更具个性化的服务。

5.2 将索引直接写在内存
你需要首先创建一个RAMDirectory,并将其传给writer,代码如下:

Directory dir = new RAMDirectory();
IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);
Document doc = new Document();
doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));
doc.add(new Field("content", "lucene works well", Field.Store.YES, Field.Index.TOKENIZED));
writer.addDocument(doc);
writer.optimize();
writer.close();

5.3 索引文本文件
如果你想把纯文本文件索引起来,而不想自己将它们读入字符串创建field,你可以用下面的代码创建field:

Field field = new Field("content", new FileReader(file));

这里的file就是该文本文件。该构造函数实际上是读去文件内容,并对其进行索引,但不存储。

6 如何维护索引
索引的维护操作都是由IndexReader类提供。

6.1 如何删除索引
lucene提供了两种从索引中删除document的方法,一种是

void deleteDocument(int docNum)

这种方法是根据document在索引中的编号来删除,每个document加进索引后都会有个唯一编号,所以根据编号删除是一种精确删除,但是这个编号是索引的内部结构,一般我们不会知道某个文件的编号到底是几,所以用处不大。另一种是

void deleteDocuments(Term term)

这种方法实际上是首先根据参数term执行一个搜索操作,然后把搜索到的结果批量删除了。我们可以通过这个方法提供一个严格的查询条件,达到删除指定document的目的。
下面给出一个例子:

Directory dir = FSDirectory.getDirectory(PATH, false);
IndexReader reader = IndexReader.open(dir);
Term term = new Term(field, key);
reader.deleteDocuments(term);
reader.close();

6.2 如何更新索引
lucene并没有提供专门的索引更新方法,我们需要先将相应的document删除,然后再将新的document加入索引。例如:

Directory dir = FSDirectory.getDirectory(PATH, false);
IndexReader reader = IndexReader.open(dir);
Term term = new Term(“title”, “lucene introduction”);
reader.deleteDocuments(term);
reader.close();

IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(), true);
Document doc = new Document();
doc.add(new Field("title", "lucene introduction", Field.Store.YES, Field.Index.TOKENIZED));
doc.add(new Field("content", "lucene is funny", Field.Store.YES, Field.Index.TOKENIZED));
writer.addDocument(doc);
writer.optimize();
writer.close();
7 如何搜索
lucene 的搜索相当强大,它提供了很多辅助查询类,每个类都继承自Query类,各自完成一种特殊的查询,你可以像搭积木一样将它们任意组合使用,完成一些复杂操 作;另外lucene还提供了Sort类对结果进行排序,提供了Filter类对查询条件进行限制。你或许会不自觉地拿它跟SQL语句进行比 较:“lucene能执行and、or、order by、where、like ‘%xx%’操作吗?”回答是:“当然没问题!”

7.1 各种各样的Query
下面我们看看lucene到底允许我们进行哪些查询操作:

7.1.1 TermQuery
首先介绍最基本的查询,如果你想执行一个这样的查询:“在content域中包含‘lucene’的document”,那么你可以用TermQuery:

Term t = new Term("content", " lucene";
Query query = new TermQuery(t);

7.1.2 BooleanQuery
如果你想这么查询:“在content域中包含java或perl的document”,那么你可以建立两个TermQuery并把它们用BooleanQuery连接起来:

TermQuery termQuery1 = new TermQuery(new Term("content", "java");
TermQuery termQuery 2 = new TermQuery(new Term("content", "perl");
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(termQuery 1, BooleanClause.Occur.SHOULD);
booleanQuery.add(termQuery 2, BooleanClause.Occur.SHOULD);

7.1.3 WildcardQuery
如果你想对某单词进行通配符查询,你可以用WildcardQuery,通配符包括’?’匹配一个任意字符和’*’匹配零个或多个任意字符,例如你搜索’use*’,你可能找到’useful’或者’useless’:

Query query = new WildcardQuery(new Term("content", "use*");

7.1.4 PhraseQuery
你可能对中日关系比较感兴趣,想查找‘中’和‘日’挨得比较近(5个字的距离内)的文章,超过这个距离的不予考虑,你可以:

PhraseQuery query = new PhraseQuery();
query.setSlop(5);
query.add(new Term("content ", “中”));
query.add(new Term(“content”, “日”));

那么它可能搜到“中日合作……”、“中方和日方……”,但是搜不到“中国某高层领导说日本欠扁”。

7.1.5 PrefixQuery
如果你想搜以‘中’开头的词语,你可以用PrefixQuery:

PrefixQuery query = new PrefixQuery(new Term("content ", "中");

7.1.6 FuzzyQuery
FuzzyQuery用来搜索相似的term,使用Levenshtein算法。假设你想搜索跟‘wuzza’相似的词语,你可以:

Query query = new FuzzyQuery(new Term("content", "wuzza");

你可能得到‘fuzzy’和‘wuzzy’。

7.1.7 RangeQuery
另一个常用的Query是RangeQuery,你也许想搜索时间域从20060101到20060130之间的document,你可以用RangeQuery:

RangeQuery query = new RangeQuery(new Term(“time”, “20060101”), new Term(“time”, “20060130”), true);

最后的true表示用闭合区间。

7.2 QueryParser
看 了这么多Query,你可能会问:“不会让我自己组合各种Query吧,太麻烦了!”当然不会,lucene提供了一种类似于SQL语句的查询语句,我们 姑且叫它lucene语句,通过它,你可以把各种查询一句话搞定,lucene会自动把它们查分成小块交给相应Query执行。下面我们对应每种 Query演示一下:
TermQuery可以用“field:key”方式,例如“content:lucene”。
BooleanQuery中‘与’用‘+’,‘或’用‘ ’,例如“content:java contenterl”。
WildcardQuery仍然用‘?’和‘*’,例如“content:use*”。
PhraseQuery用‘~’,例如“content:"中日"~5”。
PrefixQuery用‘*’,例如“中*”。
FuzzyQuery用‘~’,例如“content: wuzza ~”。
RangeQuery用‘[]’或‘{}’,前者表示闭区间,后者表示开区间,例如“time:[20060101 TO 20060130]”,注意TO区分大小写。
你 可以任意组合query string,完成复杂操作,例如“标题或正文包括lucene,并且时间在20060101到20060130之间的文章” 可以表示为:“+ (title:lucene content:lucene) +time:[20060101 TO 20060130]”。代码如下:

Directory dir = FSDirectory.getDirectory(PATH, false);
IndexSearcher is = new IndexSearcher(dir);
QueryParser parser = new QueryParser("content", new StandardAnalyzer());
Query query = parser.parse("+(title:lucene content:lucene) +time:[20060101 TO 20060130]";
Hits hits = is.search(query);
for (int i = 0; i < hits.length(); i++)
{
Document doc
= hits.doc(i);
System.out.println(doc.get("title");
}
is.close();

首先我们创建一个在指定文件目录上的IndexSearcher。
然后创建一个使用StandardAnalyzer作为分析器的QueryParser,它默认搜索的域是content。
接着我们用QueryParser来parse查询字串,生成一个Query。
然后利用这个Query去查找结果,结果以Hits的形式返回。
这个Hits对象包含一个列表,我们挨个把它的内容显示出来。

7.3 Filter
filter 的作用就是限制只查询索引的某个子集,它的作用有点像SQL语句里的 where,但又有区别,它不是正规查询的一部分,只是对数据源进行预处理,然后交给查询语句。注意它执行的是预处理,而不是对查询结果进行过滤,所以使 用filter的代价是很大的,它可能会使一次查询耗时提高一百倍。
最常用的filter是RangeFilter和QueryFilter。RangeFilter是设定只搜索指定范围内的索引;QueryFilter是在上次查询的结果中搜索。
Filter的使用非常简单,你只需创建一个filter实例,然后把它传给searcher。继续上面的例子,查询“时间在20060101到20060130之间的文章”除了将限制写在query string中,你还可以写在RangeFilter中:

Directory dir = FSDirectory.getDirectory(PATH, false);
IndexSearcher is = new
IndexSearcher(dir);
QueryParser parser = new
QueryParser("content", new StandardAnalyzer());
Query query = parser.parse("title:lucene
content:lucene";
RangeFilter filter = new
RangeFilter("time", "20060101", "20060230", true, true);
Hits hits = is.search(query,
filter);
for (int i i < hits.length(); i++)
{
Document doc
= hits.doc(i);
System.out.println(doc.get("title");
}
is.close();

7.4 Sort
有时你想要一个排好序的结果集,就像SQL语句的“order by”,lucene能做到:通过Sort。
Sort sort
Sort(“time”); //相当于SQL的“order by time”
Sort sort = new
Sort(“time”, true); // 相当于SQL的“order by time desc”
下面是一个完整的例子:

Directory dir = FSDirectory.getDirectory(PATH, false);
IndexSearcher is = new
IndexSearcher(dir);
QueryParser parser = new
QueryParser("content", new StandardAnalyzer());
Query query = parser.parse("title:lucene
content:lucene";
RangeFilter filter = new
RangeFilter("time", "20060101", "20060230", true, true);
Sort sort = new
Sort(“time”);
Hits hits = is.search(query,
filter, sort);
for (int i
= 0; i < hits.length(); i++)
{
Document doc
= hits.doc(i);
System.out.println(doc.get("title");
}
is.close();

8 分析器
在 前面的概念介绍中我们已经知道了分析器的作用,就是把句子按照语义切分成一个个词语。英文切分已经有了很成熟的分析器: StandardAnalyzer,很多情况下StandardAnalyzer是个不错的选择。甚至你会发现StandardAnalyzer也能对中 文进行分词。
但是我们的焦点是中文分词,StandardAnalyzer能支持中文分词吗?实践证明是可以的,但是效果并不好,搜索“如果”会 把“牛奶不如果汁好喝 ”也搜索出来,而且索引文件很大。那么我们手头上还有什么分析器可以使用呢?core里面没有,我们可以在sandbox里面找到两个: ChineseAnalyzer和CJKAnalyzer。但是它们同样都有分词不准的问题。相比之下用StandardAnalyzer 和 ChineseAnalyzer建立索引时间差不多,索引文件大小也差不多,CJKAnalyzer表现会差些,索引文件大且耗时比较长。
要解决 问题,首先分析一下这三个分析器的分词方式。StandardAnalyzer和ChineseAnalyzer都是把句子按单个字切分,也就是说 “牛奶不如果汁好喝”会被它们切分成“牛 奶 不 如 果 汁 好 喝”;而CJKAnalyzer则会切分成“牛奶 奶不 不如 如果 果汁 汁好好喝”。这也就解释了为什么搜索“果汁”都能匹配这个句子。
以上分词的缺点至少有两个:匹配不准确和索引文件大。我们的目标是将上面的句子分 解成“牛奶 不如 果汁好喝”。这里的关键就是语义识别,我们如何识别“ 牛奶”是一个词而“奶不”不是词语?我们很自然会想到基于词库的分词法,也就是我们先得到一个词库,里面列举了大部分词语,我们把句子按某种方式切分,当 得到的词语与词库中的项匹配时,我们就认为这种切分是正确的。这样切词的过程就转变成匹配的过程,匹配的方式最简单的有正向最大匹配和逆向最大匹配两种,说白了就是一个从句子开头向后进行匹配,一个从句子末尾向前进行匹配。基于词库的分词词库非常重要,词库的容量直接影响搜索结果,在相同词库的前提下,据说逆向最大匹配优于正向最大匹配。
当 然还有别的分词方法,这本身就是一个学科,我这里也没有深入研究。回到具体应用,我们的目标是能找到成熟的、现成的分词工具,避免重新发明车轮。经过网上 搜索,用的比较多的是中科院的ICTCLAS和一个不开放源码但是免费的JE-Analysis。ICTCLAS有个问题是它是一个动态链接库, java调用需要本地方法调用,不方便也有安全隐患,而且口碑也确实不大好。JE-Analysis效果还不错,当然也会有分词不准的地方,相比比较方便 放心。= new = 0;

9 性能优化
一 直到这里,我们还是在讨论怎么样使lucene跑起来,完成指定任务。利用前面说的也确实能完成大部分功能。但是测试表明lucene的性能并不是很好, 在大数据量大并发的条件下甚至会有半分钟返回的情况。另外大数据量的数据初始化建立索引也是一个十分耗时的过程。那么如何提高lucene的性能呢?下面 从优化创建索引性能和优化搜索性能两方面介绍。

9.1 优化创建索引性能
这方面的优化途径比较有限,IndexWriter提供 了一些接口可以控制建立索引的操作,另外我们可以先将索引写入RAMDirectory,再批量写入FSDirectory,不管怎样,目的都是尽量少的 文件IO,因为创建索引的最大瓶颈在于磁盘IO。另外选择一个较好的分析器也能提高一些性能。

9.1.1 通过设置IndexWriter的参数优化索引建立
setMaxBufferedDocs(int maxBufferedDocs)
控制写入一个新的segment前内存中保存的document的数目,设置较大的数目可以加快建索引速度,默认为10。
setMaxMergeDocs(int maxMergeDocs)
控制一个segment中可以保存的最大document数目,值较小有利于追加索引的速度,默认Integer.MAX_VALUE,无需修改。
setMergeFactor(int mergeFactor)
控制多个segment合并的频率,值较大时建立索引速度较快,默认是10,可以在建立索引时设置为100。

9.1.2 通过RAMDirectory缓写提高性能
我们可以先把索引写入RAMDirectory,达到一定数量时再批量写进FSDirectory,减少磁盘IO次数。

FSDirectory fsDir = FSDirectory.getDirectory("/data/index", true);
RAMDirectory ramDir = new
RAMDirectory();
IndexWriter fsWriter = new
IndexWriter(fsDir, new StandardAnalyzer(), true);
IndexWriter ramWriter = new
IndexWriter(ramDir, new StandardAnalyzer(), true);
while (there are documents to index)
{
… create Document …
ramWriter.addDocument(doc);
if (condition for flushing memory to disk has been met)
{
fsWriter.addIndexes(new Directory[] { ramDir });
ramWriter.close();
ramWriter
= new IndexWriter(ramDir, new StandardAnalyzer(), true);
}
}

9.1.3 选择较好的分析器
这 个优化主要是对磁盘空间的优化,可以将索引文件减小将近一半,相同测试数据下由600M减少到380M。但是对时间并没有什么帮助,甚至会需要更长时间, 因为较好的分析器需要匹配词库,会消耗更多cpu,测试数据用StandardAnalyzer耗时133分钟;用MMAnalyzer耗时150分钟。

9.2 优化搜索性能
虽然建立索引的操作非常耗时,但是那毕竟只在最初创建时才需要,平时只是少量的维护操作,更何况这些可以放到一个后台进程处理,并不影响用户搜索。我们创建索引的目的就是给用户搜索,所以搜索的性能才是我们最关心的。下面就来探讨一下如何提高搜索性能。

9.2.1 将索引放入内存
这是一个最直观的想法,因为内存比磁盘快很多。Lucene提供了RAMDirectory可以在内存中容纳索引:

Directory fsDir = FSDirectory.getDirectory(“/data/index/”, false);
Directory ramDir = new
RAMDirectory(fsDir);
Searcher searcher = new
IndexSearcher(ramDir);

但是实践证明RAMDirectory和FSDirectory速度差不多,当数据量很小时两者都非常快,当数据量较大时(索引文件400M)RAMDirectory甚至比FSDirectory还要慢一点,这确实让人出乎意料。
而且lucene的搜索非常耗内存,即使将400M的索引文件载入内存,在运行一段时间后都会out of memory,所以个人认为载入内存的作用并不大。

9.2.2 优化时间范围限制
既然载入内存并不能提高效率,一定有其它瓶颈,经过测试发现最大的瓶颈居然是时间范围限制,那么我们可以怎样使时间范围限制的代价最小呢?
当需要搜索指定时间范围内的结果时,可以:
1、 用RangeQuery,设置范围,但是RangeQuery的实现实际上是将时间范围内的时间点展开,组成一个个BooleanClause加入到 BooleanQuery中查询,因此时间范围不可能设置太大,经测试,范围超过一个月就会抛 BooleanQuery.TooManyClauses,可以通过设置 BooleanQuery.setMaxClauseCount(int maxClauseCount)扩大,但是扩大也是有限的,并且随着 maxClauseCount扩大,占用内存也扩大
2、用 RangeFilter代替RangeQuery,经测试速度不会比RangeQuery慢,但是仍然有性能瓶颈,查询的90%以上时间耗费在 RangeFilter,研究其源码发现RangeFilter实际上是首先遍历所有索引,生成一个BitSet,标记每个document,在时间范围 内的标记为true,不在的标记为false,然后将结果传递给Searcher查找,这是十分耗时的。
3、进一步提高性能,这个又有两个思路:
a、 缓存Filter结果。既然RangeFilter的执行是在搜索之前,那么它的输入都是一定的,就是IndexReader,而 IndexReader是由Directory决定的,所以可以认为RangeFilter的结果是由范围的上下限决定的,也就是由具体的 RangeFilter对象决定,所以我们只要以RangeFilter对象为键,将filter结果BitSet缓存起来即可。 lucene API已经提供了一个CachingWrapperFilter类封装了Filter及其结果,所以具体实施起来我们可以 cache CachingWrapperFilter对象,需要注意的是,不要被CachingWrapperFilter的名字及其说明误导, CachingWrapperFilter看起来是有缓存功能,但的缓存是针对同一个filter的,也就是在你用同一个filter过滤不同 IndexReader时,它可以帮你缓存不同IndexReader的结果,而我们的需求恰恰相反,我们是用不同filter过滤同一个 IndexReader,所以只能把它作为一个封装类。
b、降低时间精度。研究Filter的工作原理可以看出,它每次工作都是遍历整个索引的,所以时间粒度越大,对比越快,搜索时间越短,在不影响功能的情况下,时间精度越低越好,有时甚至牺牲一点精度也值得,当然最好的情况是根本不作时间限制。
下面针对上面的两个思路演示一下优化结果(都采用800线程随机关键词随即时间范围):
第一组,时间精度为秒:
方式 直接用RangeFilter 使用cache 不用filter
平均每个线程耗时 10s 1s 300ms

第二组,时间精度为天
方式 直接用RangeFilter 使用cache 不用filter
平均每个线程耗时 900ms 360ms 300ms

由以上数据可以得出结论:
1、 尽量降低时间精度,将精度由秒换成天带来的性能提高甚至比使用cache还好,最好不使用filter。
2、 在不能降低时间精度的情况下,使用cache能带了10倍左右的性能提高。

9.2.3 使用更好的分析器
这个跟创建索引优化道理差不多,索引文件小了搜索自然会加快。当然这个提高也是有限的。较好的分析器相对于最差的分析器对性能的提升在20%以下。

10 一些经验

10.1关键词区分大小写
or AND TO等关键词是区分大小写的,lucene只认大写的,小写的当做普通单词。

10.2 读写互斥性
同一时刻只能有一个对索引的写操作,在写的同时可以进行搜索

10.3 文件锁
在写索引的过程中强行退出将在tmp目录留下一个lock文件,使以后的写操作无法进行,可以将其手工删除

10.4 时间格式
lucene只支持一种时间格式yyMMddHHmmss,所以你传一个yy-MM-dd HH:mm:ss的时间给lucene它是不会当作时间来处理的

10.5 设置boost
有些时候在搜索时某个字段的权重需要大一些,例如你可能认为标题中出现关键词的文章比正文中出现关键词的文章更有价值,你可以把标题的boost设置的更大,那么搜索结果会优先显示标题中出现关键词的文章(没有使用排序的前题下)。使用方法:
Field. setBoost(float boost);默认值是1.0,也就是说要增加权重的需要设置得比1大。

from:http://blog.itpub.net/12639172/viewspace-626546/
1 287 288 289 414