﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-读万卷书不如行千里路，经验的积累又不是一蹴而就的，不但需要知识的沉积，还需要长久经验的总结升华-文章分类-数据库</title><link>http://www.blogjava.net/liujw/category/9480.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 05:49:59 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 05:49:59 GMT</pubDate><ttl>60</ttl><item><title>针对事务型数据库设计小结</title><link>http://www.blogjava.net/liujw/articles/39144.html</link><dc:creator>刘军伟</dc:creator><author>刘军伟</author><pubDate>Tue, 04 Apr 2006 05:43:00 GMT</pubDate><guid>http://www.blogjava.net/liujw/articles/39144.html</guid><wfw:comment>http://www.blogjava.net/liujw/comments/39144.html</wfw:comment><comments>http://www.blogjava.net/liujw/articles/39144.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/liujw/comments/commentRss/39144.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/liujw/services/trackbacks/39144.html</trackback:ping><description><![CDATA[根据个人的总结：希望大家多的补充：<br /><b><h4>以下是针对事务型数据库：</h4></b> <br />1.是否使用联合主键？个人倾向于少采用联合主键。因为这样会降低索引的效率，联合主键一般都要用到至少一个业务字段，往往是字符串型的，而且理论上多字段的索引比单字段的索引要慢些。看上去似乎也不那么清爽。 <br />在实际的设计中，我尽量避免使用联合主键，有些时候“不得不”使用联合主键。 <br /><br />2.PK采用无意义的字段（逻辑主键）还是有意义的字段（业务主键）？个人倾向于“逻辑主键”，理由是这样设计出的数据库模型结构清晰、关系脉络清楚，往往更符合“第三范式”（虽然不是故意的，呵呵）。而且更容易避开“联合主键”，而且可以使用索引效率高的字段类型，比如int、long、number。缺点是用无意义的字段建立表间的关系，使跨表查询增多，效率下降。（矛盾无处不在，前面刚说完可以提高效率，这里马上又降低效率）。“业务主键”可以提升查询编码的简洁度和效率。 <br />个人使用实际状况，总体来说“逻辑主键”比“业务主键”执行效率低，但不会低到无法满足需求。采用“逻辑主键”比采用“业务主键”更利于数据库模型的结构、关系清晰，也更便于维护。 <br />对于分析型数据库，如数据仓库，千万不要这样做。 <br /><br />3.不要使用多对多关系？个人倾向于少使用多对多关系。这个问题其实不是数据库设计的问题了，在数据库设计中，多对多关系也仅仅存在于逻辑模型（E-R）阶段，物理模型不在有多对多关系，实际数据库中也不会有“多对多”关系。这是使用ORM时的问题，比如使用Hibernate，多对多关系有时会使编码看起来灵活一些，代价是效率的明显降低。 <br />个人实际使用中，设计时基本不考虑多对多关系，但编码时总会有小组成员使用一些多对多关系，自己建立多对多的ORM，使自己编码方便些，用在数据量小的地方，影响不大。大数据量，则“禁止使用”。 <br /><br />4.为每个表增加一个state字段？我习惯在设计时给每个表设一个state字段，取值0或1，默认值为1，具体业务意义或操作上的意义可以自定义。可以作为一个状态控制字段，如查询、更新、删除条件，单据是否有效（业务单据对应的表会有业务意义上的“有/无效”或“状态”字段，这种情况下，我还是会再加一个state字段），甚至仅仅是控制一条数据是否“有效”（有效的意义你自己定）。在数据迁移（如转入分析用的数据库）时也可能会发挥作用。 <br /><br />5.为每个表设置一些备用字段？没办法，我总是设计不出“完美”的数据表，给每个表加几个备用字段（我一般用字符串型，随你）可以应付“不时之需”，尤其是需要长期维护的、业务可能有临时性变动的系统。 <br /><br />6.尽量不要在一个表中存入其关联表的字段？建议不存！这样做确实可以提高查询效率，但在一个有很多表，并且关联表多的情况下，很难保持数据的一致性！数据库结构也比较糟糕。而且不存，也不会使效率十分低下。 <br /><br />7.不要去直接修改数据库？个人认为这点很重要，当需要修改时，应该先去修改模型，然后同步物理数据库，尤其是团队开发，否则要多做更多的事情来搞定，也可能会引入更多的错误<br />8:每个表加 创建记录时间，创建记录人，修改记录时间，修改记录人 四个字段的 <br /><img src ="http://www.blogjava.net/liujw/aggbug/39144.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/liujw/" target="_blank">刘军伟</a> 2006-04-04 13:43 <a href="http://www.blogjava.net/liujw/articles/39144.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一个关于优化SQL的文章</title><link>http://www.blogjava.net/liujw/articles/39143.html</link><dc:creator>刘军伟</dc:creator><author>刘军伟</author><pubDate>Tue, 04 Apr 2006 05:42:00 GMT</pubDate><guid>http://www.blogjava.net/liujw/articles/39143.html</guid><wfw:comment>http://www.blogjava.net/liujw/comments/39143.html</wfw:comment><comments>http://www.blogjava.net/liujw/articles/39143.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/liujw/comments/commentRss/39143.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/liujw/services/trackbacks/39143.html</trackback:ping><description><![CDATA[大家都在讨论关于数据库优化方面的东东，刚好参与开发了一个数据仓库方面的项目，以下的一点东西算是数据库优化方面的学习+实战的一些心得体会了，拿出来大家共享。欢迎批评指正阿！ <br /><br />SQL语句： <br />是对数据库(数据)进行操作的惟一途径； <br />消耗了70%~90%的数据库资源；独立于程序设计逻辑，相对于对程序源代码的优化，对SQL语句的优化在时间成本和风险上的代价都很低； <br />可以有不同的写法；易学，难精通。 <br /><br />SQL优化： <br />固定的SQL书写习惯，相同的查询尽量保持相同，存储过程的效率较高。 <br />应该编写与其格式一致的语句，包括字母的大小写、标点符号、换行的位置等都要一致 <br /><br />ORACLE优化器： <br />在任何可能的时候都会对表达式进行评估，并且把特定的语法结构转换成等价的结构，这么做的原因是 <br />要么结果表达式能够比源表达式具有更快的速度 <br />要么源表达式只是结果表达式的一个等价语义结构 <br />不同的SQL结构有时具有同样的操作（例如：= ANY (subquery) and IN (subquery)），ORACLE会把他们映射到一个单一的语义结构。 <br /><br />1 常量优化： <br />常量的计算是在语句被优化时一次性完成，而不是在每次执行时。下面是检索月薪大于2000的的表达式： <br />sal &gt; 24000/12 <br />sal &gt; 2000 <br />sal*12 &gt; 24000 <br />如果SQL语句包括第一种情况，优化器会简单地把它转变成第二种。 <br />优化器不会简化跨越比较符的表达式，例如第三条语句，鉴于此，应尽量写用常量跟字段比较检索的表达式，而不要将字段置于表达式当中。否则没有办法优化，比如如果sal上有索引，第一和第二就可以使用，第三就难以使用。 <br /><br />2 操作符优化： <br />优化器把使用LIKE操作符和一个没有通配符的表达式组成的检索表达式转换为一个“=”操作符表达式。 <br />例如：优化器会把表达式ename LIKE 'SMITH'转换为ename = 'SMITH' <br />优化器只能转换涉及到可变长数据类型的表达式，前一个例子中，如果ENAME字段的类型是CHAR(10)， 那么优化器将不做任何转换。 <br />一般来讲LIKE比较难以优化。 <br /><br />其中： <br />~~ IN 操作符优化： <br />优化器把使用IN比较符的检索表达式替换为等价的使用“=”和“OR”操作符的检索表达式。 <br />例如，优化器会把表达式ename IN ('SMITH','KING','JONES')替换为 <br />ename = 'SMITH' OR ename = 'KING' OR ename = 'JONES‘ <br /><br />~~ ANY和SOME 操作符优化: <br />优化器将跟随值列表的ANY和SOME检索条件用等价的同等操作符和“OR”组成的表达式替换。 <br />例如，优化器将如下所示的第一条语句用第二条语句替换： <br />sal &gt; ANY (:first_sal, :second_sal) <br />sal &gt; :first_sal OR sal &gt; :second_sal <br />优化器将跟随子查询的ANY和SOME检索条件转换成由“EXISTS”和一个相应的子查询组成的检索表达式。 <br />例如，优化器将如下所示的第一条语句用第二条语句替换： <br />x &gt; ANY (SELECT sal FROM emp WHERE job = 'ANALYST') <br />EXISTS (SELECT sal FROM emp WHERE job = 'ANALYST' AND x &gt; sal) <br /><br />~~ ALL操作符优化: <br />优化器将跟随值列表的ALL操作符用等价的“=”和“AND”组成的表达式替换。例如： <br />sal &gt; ALL (:first_sal, :second_sal)表达式会被替换为： <br />sal &gt; :first_sal AND sal &gt; :second_sal <br />对于跟随子查询的ALL表达式，优化器用ANY和另外一个合适的比较符组成的表达式替换。例如 <br />x &gt; ALL (SELECT sal FROM emp WHERE deptno = 10) 替换为： <br />NOT (x &lt;= ANY (SELECT sal FROM emp WHERE deptno = 10)) <br />接下来优化器会把第二个表达式适用ANY表达式的转换规则转换为下面的表达式： <br />NOT EXISTS (SELECT sal FROM emp WHERE deptno = 10 AND x &lt;= sal) <br /><br />~~ BETWEEN 操作符优化: <br />优化器总是用“&gt;=”和“&lt;=”比较符来等价的代替BETWEEN操作符。 <br />例如：优化器会把表达式sal BETWEEN 2000 AND 3000用sal &gt;= 2000 AND sal &lt;= 3000来代替。 <br /><br />~~ NOT 操作符优化: <br />优化器总是试图简化检索条件以消除“NOT”逻辑操作符的影响，这将涉及到“NOT”操作符的消除以及代以相应的比较运算符。 <br />例如，优化器将下面的第一条语句用第二条语句代替： <br />NOT deptno = (SELECT deptno FROM emp WHERE ename = 'TAYLOR') <br />deptno &lt;&gt; (SELECT deptno FROM emp WHERE ename = 'TAYLOR') <br />通常情况下一个含有NOT操作符的语句有很多不同的写法，优化器的转换原则是使“NOT”操作符后边的子句尽可能的简单，即使可能会使结果表达式包含了更多的“NOT”操作符。 <br />例如，优化器将如下所示的第一条语句用第二条语句代替： <br />NOT (sal &lt; 1000 OR comm IS NULL) <br />NOT sal &lt; 1000 AND comm IS NOT NULL sal &gt;= 1000 AND comm IS NOT NULL <br /><br />如何编写高效的SQL: <br />当然要考虑sql常量的优化和操作符的优化啦，另外，还需要： <br /><br />1 合理的索引设计： <br />例：表record有620000行，试看在不同的索引下，下面几个SQL的运行情况： <br />语句A <br />SELECT count(*) FROM record <br />WHERE date &gt;'19991201' and date &lt; '19991214‘ and amount &gt;2000 <br /><br />语句B <br />SELECT count(*) FROM record <br />WHERE date &gt;'19990901' and place IN ('BJ','SH') <br /><br />语句C <br />SELECT date,sum(amount) FROM record <br />group by date <br />1 在date上建有一个非聚集索引 <br />A：(25秒) <br />B：(27秒) <br />C：(55秒) <br />分析： <br />date上有大量的重复值，在非聚集索引下，数据在物理上随机存放在数据页上，在范围查找时，必须执行一次表扫描才能找到这一范围内的全部行。 <br />2 在date上的一个聚集索引 <br />A：（14秒） <br />B：（14秒） <br />C：（28秒） <br />分析： <br />在聚集索引下，数据在物理上按顺序在数据页上，重复值也排列在一起，因而在范围查找时，可以先找到这个范围的起末点，且只在这个范围内扫描数据页，避免了大范围扫描，提高了查询速度。 <br />3 在place，date，amount上的组合索引 <br />A：（26秒） <br />C：（27秒） <br />B：（&lt; 1秒） <br />分析： <br />这是一个不很合理的组合索引，因为它的前导列是place，第一和第二条SQL没有引用place，因此也没有利用上索引；第三个SQL使用了place，且引用的所有列都包含在组合索引中，形成了索引覆盖，所以它的速度是非常快的。 <br />4 在date，place，amount上的组合索引 <br />A： (&lt; 1秒) <br />B：（&lt; 1秒） <br />C：（11秒） <br />分析： <br />这是一个合理的组合索引。它将date作为前导列，使每个SQL都可以利用索引，并且在第一和第三个SQL中形成了索引覆盖，因而性能达到了最优。 <br /><br />总结1 <br />缺省情况下建立的索引是非聚集索引，但有时它并不是最佳的；合理的索引设计要建立在对各种查询的分析和预测上。一般来说： <br />有大量重复值、且经常有范围查询（between, &gt;,&lt; ，&gt;=,&lt; =）和order by、group by发生的列，考虑建立聚集索引； <br />经 常同时存取多列，且每列都含有重复值可考虑建立组合索引；在条件表达式中经常用到的不同值较多的列上建立检索，在不同值少的列上不要建立索引。比如在雇员 表的“性别”列上只有“男”与“女”两个不同值，因此就无必要建立索引。如果建立索引不但不会提高查询效率，反而会严重降低更新速度。 <br />组合索引要尽量使关键查询形成索引覆盖，其前导列一定是使用最频繁的列。 <br /><br />2 避免使用不兼容的数据类型： <br />例如float和INt、char和varchar、bINary和varbINary是不兼容的。数据类型的不兼容可能使优化器无法执行一些本来可以进行的优化操作。例如: <br />SELECT name FROM employee WHERE salary ＞ 60000 <br />在这条语句中,如salary字段是money型的,则优化器很难对其进行优化,因为60000是个整型数。我们应当在编程时将整型转化成为钱币型,而不要等到运行时转化。 <br /><br />3 IS NULL 与IS NOT NULL： <br />不 能用null作索引，任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下，只要这些列中有一列含有null，该列就会从索引中排 除。也就是说如果某列存在空值，即使对该列建索引也不会提高性能。任何在WHERE子句中使用is null或is not null的语句优化器是不允 许使用索引的。 <br /><br />4 IN和EXISTS： <br />EXISTS要远比IN的效率高。里面关系到full table scan和range scan。几乎将所有的IN操作符子查询改写为使用EXISTS的子查询。 <br />例子： <br />语句1 <br />SELECT dname, deptno FROM dept <br />WHERE deptno NOT IN <br />(SELECT deptno FROM emp); <br />语句2 <br />SELECT dname, deptno FROM dept <br />WHERE NOT EXISTS <br />(SELECT deptno FROM emp <br />WHERE dept.deptno = emp.deptno); <br />明显的，2要比1的执行性能好很多 <br />因为1中对emp进行了full table scan,这是很浪费时间的操作。而且1中没有用到emp的INdex， <br />因为没有WHERE子句。而2中的语句对emp进行的是range scan。 <br /><br />5 IN、OR子句常会使用工作表，使索引失效： <br />如果不产生大量重复值，可以考虑把子句拆开。拆开的子句中应该包含索引。 <br /><br />6 避免或简化排序： <br />应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时，优化器就避免了排序的步骤。以下是一些影响因素： <br />索引中不包括一个或几个待排序的列； <br />group by或order by子句中列的次序与索引的次序不一样； <br />排序的列来自不同的表。 <br />为了避免不必要的排序，就要正确地增建索引，合理地合并数据库表（尽管有时可能影响表的规范化，但相对于效率的提高是值得的）。如果排序不可避免，那么应当试图简化它，如缩小排序的列的范围等。 <br /><br />7 消除对大型表行数据的顺序存取： <br />在 嵌套查询中，对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略，一个嵌套3层的查询，如果每层都查询1000行，那么这个查询就要查询 10亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如，两个表：学生表（学号、姓名、年龄??）和选课表（学号、课程号、成绩）。如果两个 表要做连接，就要在“学号”这个连接字段上建立索引。 <br />还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引，但某些形式的WHERE子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作： <br />SELECT ＊ FROM orders WHERE (customer_num=104 AND order_num&gt;1001) OR order_num=1008 <br />虽然在customer_num和order_num上建有索引，但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合，所以应该改为如下语句： <br />SELECT ＊ FROM orders WHERE customer_num=104 AND order_num&gt;1001 <br />UNION <br />SELECT ＊ FROM orders WHERE order_num=1008 <br />这样就能利用索引路径处理查询。 <br /><br />8 避免相关子查询： <br />一个列的标签同时在主查询和WHERE子句中的查询中出现，那么很可能当主查询中的列值改变之后，子查询必须重新查询一次。查询嵌套层次越多，效率越低，因此应当尽量避免子查询。如果子查询不可避免，那么要在子查询中过滤掉尽可能多的行。 <br /><br />9 避免困难的正规表达式： <br />MATCHES和LIKE关键字支持通配符匹配，技术上叫正规表达式。但这种匹配特别耗费时间。例如：SELECT ＊ FROM customer WHERE zipcode LIKE “98_ _ _” <br />即使在zipcode字段上建立了索引，在这种情况下也还是采用顺序扫描的方式。如果把语句改为SELECT ＊ FROM customer WHERE zipcode &gt;“98000”，在执行查询时就会利用索引来查询，显然会大大提高速度。 <br />另外，还要避免非开始的子串。例如语句：SELECT ＊ FROM customer WHERE zipcode[2，3] &gt;“80”，在WHERE子句中采用了非开始子串，因而这个语句也不会使用索引。 <br /><br />10 不充份的连接条件： <br />例：表card有7896行，在card_no上有一个非聚集索引，表account有191122行，在account_no上有一个非聚集索引，试看在不同的表连接条件下，两个SQL的执行情况： <br />SELECT sum(a.amount) FROM account a,card b WHERE a.card_no = b.card_no <br />（20秒） <br />将SQL改为： <br />SELECT sum(a.amount) FROM account a,card b WHERE a.card_no = b.card_no and a.account_no=b.account_no <br />（&lt; 1秒） <br />分析： <br />在第一个连接条件下，最佳查询方案是将account作外层表，card作内层表，利用card上的索引，其I/O次数可由以下公式估算为： <br />外层表account上的22541页+（外层表account的191122行*内层表card上对应外层表第一行所要查找的3页）=595907次I/O <br />在第二个连接条件下，最佳查询方案是将card作外层表，account作内层表，利用account上的索引，其I/O次数可由以下公式估算为： <br />外层表card上的1944页+（外层表card的7896行*内层表account上对应外层表每一行所要查找的4页）= 33528次I/O <br />可见，只有充份的连接条件，真正的最佳方案才会被执行。 <br />多表操作在被实际执行前，查询优化器会根据连接条件，列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表；内外表的选择可由公式：外层表中的匹配行数*内层表中每一次查找的次数确定，乘积最小为最佳方案。 <br />不可优化的WHERE子句 <br />例1 <br />下列SQL条件语句中的列都建有恰当的索引，但执行速度却非常慢： <br />SELECT * FROM record WHERE substrINg(card_no,1,4)='5378' <br />(13秒) <br />SELECT * FROM record WHERE amount/30&lt; 1000 <br />（11秒） <br />SELECT * FROM record WHERE convert(char(10),date,112)='19991201' <br />（10秒） <br />分析： <br />WHERE子句中对列的任何操作结果都是在SQL运行时逐列计算得到的，因此它不得不进行表搜索，而没有使用该列上面的索引；如果这些结果在查询编译时就能得到，那么就可以被SQL优化器优化，使用索引，避免表搜索，因此将SQL重写成下面这样： <br />SELECT * FROM record WHERE card_no like '5378%' <br />（&lt; 1秒） <br />SELECT * FROM record WHERE amount&lt; 1000*30 <br />（&lt; 1秒） <br />SELECT * FROM record WHERE date= '1999/12/01' <br />（&lt; 1秒） <br /><br />11 存储过程中，采用临时表优化查询： <br />例 <br />1．从parven表中按vendor_num的次序读数据： <br />SELECT part_num，vendor_num，price FROM parven ORDER BY vendor_num <br />INTO temp pv_by_vn <br />这个语句顺序读parven（50页），写一个临时表（50页），并排序。假定排序的开销为200页，总共是300页。 <br />2．把临时表和vendor表连接，把结果输出到一个临时表，并按part_num排序： <br />SELECT pv_by_vn，＊ vendor.vendor_num FROM pv_by_vn，vendor <br />WHERE pv_by_vn.vendor_num=vendor.vendor_num <br />ORDER BY pv_by_vn.part_num <br />INTO TMP pvvn_by_pn <br />DROP TABLE pv_by_vn <br />这 个查询读取pv_by_vn(50页)，它通过索引存取vendor表1.5万次，但由于按vendor_num次序排列，实际上只是通过索引顺序地读 vendor表（40＋2=42页），输出的表每页约95行，共160页。写并存取这些页引发5＊160=800次的读写，索引共读写892页。 <br />3．把输出和part连接得到最后的结果： <br />SELECT pvvn_by_pn.＊，part.part_desc FROM pvvn_by_pn，part <br />WHERE pvvn_by_pn.part_num=part.part_num <br />DROP TABLE pvvn_by_pn <br />这样，查询顺序地读pvvn_by_pn(160页)，通过索引读part表1.5万次，由于建有索引，所以实际上进行1772次磁盘读写，优化比例为30∶1。 <br /><br />好了，搞定。 <br />其实sql的优化，各种数据库之间都是互通的。<img src ="http://www.blogjava.net/liujw/aggbug/39143.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/liujw/" target="_blank">刘军伟</a> 2006-04-04 13:42 <a href="http://www.blogjava.net/liujw/articles/39143.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>