﻿<?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-Titan专栏-文章分类-索引调优</title><link>http://www.blogjava.net/Titan/category/5381.html</link><description>用文字来整理生命</description><language>zh-cn</language><lastBuildDate>Tue, 27 Feb 2007 12:35:23 GMT</lastBuildDate><pubDate>Tue, 27 Feb 2007 12:35:23 GMT</pubDate><ttl>60</ttl><item><title>B+树的研究</title><link>http://www.blogjava.net/Titan/articles/30387.html</link><dc:creator>Titan</dc:creator><author>Titan</author><pubDate>Sun, 12 Feb 2006 15:12:00 GMT</pubDate><guid>http://www.blogjava.net/Titan/articles/30387.html</guid><wfw:comment>http://www.blogjava.net/Titan/comments/30387.html</wfw:comment><comments>http://www.blogjava.net/Titan/articles/30387.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Titan/comments/commentRss/30387.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Titan/services/trackbacks/30387.html</trackback:ping><description><![CDATA[<TABLE height="100%" cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD vAlign=top align=middle>
<TABLE cellSpacing=0 cellPadding=0 width="92%" border=0>
<TBODY>
<TR>
<TD class=text align=left>
<P><B></B>1、B+树索引的总体结构<BR>①B<SUP>+</SUP>树索引是一个多级索引，但是其结构不同于多级顺序索引；<BR>②B<SUP>+</SUP>树索引采用平衡树结构，即每个叶结点到根的路径长度都相同；<BR>③每个非叶结点有<IMG height=14 src="http://www.nuist.edu.cn/courses/jsj/GD_jsj_013b/image/title/08/02.gif" width=29>到n个子女，n对特定的树是固定的；<BR>④B<SUP>+</SUP>树的所有结点结构都相同，它最多包含n-1个搜索码值K<FONT size=-5>1</FONT>、K<FONT size=-5>2</FONT>、…、K<FONT size=-5>n-1</FONT>，以及n个指针P<FONT size=-5>1</FONT>、P<FONT size=-5>2</FONT>、…、P<FONT size=-5>n</FONT>，每个结点中的搜索码值按次序存放，即如果i&lt;j，那么Ki&lt;Kj，如<SPAN class=part>图8-3-1</SPAN>所示。<BR></P></TD></TR>
<TR>
<TD class=text align=left>
<DIV align=center><SPAN class=part>图8-3-1：B<SUP>+</SUP>树的结点结构 </SPAN><BR><IMG height=34 src="http://www.nuist.edu.cn/courses/jsj/GD_jsj_013b/image/chapter/08/8-3-1.gif" width=420> </DIV></TD></TR>
<TR>
<TD class=text align=left><SPAN class=part>2、B<SUP>+</SUP>树索引的叶结点</SPAN><BR>①指针Pi(i=1,2,…,n-1)指向具有搜索码值Ki的一个文件记录或一个指针（存储）桶，桶中的每个指针指向具有搜索码值Ki的一个文件记录。指针桶只在文件不按搜索码顺序物理存储时才使用。指针Pn具有特殊的作用；<BR>②每个叶结点最多可有n-1个搜索码值，最少也要有<IMG height=20 src="http://www.nuist.edu.cn/courses/jsj/GD_jsj_013b/image/title/08/04.gif" width=60 align=absMiddle>个搜索码值。各个叶结点中搜索码值的范围互不相交。要使B+树索引成为稠密索引，数据文件中的各搜索码值都必须出现在某个叶结点中且只能出现一次；<BR>③由于各叶结点按照所含的搜索码值有一个线性顺序，所以就可以利用各个叶结点的指针Pn将叶结点按搜索码顺序链接在一起。这种排序能够高效地对文件进行顺序处理，而B<SUP>+</SUP>树索引的其他结构能够高效地对文件进行随机处理，如<SPAN class=part>图8-3-2</SPAN>所示。<BR></TD></TR>
<TR>
<TD class=text align=left>
<DIV align=center><SPAN class=part>图8-3-2：B+树索引的叶结点结构示例</SPAN> <BR><IMG height=160 src="http://www.nuist.edu.cn/courses/jsj/GD_jsj_013b/image/chapter/08/8-3-2.gif" width=420> </DIV></TD></TR>
<TR>
<TD class=text align=left><SPAN class=part>3、B<SUP>+</SUP>树索引的非叶结点</SPAN><BR>①B<SUP>+</SUP>树索引的非叶结点形成叶结点上的一个多级（稀疏）索引；<BR>②非叶结点的结构和叶结点的结构相同，即含有能够存储n-1个搜索码值和n个指针的存储单元的数据结构。只不过非叶结点中的所有指针都指向树中的结点；<BR>③如果一个非叶结点有m个指针，则<IMG height=14 src="http://www.nuist.edu.cn/courses/jsj/GD_jsj_013b/image/title/08/02.gif" width=29>≤m≤n。若m&lt;n，则非叶结点中指针Pm之后的所有空闲空间作为预留空间，与叶结点的区别在于结点的最后一个指针Pm和Pn的位置与指向不同，如<SPAN class=part>图8-3-3</SPAN>所示；<BR></TD></TR>
<TR>
<TD class=text align=left>
<DIV align=center><SPAN class=part>图8-3-3：B+树索引的非叶结点结构 </SPAN><BR><IMG height=80 src="http://www.nuist.edu.cn/courses/jsj/GD_jsj_013b/image/chapter/08/8-3-3.gif" width=420> </DIV></TD></TR>
<TR>
<TD class=text align=left>④在一个含有m个指针的非叶结点中，指针P<FONT size=-5>i</FONT>(i=2,…,m-1)指向一棵子树，该子树的所有结点的搜索码值大于等于K<FONT size=-5>i-1</FONT>而小于K<FONT size=-5>i</FONT>。指针Pm指向子树中所含搜索码值大于等于K<FONT size=-5>m-1</FONT>的那一部分，而指针P<FONT size=-5>1</FONT>指向子树中所含搜索码值小于K<FONT size=-5>1</FONT>的那一部分，如<SPAN class=part>图8-3-4</SPAN>所示。<BR></TD></TR>
<TR>
<TD class=text align=left>
<DIV class=part align=center>图8-3-4：B<SUP>+</SUP>树索引的非叶结点中指针Pi的指向<BR><IMG height=106 src="http://www.nuist.edu.cn/courses/jsj/GD_jsj_013b/image/chapter/08/8-3-4.gif" width=420> </DIV></TD></TR>
<TR>
<TD class=text align=left><SPAN class=part>4、B<SUP>+</SUP>树索引的根结点</SPAN><BR>①根结点的结构也与叶结点相同；<BR>②根结点包含的指针数可以小于<IMG height=14 src="http://www.nuist.edu.cn/courses/jsj/GD_jsj_013b/image/title/08/02.gif" width=29>。但是，除非整棵树只有一个结点，否则根结点必须至少包含两个指针。<SPAN class=part>图8-3-5</SPAN>给出一个B<SUP>+</SUP>树结构的示意图。<BR></TD></TR>
<TR>
<TD class=text align=left>
<DIV align=center><SPAN class=part>图8-3-5：account关系的B<SUP>+</SUP>树索引结构 </SPAN><BR><IMG height=134 src="http://www.nuist.edu.cn/courses/jsj/GD_jsj_013b/image/chapter/08/8-3-5.gif" width=420><BR></DIV></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/Titan/aggbug/30387.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Titan/" target="_blank">Titan</a> 2006-02-12 23:12 <a href="http://www.blogjava.net/Titan/articles/30387.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于索引的SQL语句优化之降龙十八掌</title><link>http://www.blogjava.net/Titan/articles/26467.html</link><dc:creator>Titan</dc:creator><author>Titan</author><pubDate>Tue, 03 Jan 2006 12:26:00 GMT</pubDate><guid>http://www.blogjava.net/Titan/articles/26467.html</guid><wfw:comment>http://www.blogjava.net/Titan/comments/26467.html</wfw:comment><comments>http://www.blogjava.net/Titan/articles/26467.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Titan/comments/commentRss/26467.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Titan/services/trackbacks/26467.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 1&nbsp;&nbsp;&nbsp;&nbsp; 前言... 22&nbsp;&nbsp;&nbsp;&nbsp; 总纲... 23&nbsp;&nbsp;&nbsp;&nbsp; 降龙十八掌... 3第一掌 避免对列的操作... 3第二掌 避免不必要的类型转换... 4第三掌 增加查询的范围限制... 4第四掌 尽量去掉"IN"、"OR" 4第五掌 尽量去掉 "&l...&nbsp;&nbsp;<a href='http://www.blogjava.net/Titan/articles/26467.html'>阅读全文</a><img src ="http://www.blogjava.net/Titan/aggbug/26467.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Titan/" target="_blank">Titan</a> 2006-01-03 20:26 <a href="http://www.blogjava.net/Titan/articles/26467.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转]B-tree algorithms</title><link>http://www.blogjava.net/Titan/articles/26466.html</link><dc:creator>Titan</dc:creator><author>Titan</author><pubDate>Tue, 03 Jan 2006 12:24:00 GMT</pubDate><guid>http://www.blogjava.net/Titan/articles/26466.html</guid><wfw:comment>http://www.blogjava.net/Titan/comments/26466.html</wfw:comment><comments>http://www.blogjava.net/Titan/articles/26466.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/Titan/comments/commentRss/26466.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Titan/services/trackbacks/26466.html</trackback:ping><description><![CDATA[<H2>B-tree algorithms</H2>
<P>A B-tree is a data structure that maintains an ordered set of data and allows efficient operations to find, delete, insert, and browse the data. In this discussion, each piece of data stored in a B-tree will be called a "key", because each key is unique and can occur in the B-tree in only one location. 
<P>A B-tree consists of "node" records containing the keys, and pointers that link the nodes of the B-tree together. 
<P>Every B-tree is of some "order n", meaning nodes contain from n to 2n keys, and nodes are thereby always at least half full of keys. Keys are kept in sorted order within each node. A corresponding list of pointers are effectively interspersed between keys to indicate where to search for a key if it isn't in the current node. A node containing k keys always also contains k+1 pointers. 
<P>For example, here is a portion of a B-tree with order 2 (nodes have at least 2 keys and 3 pointers). Nodes are delimited with [square brackets]. The keys are city names, and are kept sorted in each node. On either side of every key are pointers linking the key to subsequent nodes: <PRE>           Start here
           | 
           v 
           [ Chicago Hoboken ]  
            |       |       | 
+-----------+       |       +------------+
|                   |                    |
v                   v                    v
[ Aptos Boston ]    [ Denver Detroit ]   [ San-Jose Seattle ]
 |     |      |      |      |       |     |        |       |
 v     v      v      v      v       v     v        v       v
                     X

</PRE>To find the key "Dallas", we begin searching at the top "root" node. "Dallas" is not in the node but sorts between "Chicago" and "Hoboken", so we follow the middle pointer to the next node. Again, "Dallas" is not in the node but sorts before "Denver", so we follow that node's first pointer down to the next node (marked with an "X"). Eventually, we will either locate the key, or encounter a "leaf" node at the bottom level of the B-tree with no pointers to any lower nodes and without the key we want, indicating the key is nowhere in the B-tree. 
<P>Below is another fragment of an order 1 B-tree (nodes have at least 1 key and 2 pointers). Searching for the key "Chicago" begins at "Marin", follows the first pointer to "Aptos" (since Chicago sorts before Marin), then follows that node's second pointer down to the next level (since Chicago sorts after Aptos), as marked with an "X". <PRE>          | 
          v 
      [ Marin ]  
       |     |
    +--+     +---+
    |            |
    v            v
[ Aptos ]   [ Seattle ]
 |     |     |       |
 v     v     v       v 
       X

</PRE>Searching a B-tree for a key always begins at the root node and follows pointers from node to node until either the key is located or the search fails because a leaf node is reached and there are no more pointers to follow. 
<P>B-trees grow when new keys are inserted. Since the root node initially begins with just one key, the root node is a special exception and the only node allowed to have less than n keys in an order n B-tree. 
<P>Here is an order 2 B-tree with integer keys. Except for the special root node, order 2 requires every node to have from 2 to 4 keys and 3 to 5 pointers. Empty slots are marked with <TT>"."</TT>, showing where future keys have not yet been stored in the nodes: <PRE>                       [ 57 . . .]
                        |  |
        +---------------+  +---------------------+
        |                                        |
        v                                        v
        [ 14 40 . .]                             [ 72 84 . .]
         |  |  |                                  |  |  |
+--------+  |  +----------+            +----------+  |  +-----------+
|           |             |            |             |              |
v           v             v            v             v              v
[01 12 . .] [15 16 17 .]  [47 56 . .]  [58 60 61 .]  [74 75 76 78]  [85 86 99 .]

</PRE>To insert the key "59", we first simply search for that key. If 59 is found, the key is already in the tree and the insertion is superfluous. Otherwise, we must end up at a leaf node at the bottom level of the tree where 59 would be stored. In the above case, the leaf node contains 58, 60, 61, and room for a fourth key, so 59 is simply inserted in the leaf node in sorted order: <PRE>[58 59 60 61]

</PRE>Now we'll insert the key "77". The initial search leads us to the leaf node where 77 would be inserted, but the node is already full with 4 keys: 74, 75, 76, and 78. Adding another key would violate the rule that order 2 B-trees can't have more than 4 keys. Because of this "overflow" condition, the leaf node is split into two leaf nodes. The leftmost 2 keys are put in the left node, the rightmost 2 keys are put in the right node, and the middle key is "promoted" by inserting it into the parent node above the leaf. Here, inserting 77 causes the 74-75-76-78 node to be split into two nodes, and 76 is moved up to the parent node that contained 72 and 84: <PRE>Before inserting 77           After inserting 77

[ 72 84 . .]                    [ 72 76 84 .]
 |  |  |                         |  |  |  |
-+  |  +-                      --+  |  |  +--
    |                               |  |
    |                          +----+  +------+
    |                          |              |
    v                          v              v
    [74 75 76 78]              [74 75 . .]    [77 78 . .]

</PRE>In this case, the parent node contained only 2 keys (72 and 84), leaving room for 76 to be promoted and inserted. But if the parent node was also already full with 4 keys, then it too would have to split. Indeed, splitting may propagate all the way up to the root node. When the root splits, the B-tree grows in height by one level, and a new root with a single promoted key is formed. (A situation when an order n root node sometimes has fewer than n keys, just like the situation described earlier when the root node stores the very first key placed in the B-tree.) 
<P>B-trees shrink when keys are deleted. To delete a key, first perform the usual search operation to locate the node containing the key. (If the key isn't found, it isn't in the tree and can't be deleted.) 
<P>If the found key is not in a leaf, move it to a leaf by swapping the key with the logical "next" key. In a B-tree, the "next" key is always the first key in the leftmost leaf of the right subtree. 
<P>For example, in this B-tree we want to delete "37", which is not in a leaf. <TT>"xx"</TT> indicates key values that don't matter: <PRE>[ xx 37 xx xx ]
       |
       |
       +-&gt;[ xx xx xx xx ]
           |
           |
           +-&gt;[ xx xx xx xx ]
               |
               |
               +-&gt;[41 43 . .]

</PRE>We follow the pointer immediately to the right of 37 to find 37's right subtree, then follow the leftmost pointers in each subnode until we reach a leaf. The first key in the leaf is "41", the logical "next" key after 37 in the list of all keys in the tree. By swapping 37 and 41, we can move 37 to a leaf node to set up a deletion without violating the key order or pointer order of the overall B-tree. 
<P>Once the key we want is in a leaf, we can delete it. If at least n keys remain in the node, we're done, otherwise it is an "underflow", since every node (except the root) must have at least n keys. 
<P>If a node underflows, we may be able to "redistribute" keys by borrowing some from a neighboring node. For example, in the order 3 B-tree below, the key 67 is being deleted, which causes a node to underflow since it only has keys 66 and 88 left. So keys from the neighbor on the left are "shifted through" the parent node and redistributed so both leaf nodes end up with 4 keys: <PRE>   Before deleting 67                         After deleting 67

     [ xx 55 xx ]                               [ xx 33 xx ]
         |  |                                       |  |
+--------+  +--------+                     +--------+  +------+
|                    |                     |                  |
v                    v                     v                  v
[22 24 26 28 33 44]  [66 67 88 . . .]      [22 24 26 28 . .]  [44 55 66 88 . .]

</PRE>But if the underflow node and the neighbor node have less than 2n keys to redistribute, the two nodes will have to be combined. For example, here key 52 is being deleted from the B-tree below, causing an underflow, and the neighbor node can't afford to give up any keys for redistribution. So one node is discarded, and the parent key moves down with the other keys to fill up a single node: <PRE>Before deleting 52          After deleting 52

  [ 35 45 55 . ]              [ 35 55 . . ]
   |  |  |  |                  |  |  |
  -+  |  |  +-                -+  |  +-
      |  |                        |
+-----+  +---+                    |
|            |                    |
v            v                    v
[40 42 . .]  [50 52 . .]          [40 42 45 50]

</PRE>In the above case, moving the key 45 out of the parent node left two keys (35 and 55) remaining. But if the parent node only had n keys to begin with, then the parent node also would underflow when the parent key was moved down to combine with the leaf key. Indeed, underflow and the combining of nodes may propagate all the way up to the root node. When the root underflows, the B-tree shrinks in height by one level, and the nodes under the old root combine to form a new root. 
<P>The payoff of the B-tree insert and delete rules are that B-trees are always "balanced". Searching an unbalanced tree may require traversing an arbitrary and unpredictable number of nodes and pointers. <PRE>An unbalanced tree of 4 nodes              A balanced tree of 4 nodes

[ x x ]                                             [ x x ]
    |                                                | | |
    [ x x ]                                   +------+ | +------+
        |                                     |        |        |
        [ x x ]                               [ x x ]  [ x x ]  [ x x ]
            |
            [ x x ]

</PRE>Searching a balanced tree means that all leaves are at the same depth. There is no runaway pointer overhead. Indeed, even very large B-trees can guarantee only a small number of nodes must be retrieved to find a given key. For example, a B-tree of 10,000,000 keys with 50 keys per node never needs to retrieve more than 4 nodes to find any key. 
<HR>
<A href="http://www.semaphorecorp.com/btp/btp.html">B-TREE-P</A> copyright © 1987-2000 by <A href="http://www.semaphorecorp.com/home/contact.html">Semaphore Corporation</A><img src ="http://www.blogjava.net/Titan/aggbug/26466.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Titan/" target="_blank">Titan</a> 2006-01-03 20:24 <a href="http://www.blogjava.net/Titan/articles/26466.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ORACLE索引与高性能SQL介绍</title><link>http://www.blogjava.net/Titan/articles/24433.html</link><dc:creator>Titan</dc:creator><author>Titan</author><pubDate>Sat, 17 Dec 2005 16:09:00 GMT</pubDate><guid>http://www.blogjava.net/Titan/articles/24433.html</guid><wfw:comment>http://www.blogjava.net/Titan/comments/24433.html</wfw:comment><comments>http://www.blogjava.net/Titan/articles/24433.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Titan/comments/commentRss/24433.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Titan/services/trackbacks/24433.html</trackback:ping><description><![CDATA[<BR>　　<B>什么是索引</B><BR>　　<BR>　　索引是建立在表的一列或多个列上的辅助对象，目的是加快访问表中的数据；<BR>　　<BR>　　Oracle存储索引的数据结构是B*树，位图索引也是如此，只不过是叶子节点不同B*数索引；<BR>　　<BR>　　索引由根节点、分支节点和叶子节点组成，上级索引块包含下级索引块的索引数据，叶节点包含索引数据和确定行实际位置的rowid。<BR>　　<BR>　　使用索引的目的<BR>　　加快查询速度<BR>　　减少I/O操作<BR>　　消除磁盘排序<BR>　　<BR>　　何时使用索引<BR>　　查询返回的记录数<BR>　　排序表&lt;40%<BR>　　非排序表 &lt;7%<BR>　　表的碎片较多（频繁增加、删除）<BR>　　<BR>　　索引的种类<BR>　　非唯一索引（最常用）<BR>　　唯一索引<BR>　　位图索引<BR>　　局部有前缀分区索引<BR>　　局部无前缀分区索引<BR>　　全局有前缀分区索引<BR>　　散列分区索引<BR>　　基于函数的索引<BR>　　<BR>　　<B>管理索引的准则<BR>　　<BR>　　在表中插入数据后创建索引<BR>　　</B><BR>　　。在用SQL*Loader或import工具插入或装载数据后，建立索引比较有效；<BR>　　<BR>　　<B>索引正确的表和列</B><BR>　　<BR>　　。经常检索排序大表中40%或非排序表7%的行，建议建索引；<BR>　　。为了改善多表关联，索引列用于联结；<BR>　　。列中的值相对比较唯一；<BR>　　。取值范围（大：B*树索引，小：位图索引）；<BR>　　。Date型列一般适合基于函数的索引；<BR>　　。列中有许多空值，不适合建立索引<BR>　　<BR>　　<B>为性能而安排索引列</B><BR>　　<BR>　　。经常一起使用多个字段检索记录，组合索引比单索引更有效；<BR>　　。把最常用的列放在最前面，例：dx_groupid_serv_id(groupid,serv_id)，在where条件中使用groupid或groupid,serv_id，查询将使用索引，若仅用到serv_id字段，则索引无效；<BR>　　。合并/拆分不必要的索引。<BR>　　<BR>　　<B>限制每个表索引的数量</B><BR>　　<BR>　　。一个表可以有几百个索引（你会这样做吗？），但是对于频繁插入和更新表，索引越多系统CPU，I/O负担就越重；<BR>　　。建议每张表不超过5个索引。<BR>　　<BR>　　<B>删除不再需要的索引</B><BR>　　<BR>　　。索引无效，集中表现在该使用基于函数的索引或位图索引，而使用了B*树索引；<BR>　　。应用中的查询不使用索引；<BR>　　。重建索引之前必须先删除索引，若用alter index … rebuild重建索引，则不必删除索引。<BR>　　<BR>　　<B>索引数据块空间使用</B><BR>　　<BR>　　。创建索引时指定表空间，特别是在建立主键时，应明确指定表空间；<BR>　　。合理设定pctfress，注意：不能给索引指定pctused；<BR>　　。估计索引的大小和合理地设置存储参数，默认为表空间大小，或initial与next设置成一样大。<BR>　　<BR>　　<B>考虑并行创建索引</B><BR>　　<BR>　　。对大表可以采用并行创建索引，在并行创建索引时，存储参数被每个查询服务器进程分别使用，例如：initial为1M，并行度为8，则创建索引期间至少要消耗8M空间；<BR>　　<BR>　　<B>考虑用nologging创建索引</B><BR>　　<BR>　　。对大表创建索引可以使用nologging来减少重做日志；<BR>　　。节省重做日志文件的空间；<BR>　　。缩短创建索引的时间；<BR>　　。改善了并行创建大索引时的性能。<BR>　　<BR>　　<B>怎样建立最佳索引</B><BR>　　<BR>　　明确地创建索引<BR>　　create index index_name on table_name(field_name)<BR>　　tablespace tablespace_name<BR>　　pctfree 5<BR>　　initrans 2<BR>　　maxtrans 255<BR>　　storage<BR>　　(<BR>　　minextents 1<BR>　　maxextents 16382<BR>　　pctincrease 0<BR>　　);<BR>　　<BR>　　<B>创建基于函数的索引</B><BR>　　<BR>　　。常用与UPPER、LOWER、TO_CHAR(date)等函数分类上，例：<BR>　　create index idx_func on emp (UPPER(ename)) tablespace tablespace_name;<BR>　　<BR>　　<B>创建位图索引</B><BR>　　<BR>　　。对基数较小，且基数相对稳定的列建立索引时，首先应该考虑位图索引，例：<BR>　　create bitmap index idx_bitm on class (classno) tablespace tablespace_name;<BR>　　<BR>　　<B>明确地创建唯一索引</B><BR>　　<BR>　　。可以用create unique index语句来创建唯一索引，例：<BR>　　create unique index dept_unique_idx on dept(dept_no) tablespace idx_1;<BR>　　<BR>　　<B>创建与约束相关的索引</B><BR>　　<BR>　　。可以用using index字句，为与unique和primary key约束相关的索引，例如：<BR>　　alter table table_name<BR>　　add constraint PK_primary_keyname primary key (field_name)<BR>　　using index tablespace tablespace_name；<BR>　　<BR>　　<B>如何创建局部分区索引</B><BR>　　<BR>　　。基础表必须是分区表；<BR>　　。分区数量与基础表相同；<BR>　　。每个索引分区的子分区数量与相应的基础表分区相同；<BR>　　。基础表的子分区中的行的索引项，被存储在该索引的相应的子分区中,例如:<BR>　　Create Index TG_CDR04_SERV_ID_IDX On TG_CDR04(SERV_ID)<BR>　　Pctfree 5<BR>　　Tablespace TBS_AK01_IDX<BR>　　Storage (<BR>　　MaxExtents 32768<BR>　　PctIncrease 0<BR>　　FreeLists 1<BR>　　FreeList Groups 1<BR>　　)<BR>　　local<BR>　　/<BR>　　<BR>　　<B>如何创建范围分区的全局索引</B><BR>　　<BR>　　。基础表可以是全局表和分区表。<BR>　　create index idx_start_date on tg_cdr01(start_date)<BR>　　global partition by range(start_date)<BR>　　(partition p01_idx vlaues less than (‘0106’)<BR>　　partition p01_idx vlaues less than (‘0111’)<BR>　　…<BR>　　partition p01_idx vlaues less than (‘0401’ ))<BR>　　/<BR>　　<BR>　　<B>重建现存的索引</B><BR>　　<BR>　　重建现存的索引的当前时刻不会影响查询；<BR>　　<BR>　　重建索引可以删除额外的数据块；<BR>　　<BR>　　提高索引查询效率；<BR>　　alter index idx_name rebuild nologging;<BR>　　<BR>　　对于分区索引：<BR>　　alter index idx_name rebuild partition partiton_name nologging;<BR>　　<BR>　　<B>要删除索引的原因</B><BR>　　<BR>　　。不再需要的索引；<BR>　　。索引没有针对其相关的表所发布的查询提供所期望的性能改善；<BR>　　。应用没有用该索引来查询数据；<BR>　　。该索引无效，必须在重建之前删除该索引；<BR>　　。该索引已经变的太碎了，必须在重建之前删除该索引；<BR>　　。语句：drop index idx_name;drop index idx_name drop partition partition_name;<BR>　　<BR>　　<B>建立索引的代价</B><BR>　　<BR>　　基础表维护时，系统要同时维护索引，不合理的索引将严重影响系统资源，主要表现在CPU和I/O上；<BR>　　<BR>　　插入、更新、删除数据产生大量db file sequential read锁等待；<BR>　　<BR>　　<B>SQL优化器简介<BR>　　<BR>　　基于规则的优化器</B><BR>　　<BR>　　。总是使用索引<BR>　　。总是从驱动表开始（from子句最右边的表）<BR>　　。只有在不可避免的情况下，才使用全表扫描<BR>　　。任何索引都可以<BR>　　<BR>　　<B>基于成本的优化器</B><BR>　　<BR>　　。需要表、索引的统计资料<BR>　　Analyze table customer compute statistics;<BR>　　Analyze table customer estimate statistics sample 5000 rows;<BR>　　。表中设置并行度、表分区<BR>　　<BR>　　优化器模式<BR>　　<BR>　　rule模式<BR>　　<BR>　　。总忽略CBO和统计信息而基于规则<BR>　　choose模式<BR>　　<BR>　　。Oracle根据情况选择rule or first_rows or all_rows<BR>　　first_rows 模式<BR>　　<BR>　　。基于成本，以最快的速度返回记录，会造成总体查询速度的下降或消耗更多的资源，倾向索引扫描，适合OLTP系统<BR>　　all_rows模式<BR>　　<BR>　　。基于成本，确保总体查询时间最短，倾向并行全表扫描<BR>　　<BR>　　例如：<BR>　　Select last_name from customer order by last_name;用first_rows时，迅速返回记录，但I/O量大，用all_rows时，返回记录慢，但使用资源少。<BR>　　<BR>　　<B>调整SQL表访问</B><BR>　　<BR>　　全表扫描<BR>　　<BR>　　。返回记录：未排序表&gt;40%，排序表&gt;7%，建议采用并行机制来提高访问速度，DDS；<BR>　　<BR>　　索引访问<BR>　　<BR>　　。最常用的方法，包括索引唯一扫描和索引范围扫描，OLTP；<BR>　　<BR>　　快速完全索引扫描<BR>　　<BR>　　。访问索引中所有数据块，结果相当于全表扫描，可以用索引扫描代替全表扫描，例如：<BR>　　<BR>　　Select serv_id,count(* ) from tg_cdr01 group by serv_id;<BR>　　<BR>　　评估全表扫描的合法性<BR>　　<BR>　　如何实现并行扫描<BR>　　<BR>　　。永久并行化（不推荐）<BR>　　alter table customer parallel degree 8;<BR>　　<BR>　　。单个查询并行化<BR>　　select /*+ full(emp) parallel(emp,8)*/ * from emp;<BR>　　<BR>　　分区表效果明显<BR>　　<BR>　　优化SQL语句排序<BR>　　<BR>　　排序的操作：<BR>　　<BR>　　。order by 子句<BR>　　。group by 子句<BR>　　。select distinct子句<BR>　　。创建索引时<BR>　　。union或minus<BR>　　。排序合并连接<BR>　　<BR>　　如何避免排序<BR>　　<BR>　　。添加索引<BR>　　。在索引中使用distinct子句<BR>　　。避免排序合并连接<BR>　　<BR>　　<B>使用提示进行调整</B><BR>　　<BR>　　使用提示的原则<BR>　　<BR>　　。语法：/*+ hint */<BR>　　。使用表别名:select /*+ index(e dept_idx)*/ * from emp e<BR>　　。检验提示<BR>　　<BR>　　常用的提示<BR>　　<BR>　　。rule<BR>　　。all_rows<BR>　　。first_rows<BR>　　。use_nl<BR>　　。use_hash<BR>　　。use_merge<BR>　　。index<BR>　　。index_asc<BR>　　。no_index<BR>　　。index_desc（常用于使用max内置函数）<BR>　　。index_combine(强制使用位图索引)<BR>　　。index_ffs（索引快速完全扫描）<BR>　　。use_concat(将查询中所有or条件使用union all)<BR>　　。parallel<BR>　　。noparallel<BR>　　。full<BR>　　。ordered（基于成本）<BR>　　<BR>　　<B>调整表连接</B><BR>　　<BR>　　表连接的类型<BR>　　<BR>　　。等连接<BR>　　where 条件中用等式连接；<BR>　　。外部连接（左、右连接）<BR>　　<BR>　　在where条件子句的等式谓词放置一个(+)来实现，例如：<BR>　　select a.ename,b.comm from emp a,bonus b where a.ename=b.ename(+);<BR>　　<BR>　　该语句返回所有emp表的记录；<BR>　　。自连接<BR>　　　Select a.value total, B.value hard, (A.value - b.value) soft ,<BR>　　Round((b.value/a.value)*100,1) perc<BR>　　From v$sysstat a,v$sysstat b<BR>　　Where a.statistic# = 179<BR>　　and B.statistic# = 180;<BR>　　<BR>　　反连接<BR>　　<BR>　　反连接常用于not in or not exists中，是指在查询中找到的任何记录都不包含在结果集中的子查询；不建议使用not in or not exists;<BR>　　<BR>　　。半连接<BR>　　<BR>　　查询中使用exists，含义：即使在子查询中返回多条重复的记录，外部查询也只返回一条记录。<BR>　　<BR>　　嵌套循环连接<BR>　　<BR>　　。被连接表中存在索引的情况下使用；<BR>　　。使用use_nl。<BR>　　<BR>　　hash连接<BR>　　<BR>　　。Hash连接将驱动表加载在内存中，并使用hash技术连接第二个表，提高等连接速度。<BR>　　。适合于大表和小表连接；<BR>　　。使用use_hash。<BR>　　<BR>　　排序合并连接<BR>　　<BR>　　。排序合并连接不使用索引<BR>　　。使用原则：<BR>　　<BR>　　连接表子段中不存在可用索引；<BR>　　<BR>　　查询返回两个表中大部分的数据快；<BR>　　<BR>　　CBO认为全表扫描比索引扫描执行的更快。<BR>　　<BR>　　。使用use_merge<BR>　　<BR>　　<B>使用临时/中间表</B><BR>　　<BR>　　多个大表关联时，可以分别把满足条件的结果集存放到中间表，然后用中间表关联；<BR>　　<BR>　　<B>SQL子查询的调整</B><BR>　　<BR>　　关联与非关联子查询<BR>　　<BR>　　。关联：子查询的内部引用的是外部表，每行执行一次；<BR>　　。非关联：子查询只执行一次，存放在内存中。<BR>　　<BR>　　调整not in 和not exists语句<BR>　　<BR>　　。可以使用外部连接优化not in子句，例如：<BR>　　select ename from emp where dept_no not in<BR>　　(select dept_no from dept where dept_name =‘Math’);<BR>　　<BR>　　改为：<BR>　　select ename from emp,dept<BR>　　where emp.dept_no=dept.dept_no<BR>　　and dept.dept_name is null;<BR>　　<BR>　　使用索引调整SQL<BR>　　<BR>　　Oracle 为什么不使用索引<BR>　　<BR>　　。检查被索引的列或组合索引的首列是否出现在PL/SQL语句的WHERE子句中，这是“执行计划”能用到相关索引的必要条件。<BR>　　<BR>　　。看采用了哪种类型的连接方式。ORACLE的共有Sort Merge Join（SMJ）、Hash Join（HJ）和Nested Loop Join（NL）。在两张表连接，且内表的目标列上建有索引时，只有Nested Loop才能有效地利用到该索引。SMJ即使相关列上建有索引，最多只能因索引的存在，避免数据排序过程。HJ由于须做HASH运算，索引的存在对数据查询速度几乎没有影响。<BR>　　<BR>　　。看连接顺序是否允许使用相关索引。假设表emp的deptno列上有索引，表dept的列deptno上无索引，WHERE语句有emp.deptno=dept.deptno条件。在做NL连接时，emp做为外表，先被访问，由于连接机制原因，外表的数据访问方式是全表扫描，emp.deptno上的索引显然是用不上，最多在其上做索引全扫描或索引快速全扫描。<BR>　　<BR>　　。是否用到系统数据字典表或视图。由于系统数据字典表都未被分析过，可能导致极差的“执行计划”。但是不要擅自对数据字典表做分析，否则可能导致死锁，或系统性能下降。<BR>　　<BR>　　。索引列是否函数的参数。如是，索引在查询时用不上。<BR>　　<BR>　　。是否存在潜在的数据类型转换。如将字符型数据与数值型数据比较，ORACLE会自动将字符型用to_number()函数进行转换，从而导致上一种现象的发生。<BR>　　<BR>　　。是否为表和相关的索引搜集足够的统计数据。对数据经常有增、删、改的表最好定期对表和索引进行分析，可用SQL语句“analyze table xxxx compute statistics for all indexes;”。ORACLE掌握了充分反映实际的统计数据，才有可能做出正确的选择。<BR>　　<BR>　　。索引列的选择性不高。 　　我们假设典型情况，有表emp，共有一百万行数据，但其中的emp.deptno列，数据只有4种不同的值，如10、20、30、40。虽然emp数据行有很多，ORACLE缺省认定表中列的值是在所有数据行均匀分布的，也就是说每种deptno值各有25万数据行与之对应。假设SQL搜索条件DEPTNO=10，利用deptno列上的索引进行数据搜索效率，往往不比全表扫描的高。<BR>　　<BR>　　。索引列值是否可为空（NULL）。如果索引列值可以是空值，在SQL语句中那些要返回NULL值的操作，将不会用到索引，如COUNT（*），而是用全表扫描。这是因为索引中存储值不能为全空。<BR>　　<BR>　　。看是否有用到并行查询（PQO）。并行查询将不会用到索引。<BR>　　<BR>　　。如果从以上几个方面都查不出原因的话，我们只好用采用在语句中加hint的方式强制ORACLE使用最优的“执行计划”。 　hint采用注释的方式，有行注释和段注释两种方式。 　如我们想要用到A表的IND_COL1索引的话，可采用以下方式： 　“SELECT /*+ INDEX（A IND_COL1）*/ * FROM A WHERE COL1 = XXX;"<BR>　　<BR>　　<B>如何屏蔽索引</B><BR>　　<BR>　　语句的执行计划中有不良索引时，可以人为地屏蔽该索引，方法：<BR>　　<BR>　　。数值型：在索引字段上加0，例如<BR>　　select * from emp where emp_no+0 = v_emp_no;<BR>　　<BR>　　。字符型：在索引字段上加‘’，例如<BR>　　select * from tg_cdr01 where msisdn||’’=v_msisdn; <img src ="http://www.blogjava.net/Titan/aggbug/24433.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Titan/" target="_blank">Titan</a> 2005-12-18 00:09 <a href="http://www.blogjava.net/Titan/articles/24433.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于三种JOIN的理解 </title><link>http://www.blogjava.net/Titan/articles/24432.html</link><dc:creator>Titan</dc:creator><author>Titan</author><pubDate>Sat, 17 Dec 2005 16:06:00 GMT</pubDate><guid>http://www.blogjava.net/Titan/articles/24432.html</guid><wfw:comment>http://www.blogjava.net/Titan/comments/24432.html</wfw:comment><comments>http://www.blogjava.net/Titan/articles/24432.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Titan/comments/commentRss/24432.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Titan/services/trackbacks/24432.html</trackback:ping><description><![CDATA[　　Nested loop join:<BR>　　<BR>　　步骤：确定一个驱动表(outer table)，另一个表为inner table，驱动表中的每一行与inner表中的相应记录JOIN。类似一个嵌套的循环。适用于驱动表的记录集比较小（&lt;10000）而且inner表需要有有效的访问方法（Index）。需要注意的是：JOIN的顺序很重要，驱动表的记录集一定要小，返回结果集的响应时间是最快的。<BR>　　<BR>　　cost　= outer access cost + (inner access cost * outer cardinality)<BR>　　<BR>　　|　 2 |　 NESTED LOOPS　　　　　　　　|　　　　　　　|　　 3 |　 141 |　　 7　(15)|<BR>　　|　 3 |　　TABLE ACCESS FULL　　　　　| EMPLOYEES　　|　　 3 |　　60 |　　 4　(25)|<BR>　　|　 4 |　　TABLE ACCESS BY INDEX ROWID| JOBS　　　　 |　　19 |　 513 |　　 2　(50)|<BR>　　|　 5 |　　 INDEX UNIQUE SCAN　　　　 | JOB_ID_PK　　|　　 1 |　　　 |　　　　　　|<BR>　　<BR>　　EMPLOYEES为outer table, JOBS为inner table.<BR>　　<BR>　　Hash join<BR>　　<BR>　　步骤：将两个表中较小的一个在内存中构造一个HASH表（对JOIN KEY），扫描另一个表，同样对JOIN KEY进行HASH后探测是否可以JOIN。适用于记录集比较大的情况。需要注意的是：如果HASH表太大，无法一次构造在内存中，则分成若干个partition，写入磁盘的temporary segment，则会多一个写的代价，会降低效率。<BR>　　<BR>　　cost = (outer access cost * # of hash partitions) + inner access cost<BR>　　--------------------------------------------------------------------------<BR>　　| Id　| Operation　　　　　　|　Name　　　　| Rows　| Bytes | Cost (%CPU)|<BR>　　--------------------------------------------------------------------------<BR>　　|　 0 | SELECT STATEMENT　　 |　　　　　　　|　 665 | 13300 |　　 8　(25)|<BR>　　|　 1 |　HASH JOIN　　　　　 |　　　　　　　|　 665 | 13300 |　　 8　(25)|<BR>　　|　 2 |　 TABLE ACCESS FULL　| ORDERS　　　 |　 105 |　 840 |　　 4　(25)|<BR>　　|　 3 |　 TABLE ACCESS FULL　| ORDER_ITEMS　|　 665 |　7980 |　　 4　(25)|<BR>　　--------------------------------------------------------------------------<BR>　　<BR>　　ORDERS为HASH TABLE，ORDER_ITEMS扫描<BR>　　<BR>　　Sort merge join<BR>　　<BR>　　步骤：将两个表排序，然后将两个表合并。通常情况下，只有在以下情况发生时，才会使用此种JOIN方式：<BR>　　<BR>　　1.RBO模式<BR>　　<BR>　　2.不等价关联(&gt;,&lt;,&gt;=,&lt;=,&lt;&gt;)<BR>　　<BR>　　3.HASH_JOIN_ENABLED=false<BR>　　<BR>　　4.数据源已排序<BR>　　<BR>　　cost = (outer access cost * # of hash partitions) + inner access cost <img src ="http://www.blogjava.net/Titan/aggbug/24432.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Titan/" target="_blank">Titan</a> 2005-12-18 00:06 <a href="http://www.blogjava.net/Titan/articles/24432.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SQL语句主要的连接方法</title><link>http://www.blogjava.net/Titan/articles/24430.html</link><dc:creator>Titan</dc:creator><author>Titan</author><pubDate>Sat, 17 Dec 2005 15:56:00 GMT</pubDate><guid>http://www.blogjava.net/Titan/articles/24430.html</guid><wfw:comment>http://www.blogjava.net/Titan/comments/24430.html</wfw:comment><comments>http://www.blogjava.net/Titan/articles/24430.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Titan/comments/commentRss/24430.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Titan/services/trackbacks/24430.html</trackback:ping><description><![CDATA[SQL语句主要的连接方法<BR><BR>a) Nested-loop&nbsp;join<BR>适合于小表(几千条，几万条记录)与大表做联接<BR>在联接列上有索引。<BR><BR>分内表和外表(驱动表)，靠近from子句的是内表。从效率上讲，小表应该作外表，大表应该作内表，即大表查询时走索引。<BR><BR>COST=&nbsp;Access&nbsp;cost&nbsp;of&nbsp;A(驱动表)&nbsp;+&nbsp;(access&nbsp;cost&nbsp;of&nbsp;B&nbsp;*&nbsp;number&nbsp;of&nbsp;rows&nbsp;from&nbsp;A)<BR><BR>成本计算方法：<BR>设小表100行，大表100000行。<BR><BR>两表均有索引：<BR>如果小表在内，大表在外(驱动表)的话，则扫描次数为：<BR>100000+100000*2&nbsp;(其中2表示IO次数，一次索引，一次数据)<BR>如果大表在内，小表在外(驱动表)的话，则扫描次数为：<BR>100+100*2.<BR><BR>两表均无索引：<BR>如果小表在内，大表在外的话，则扫描次数为：<BR>100000+100*100000<BR>如果大表在内，小表在外的话，则扫描次数为：<BR>100+100000*100<BR><BR>注意：如果一个表有索引，一个表没有索引，ORACLE会将没有索引的表作驱动表。如果两个表都有索引，则外表作驱动表。如果两个都没索引的话，则也是外表作驱动表。<BR><BR>基本的执行计划如下所示：<BR>NESTED&nbsp;LOOPS<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TABLE&nbsp;ACCESS&nbsp;(BY&nbsp;ROWID)&nbsp;&nbsp;OF&nbsp;&nbsp;our_outer_table<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;INDEX&nbsp;(..SCAN)&nbsp;OF&nbsp;outer_table_index(….)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TABLE&nbsp;ACCESS&nbsp;(BY&nbsp;ROWID)&nbsp;&nbsp;OF&nbsp;&nbsp;our_inner_table<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;INDEX&nbsp;(..SCAN)&nbsp;OF&nbsp;inner_table_index(….)<BR><BR>b) Hash&nbsp;join&nbsp;<BR><BR>适合于大表与大表，小表(几十万，几百万)与大表之间的联连。<BR>联接列上不需要索引。<BR><BR>基本执行计划如下：<BR>HASH&nbsp;JOIN<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TABLE&nbsp;ACCESS&nbsp;(….)&nbsp;&nbsp;OF&nbsp;&nbsp;tableA<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TABLE&nbsp;ACCESS&nbsp;(….)&nbsp;&nbsp;OF&nbsp;&nbsp;tableB<BR><BR>cost=&nbsp;(access&nbsp;cost&nbsp;of&nbsp;A&nbsp;*&nbsp;number&nbsp;of&nbsp;hash&nbsp;partitions&nbsp;of&nbsp;B)&nbsp;+&nbsp;access&nbsp;cost&nbsp;of&nbsp;B<BR><BR>可以看出主要成本在于A表是否可以被Cache。Hash_area_size的大小将决定Hash&nbsp;Join的主要成本。可以看出Hash&nbsp;Join的成本和返回集合并没有直接的关系，所以当返回结果集比较大的时候一般具有较好的性能。<BR><BR>为了加快hash&nbsp;join的速度，可以调大hash_area_size和pga_aggregate_target（默认为25M）的值。<BR><BR><BR>c) Sort&nbsp;Merge&nbsp;join<BR><BR>每一个Row&nbsp;Source在Join列上均排序。<BR>然后两个排序后的Row&nbsp;Source合并后，作一个结果集返回。<BR>Sort/Merge&nbsp;Join仅仅对equal&nbsp;Join有效。<BR><BR>基本执行计划<BR>MERGE&nbsp;(JOIN)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SORT&nbsp;(JOIN)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TABLE&nbsp;ACCESS&nbsp;(….)&nbsp;&nbsp;OF&nbsp;&nbsp;tableA<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SORT&nbsp;(JOIN)&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;TABLE&nbsp;ACCESS&nbsp;(….)&nbsp;&nbsp;OF&nbsp;&nbsp;tableB<BR><BR>cost=&nbsp;access&nbsp;cost&nbsp;of&nbsp;A&nbsp;+&nbsp;access&nbsp;cost&nbsp;of&nbsp;B&nbsp;+(sort&nbsp;cost&nbsp;of&nbsp;A&nbsp;+&nbsp;sort&nbsp;cost&nbsp;of&nbsp;B)<BR><BR>可以看出Sort的成本是Merge&nbsp;Join的主要构成部分。这样sort_area_size的大小将很大程度决定Merge&nbsp;Join的大小。同样如果A表或者B表已经经过排序的，那么Merge&nbsp;Join往往具有很好的性能。其不会走索引。<BR><BR>没有驱动表的概念，即时响应能力较差。<BR><img src ="http://www.blogjava.net/Titan/aggbug/24430.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Titan/" target="_blank">Titan</a> 2005-12-17 23:56 <a href="http://www.blogjava.net/Titan/articles/24430.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转载]Effective SQL </title><link>http://www.blogjava.net/Titan/articles/21744.html</link><dc:creator>Titan</dc:creator><author>Titan</author><pubDate>Mon, 28 Nov 2005 15:06:00 GMT</pubDate><guid>http://www.blogjava.net/Titan/articles/21744.html</guid><wfw:comment>http://www.blogjava.net/Titan/comments/21744.html</wfw:comment><comments>http://www.blogjava.net/Titan/articles/21744.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/Titan/comments/commentRss/21744.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/Titan/services/trackbacks/21744.html</trackback:ping><description><![CDATA[<P>前言： <BR>Effective SQL <BR>浏览了一遍EFFECTIVE系列书名，似乎缺少Effective SQL,所以有了一种莫名的冲动吧。 <BR>参考CSDN和博客堂的文档，加上以自己的切身体会总结出的一些Effective，希望能够给大家带来一些帮助。 <BR>由于转载,整理的文章比较多,所以不一一指出出处,请原文作者多多谅解. <BR>签于作者水平有限，所以可能于实际中有较大出入，望见谅。 <BR>如有不正之处，请及时与作者本人联系。谢谢！ </P>
<P><BR>正文： </P>
<P>一.名词解释： <BR>0。SQL 结构化查询语言(Structured Query Language) </P>
<P>1。非关系型数据库系统 <BR>做为第一代数据库系统的总称，其包括2种类型：“层次”数据库与“网状”数据库 </P>
<P>“层次”数据库管理系统 eg:IBM&amp;IMS (Information&nbsp; Management System) <BR>特点:数据按层次模型组织 </P>
<P>"网状"数据库 <BR>特点：数据按网状模型组织 </P>
<P>2。关系型数据库系统 <BR>关系性数据库管理系统 (RDBMS) <BR>eg:SQL/DS , DB2, Oracle ,Informix ,Unity,dBASE等 <BR>特点：数据按二维的表格组织。 </P>
<P>3。数据库(DataBase) <BR>按一定结构存储在计算机中相互关联的数据的集合。 </P>
<P>4。数据库管理系统DBMS(Database Management System) <BR>一个通用的软件系统。就是让你怎么管理你的数据库。其中包括存储，安全，完整性管理等。 </P>
<P>5。数据库应用系统DBAS （Database Application System） <BR>数据库应用程序系统，建立在DBMS基础之上的。就是一个面向用户的软件系统。 </P>
<P>6。ANSI标准 （American National Standards Institute）美国国家标准委员会 <BR>因为1999年第2次更新SQL，所以SQL又称为SQL99或SQL3（第3版，前2个版本分别为1986年的sql ,1992 年的sql2/sql92）。 </P>
<P>7。SQL语句的3种类型 <BR>数据操作语句(Data Manipulation Language ) DML 关于数据操作命令的&nbsp; eg:select,insert,update,delete <BR>数据定义语句(Data Definition Language ) DDL&nbsp;&nbsp;&nbsp;&nbsp; 关于数据对象访问的&nbsp; eg:create， drop <BR>数据控制语句(Data Control Language) DCL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 关于权限的&nbsp;&nbsp;eg:grant ，revoke </P>
<P>8。PL/SQL Procedural Language/sql <BR>用于oracle的语言 </P>
<P>9.T-SQL&nbsp; transact-sql <BR>用于 microsoft sql server 和sybase adaptive server </P>
<P>10。E.F.Codd关于关系型数据库12条检验原则（MYSQL，不支持视图和原子事物处理，所以排除） <BR>内容：暂略 </P>
<P>11。数据库设计之新奥尔良方法。 <BR>需求分析==》概念设计==》逻辑设计==》物理设计. <BR>4个步骤的具体中以需求分析最重要. <BR>需求分析的内容:暂略 <BR>概念设计的内容:暂略 <BR>逻辑设计的内容:暂略 <BR>物理设计的内容:暂略 </P>
<P></P>
<P></P>
<P><BR>二.数据库优化方案 <BR>1.索引 <BR>一 概述 </P>
<P>&nbsp;&nbsp; 可以利用索引快速访问数据库表中的特定信息。索引是对数据库表中一个或多个列的值进行排序的结构。 <BR>&nbsp;&nbsp; 索引提供指针以指向存储在表中指定列的数据值，然后根据指定的排序次序排列这些指针。 <BR>&nbsp;&nbsp; 数据库使用索引的方式与使用书的目录很相似：通过搜索索引找到特定的值， <BR>&nbsp;&nbsp; 然后跟随指针到达包含该值的行 </P>
<P>索引是一个单独的、物理的数据库结构，它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。 </P>
<P>一个表的存储是由两部分组成的，一部分用来存放表的数据页面，另一部分存放索引页面。索引就存放在索引页面上</P>
<P>二 索引的两种类型： </P>
<P>聚集索引=簇集索引</P>
<P>聚集索引基于数据行的键值在表内排序和存储这些数据行。由于数据行按基于聚集索引键的排序次序存储， <BR>因此聚集索引对查找行很有效。每个表只能有一个聚集索引，因为数据行本身只能按一个顺序存储。 <BR>数据行本身构成聚集索引的最低级别。 </P>
<P>只有当表包含聚集索引时，表内的数据行才按排序次序存储。如果表没有聚集索引， <BR>则其数据行按堆集方式存储。 </P>
<P>聚集索引对于那些经常要搜索范围值的列特别有效。使用聚集索引找到包含第一个值的行后， <BR>便可以确保包含后续索引值的行在物理相邻。例如，如果应用程序执行的一个查询经常检索某一日期范围 <BR>内的记录，则使用聚集索引可以迅速找到包含开始日期的行，然后检索表中所有相邻的行， <BR>直到到达结束日期。这样有助于提高此类查询的性能。同样，如果对从表中检索的数据进行排序时 <BR>经常要用到某一列，则可以将该表在该列上聚集（物理排序），避免每次查询该列时都进行排序， <BR>从而节省成本 </P>
<P>非聚集索引 </P>
<P>非聚集索引具有完全独立于数据行的结构。非聚集索引的最低行包含非聚集索引的键值， <BR>并且每个键值项都有指针指向包含该键值的数据行。数据行不按基于非聚集键的次序存储。 </P>
<P>在非聚集索引内，从索引行指向数据行的指针称为行定位器。 <BR>行定位器的结构取决于数据页的存储方式是堆集还是聚集。对于堆集，行定位器是指向行的指针。 <BR>对于有聚集索引的表，行定位器是聚集索引键。 <BR>只有在表上创建了聚集索引时，表内的行才按特定的顺序存储。这些行就基于聚集索引键按顺序存储。 <BR>如果一个表只有非聚集索引，它的数据行将按无序的堆集方式存储 <BR>非聚集索引可以建多个,两者都能改善查询性能 </P>
<P>非聚集索引与聚集索引一样有 B 树结构，但是有两个重大差别： <BR>数据行不按非聚集索引键的顺序排序和存储。 <BR>非聚集索引的叶层不包含数据页。 <BR>相反，叶节点包含索引行。每个索引行包含非聚集键值以及一个或多个行定位器， <BR>这些行定位器指向有该键值的数据行（如果索引不唯一，则可能是多行）。 <BR>非聚集索引可以在有聚集索引的表、堆集或索引视图上定义 </P>
<P><BR>聚集索引--&gt;顺序表结构.其物理数据和逻辑排序紧邻. <BR>非聚集索引--&gt;单链表结构.起物理和逻辑排序不按顺序排列. </P>
<P>打个比方. <BR>一本字典,你现在查一个陈字.你有2种方法.首先,你在知道他念chen的情况下去按照拼音字母去查找.他是排在字母A,B <BR>于是你很容易的就找到"陈"字.第2种方法则是按编旁查找,先找到耳朵旁,去找到一个临时的编旁表在去找"东"这个字,然后按照给出的 <BR>页数找到相应的位置. <BR>显然,第一种方法就是聚集索引,按照物理位置根据排序来查找. <BR>第2种方法则是非聚集索引,按照一个临时索引来查找. </P>
<P>另外 <BR>唯一索引 </P>
<P>唯一索引可以确保索引列不包含重复的值。在多列唯一索引的情况下，该索引可以确保索引列中每个值组 <BR>合都是唯一的。唯一索引既是索引也是约束。 </P>
<P>复合索引 <BR>索引项是多个的就叫组合索引，也叫复合索引。复合索引使用时需要注意索引项的次序。 </P>
<P>二 索引的创建</P>
<P>有两种方法可以在 SQL Server 内定义索引: CREATE INDEX 语句和CREATE TABLE 语句</P>
<P>CREATE TABLE支持在创建索引时使用下列约束：</P>
<P>PRIMARY KEY 创建唯一索引来强制执行主键 <BR>UNIQUE 创建唯一索引 <BR>CLUSTERED 创建聚集索引 <BR>NONCLUSTERED 创建非聚集索引 </P>
<P>注: 1 定义索引时，可以指定每列的数据是按升序还是降序存储。如果不指定，则默认为升序 <BR>&nbsp;&nbsp;&nbsp; 2 支持在计算列上创建索引 <BR>&nbsp;&nbsp;&nbsp; 3 为索引指定填充因子 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可标识填充因子来指定每个索引页的填满程度。索引页上的空余空间量很重要， <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 因为当索引页填满时，系统必须花时间拆分它以便为新行腾出空间。 </P>
<P><BR>三 索引的维护语句 </P>
<P>DBCC DBREINDEX&nbsp;&nbsp;&nbsp; 重建指定数据库中表的一个或多个索引 <BR>DBCC INDEXFRAG　　整理指定的表或视图的聚集索引和辅助索引碎片 </P>
<P>比较</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 速度&nbsp;&nbsp;&nbsp; 兼容性&nbsp;&nbsp;&nbsp;&nbsp; 日志影响&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 数据访问影响&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 额外磁盘空间 <BR>DBCC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 最快&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 最好&nbsp;&nbsp;&nbsp;&nbsp; 大,但能通过把&nbsp;&nbsp; 操作过程中数据不&nbsp;&nbsp; 需要大 <BR>DBREINDEX&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以重&nbsp;&nbsp; 故障还原模型设&nbsp; 能访问，影响大 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 建所有&nbsp;&nbsp; 为简单减少日志&nbsp;&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 有索引 </P>
<P>DBCC&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 慢&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 但可&nbsp;&nbsp; 必须分&nbsp;&nbsp; 小&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 数据未被锁定&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 需要小 <BR>INDEXDEFRAG&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 随时终 别指定 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 止执行&nbsp;&nbsp; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P>drop index&nbsp;&nbsp;&nbsp; 中等&nbsp; 必须分&nbsp;&nbsp; 大,但能通过把&nbsp;&nbsp;&nbsp; 仅在操作执行时&nbsp;&nbsp;&nbsp; 中等，操作在&nbsp;&nbsp;&nbsp; <BR>create index&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 别指定&nbsp;&nbsp; 故障还原模型设&nbsp;&nbsp; 锁定数据&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tempdb中进行 <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为简单减少日志 </P>
<P><BR>四 查看索引的方法 </P>
<P>sp_indexes&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 返回指定远程表的索引信息 <BR>INDEXKEY_PROPERTY 返回有关索引键的信息 <BR>sysindexes系统表&nbsp; 数据库中的每个索引和表在表中各占一行，该表存储在每个数据库中 </P>
<P><BR>五 可以通过执行计划 <BR>&nbsp;&nbsp; 查看sql语句执行时是否建立在索引之上 </P>
<P>比如 <BR>CREATE TABLE Test <BR>(Field_1 int NOT NULL, <BR>&nbsp;Field_2 int CONSTRAINT PK_Test <BR>&nbsp;PRIMARY KEY CLUSTERED (Field_1)) </P>
<P>CREATE index IX_Test ON Test (Field_2)</P>
<P>1 SELECT * FROM Test WHERE Field_2 =408 <BR>&nbsp; 执行计划可以看出使用了IX_Test索引 <BR>2 SELECT * FROM Test WHERE Field_1 =1 <BR>&nbsp; 执行计划可以看出使用了PK_Test <BR>3 但如果是SELECT * FROM Test with (index(IX_Test)) WHERE Field_1 =1 <BR>&nbsp; 则指定使用索引 </P>
<P><BR>六 索引的具体使用 </P>
<P>1） 索引的设计 <BR>A:尽量避免表扫描 <BR>检查你的查询语句的where子句，因为这是优化器重要关注的地方。包含在where里面的每一列（column)都是可能的侯选索引，为能达到最优的性能，考虑在下面给出的例子：对于在where子句中给出了column1这个列。 <BR>下面的两个条件可以提高索引的优化查询性能！ <BR>第一：在表中的column1列上有一个单索引 <BR>第二：在表中有多索引，但是column1是第一个索引的列 <BR>避免定义多索引而column1是第二个或后面的索引，这样的索引不能优化服务器性能 <BR>例如：下面的例子用了pubs数据库。 <BR>SELECT au_id, au_lname, au_fname FROM authors <BR>WHERE au_lname = ’White’ <BR>按下面几个列上建立的索引将会是对优化器有用的索引 <BR>?au_lname <BR>?au_lname, au_fname <BR>而在下面几个列上建立的索引将不会对优化器起到好的作用 <BR>?au_address <BR>?au_fname, au_lname <BR>考虑使用窄的索引在一个或两个列上，窄索引比多索引和复合索引更能有效。用窄的索引，在每一页上 <BR>将会有更多的行和更少的索引级别（相对与多索引和复合索引而言），这将推进系统性能。 <BR>对于多列索引，SQL Server维持一个在所有列的索引上的密度统计（用于联合）和在第一个索引上的 <BR>histogram（柱状图）统计。根据统计结果，如果在复合索引上的第一个索引很少被选择使用，那么优化器对很多查询请求将不会使用索引。 <BR>有用的索引会提高select语句的性能，包括insert,uodate,delete。 <BR>但是，由于改变一个表的内容，将会影响索引。每一个insert,update,delete语句将会使性能下降一些。实验表明，不要在一个单表上用大量的索引，不要在共享的列上（指在多表中用了参考约束）使用重叠的索引。 <BR>在某一列上检查唯一的数据的个数，比较它与表中数据的行数做一个比较。这就是数据的选择性，这比较结果将会帮助你决定是否将某一列作为侯选的索引列，如果需要，建哪一种索引。你可以用下面的查询语句返回某一列的不同值的数目。 <BR>select count(distinct cloumn_name) from table_name <BR>假设column_name是一个10000行的表，则看column_name返回值来决定是否应该使用，及应该使用什么索引。 <BR>Unique values Index </P>
<P>5000 Nonclustered index <BR>20 Clustered index <BR>3 No index </P>
<P><BR>2) 镞索引和非镞索引的选择 </P>
<P>&lt;1:&gt;镞索引是行的物理顺序和索引的顺序是一致的。页级，低层等索引的各个级别上都包含实际的数据页。一个表只能是有一个镞索引。由于update,delete语句要求相对多一些的读操作，因此镞索引常常能加速这样的操作。在至少有一个索引的表中，你应该有一个镞索引。 <BR>在下面的几个情况下，你可以考虑用镞索引： <BR>例如： 某列包括的不同值的个数是有限的（但是不是极少的） <BR>顾客表的州名列有50个左右的不同州名的缩写值，可以使用镞索引。 <BR>例如： 对返回一定范围内值的列可以使用镞索引，比如用between,&gt;,&gt;=,&lt;,&lt;=等等来对列进行操作的列上。 <BR>select * from sales where ord_date between ’5/1/93’ and ’6/1/93’ <BR>例如： 对查询时返回大量结果的列可以使用镞索引。 <BR>SELECT * FROM phonebook WHERE last_name = ’Smith’ </P>
<P>当有大量的行正在被插入表中时，要避免在本表一个自然增长（例如，identity列）的列上建立镞索引。如果你建立了镞的索引，那么insert的性能就会大大降低。因为每一个插入的行必须到表的最后，表的最后一个数据页。 <BR>当一个数据正在被插入（这时这个数据页是被锁定的），所有的其他插入行必须等待直到当前的插入已经结束。 <BR>一个索引的叶级页中包括实际的数据页，并且在硬盘上的数据页的次序是跟镞索引的逻辑次序一样的。 </P>
<P>&lt;2:&gt;一个非镞的索引就是行的物理次序与索引的次序是不同的。一个非镞索引的叶级包含了指向行数据页的指针。 <BR>在一个表中可以有多个非镞索引，你可以在以下几个情况下考虑使用非镞索引。 <BR>在有很多不同值的列上可以考虑使用非镞索引 <BR>例如：一个part_id列在一个part表中 <BR>select * from employee where emp_id = ’pcm9809f’ <BR>查询语句中用order by 子句的列上可以考虑使用镞索引 </P>
<P>&nbsp;</P>
<P>3) 一个表列如果设为主键(primary key),它会自动生成一个聚簇索引 <BR>这时不能直接使用Drop index Table1.Tableindex1语句 <BR>必须删除主键约束，用语句:alter table table1 drop constraint 约束名(如pk_xxx) </P>
<P><BR>七.全文索引 <BR>use pubs <BR>　　go </P>
<P>　　--打开数据库全文索引的支持</P>
<P>　execute sp_fulltext_database 'enable' <BR>　go </P>
<P>　　--建立全文目录ft_titles</P>
<P>　　execute sp_fulltext_catalog 'ft_titles', 'create' <BR>　　go </P>
<P>　　--为titles表建立全文索引数据元，UPKCL_titleidind是主键所建立的唯一索引，可由sp_help titles得知</P>
<P>　　execute sp_fulltext_table 'titles','create', 'ft_titles', 'UPKCL_titleidind' <BR>　　go </P>
<P>　　--设置全文索引列名</P>
<P>　　exec sp_fulltext_column 'titles', 'title', 'add' <BR>　　go <BR>　　exec sp_fulltext_column 'titles', 'notes', 'add' <BR>　　go </P>
<P>　　--建立全文索引</P>
<P>　　exec sp_fulltext_table 'titles', 'activate' <BR>　　go </P>
<P>　　--填充全文索引目录</P>
<P>　　exec sp_fulltext_catalog 'ft_titles', 'start_full' <BR>　　go </P>
<P>　　--使用contains和freetext</P>
<P>　　select title, notes from titles <BR>　　where contains(title, '"computer Cooking"') <BR>　　go <BR>　　select title, notes from titles <BR>　　where freetext(title, 'computer Cooking') <BR>　　go <BR>　　select title, notes from titles <BR>　　where freetext(title, '"computer Cooking"') <BR>　　go <BR>　　select title, notes from titles <BR>　　where contains(title, 'computer') <BR>　　go <BR>　　select title, notes from titles <BR>　　where freetext (*, 'computer') <BR>　　go </P>
<P>这里提一下google的搜索引擎的原理. <BR>他把每个字词都做为单元去查询. <BR>打个比方:我在字典里查询,现在我要搜索"树型"这个词,他会把这个树型这个词全文扫描一遍,生成一个二叉树.并记下他的页数. <BR>然后当我第2次查找的时候显然这个"记忆"提示,然后"提取".如果你对某一个字段做了全文索引的话，他会全文扫描表一遍,然后纪录下 <BR>相应的纪录,生成二叉树. <BR>如果我要查找"树叶",同理也可以得出页数.但当我们去查找一下"树型结构"他则会把"树型"和"树型结构"都"纪录"下来. </P>
<P></P>
<P>八.巧妙的使用索引. <BR>SELECT SUM(quantity) AS quantity FROM test WHERE... <BR>1.若WHERE 里用的是字段与常量比较，MSSQL会自动引用该字段上的索引；若用的是变量，MSSQL不会自动引用该字段上的索引而是根据聚集索引进行扫描 <BR>2.加上with(index(索引名))指定索引，即： <BR>SELECT SUM(quantity) AS quantity FROM with(index(索引名)) test WHERE... <BR>指定索引后，WHERE 里不论是常量还是变量，MSSQL都根据指定的索引进行扫描 <BR>3.DBCC DBREINDEX执行并不一定能优化MSSQL性能，慎用 <BR>4.如果在pub_id上建立索引的话 <BR>select * from titles where pub_id-500 &gt;1000&nbsp;&nbsp; ---------(a) <BR>select * from titles where pub_id &gt;1000+500&nbsp; -----------(b) <BR>请选用(b)语句,这样的话，他会利用索引,而(a)的话由于对字段操作了,所以不会利用索引. <BR>5.尽量避免用like语句, <BR>如果去查找baa%,caa%的话 <BR>如果是like '%aa%','_aa%','[m-z]o%'&nbsp; 则根本不会用到索引. <BR>替换方法.columns like 'baa%' or&nbsp; columns like 'caa %' <BR>6什么情况下应不建或少建索引 <BR>a.表记录太少 .因为索引的话，要对数据库往返2次操作,如果1个表只有几行字段的话，数据库会对他的纪录一次性全部取出来,这样的效率要远远高于索引. <BR>b.经常insert,delete,update的表&nbsp; 对一些经常处理的业务表应在查询允许的情况下尽量减少索引 <BR>c.数据重复且分布平均的表字段,如:性别字段,各占50%的话，你即使建了,也起不到明显的作用. <BR>d.经常和主字段一块查询但主字段索引值比较多的表字段 <BR>表经常按收费序号、户标识编号、抄表日期、电费发生年月、操作标志来具体查询某一笔收款的情况，如果将所有的字段都建在一个索引里那将会增加数据的修改、插入、删除时间，从实际上分析一笔收款如果按收费序号索引就已经将记录减少到只有几条，如果再按后面的几个字段索引查询将对性能不产生太大的影响。 <BR>e.如果一个表的记录达到100万以上的话，要对其中一个字段建索引可能要花很长的时间，甚至导致服务器数据库死机，因为在建索引的时候ORACLE要将索引字段所有的内容取出并进行全面排序，数据量大的话可能导致服务器排序内存不足而引用磁盘交换空间进行，这将严重影响服务器数据库的工作。解决方法是增大数据库启动初始化中的排序内存参数，如果要进行大量的索引修改可以设置10M以上的排序内存（ORACLE缺省大小为64K），在索引建立完成后应将参数修改回来，因为在实际OLTP数据库应用中一般不会用到这么大的排序内存。 </P>
<P><BR>以下转载 <BR>&nbsp;great_domino 的 Blog </P>
<P>探讨如何在有着1000万条数据的MS SQL SERVER数据库中实现快速的数据提取和数据分页。以下代码说明了我们实例中数据库的“红头文件”一表的部分数据结构：</P>
<P>CREATE TABLE [dbo].[TGongwen] (&nbsp;&nbsp;&nbsp; --TGongwen是红头文件表名</P>
<P>&nbsp;&nbsp; [Gid] [int] IDENTITY (1, 1) NOT NULL , <BR>--本表的id号，也是主键 </P>
<P>&nbsp;&nbsp; [title] [varchar] (80) COLLATE Chinese_PRC_CI_AS NULL ,&nbsp; <BR>--红头文件的标题 </P>
<P>&nbsp;&nbsp; [fariqi] [datetime] NULL , <BR>--发布日期 </P>
<P>&nbsp;&nbsp; [neibuYonghu] [varchar] (70) COLLATE Chinese_PRC_CI_AS NULL , <BR>--发布用户 </P>
<P>&nbsp;&nbsp; [reader] [varchar] (900) COLLATE Chinese_PRC_CI_AS NULL ,</P>
<P>--需要浏览的用户。每个用户中间用分隔符“,”分开</P>
<P>) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]</P>
<P>GO</P>
<P><BR>　　下面，我们来往数据库中添加1000万条数据： </P>
<P>declare @i int</P>
<P>set @i=1</P>
<P>while @i&lt;=250000</P>
<P>begin</P>
<P>&nbsp;&nbsp;&nbsp; insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-2-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最先的25万条记录')</P>
<P>&nbsp;&nbsp;&nbsp; set @i=@i+1</P>
<P>end</P>
<P>GO</P>
<P>&nbsp;</P>
<P>declare @i int</P>
<P>set @i=1</P>
<P>while @i&lt;=250000</P>
<P>begin</P>
<P>&nbsp;&nbsp;&nbsp; insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-9-16','办公室','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是中间的25万条记录')</P>
<P>&nbsp;&nbsp;&nbsp; set @i=@i+1</P>
<P>end</P>
<P>GO</P>
<P>&nbsp;</P>
<P>declare @h int</P>
<P>set @h=1</P>
<P>while @h&lt;=100</P>
<P>begin</P>
<P>declare @i int</P>
<P>set @i=2002</P>
<P>while @i&lt;=2003</P>
<P>begin</P>
<P>declare @j int</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set @j=0</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while @j&lt;50</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin</P>
<P>declare @k int</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set @k=0</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while @k&lt;50</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; begin</P>
<P>&nbsp;&nbsp;&nbsp; insert into Tgongwen(fariqi,neibuyonghu,reader,title) values(cast(@i as varchar(4))+'-8-15 3:'+cast(@j as varchar(2))+':'+cast(@j as varchar(2)),'通信科','办公室,通信科,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,外事科','这是最后的50万条记录')</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set @k=@k+1</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end</P>
<P>set @j=@j+1</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end</P>
<P>set @i=@i+1</P>
<P>end</P>
<P>set @h=@h+1</P>
<P>end</P>
<P>GO</P>
<P>&nbsp;</P>
<P>declare @i int</P>
<P>set @i=1</P>
<P>while @i&lt;=9000000</P>
<P>begin</P>
<P>&nbsp;&nbsp;&nbsp; insert into Tgongwen(fariqi,neibuyonghu,reader,title) values('2004-5-5','通信科','通信科,办公室,王局长,刘局长,张局长,admin,刑侦支队,特勤支队,交巡警支队,经侦支队,户政科,治安支队,外事科','这是最后添加的900万条记录')</P>
<P>&nbsp;&nbsp;&nbsp; set @i=@i+1000000</P>
<P>end</P>
<P>GO</P>
<P>通过以上语句，我们创建了25万条由于2004年2月5日发布的记录，25万条由办公室于2004年9月6日发布的记录，2002年和2003年各100个2500条相同日期、不同分秒的记录（共50万条），还有由通信科于2004年5月5日发布的900万条记录，合计1000万条。</P>
<P>何时使用聚集索引或非聚集索引</P>
<P>　　下面的表总结了何时使用聚集索引或非聚集索引（很重要）。</P>
<P>　　动作描述 <BR>　　　使用聚集索引 <BR>　　　使用非聚集索引 <BR>&nbsp; <BR>　　列经常被分组排序 <BR>　　　应 <BR>　　　应 <BR>&nbsp; <BR>　　返回某范围内的数据 <BR>　　　应 <BR>　　　不应 <BR>&nbsp; <BR>　　一个或极少不同值 <BR>　　　不应 <BR>　　　不应 <BR>&nbsp; <BR>　　小数目的不同值 <BR>　　　应 <BR>　　　不应 <BR>&nbsp; <BR>　　大数目的不同值 <BR>　　　不应 <BR>　　　应 <BR>&nbsp; <BR>　　频繁更新的列 <BR>　　　不应 <BR>　　　应 <BR>&nbsp; <BR>　　外键列 <BR>　　　应 <BR>　　　应 <BR>&nbsp; <BR>　　主键列 <BR>　　　应 <BR>　　　应 <BR>&nbsp; <BR>　　频繁修改索引列 <BR>　　　不应 <BR>　　　应 <BR>&nbsp; </P>
<P>　　事实上，我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如：返回某范围内的数据一项。比如您的某个表有一个时间列，恰好您把聚合索引建立在了该列，这时您查询2004年1月1日至2004年10月1日之间的全部数据时，这个速度就将是很快的，因为您的这本字典正文是按日期进行排序的，聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可；而不像非聚集索引，必须先查到目录中查到每一项数据对应的页码，然后再根据页码查到具体内容。</P>
<P>（三）结合实际，谈索引使用的误区</P>
<P>　　理论的目的是应用。虽然我们刚才列出了何时应使用聚集索引或非聚集索引，但在实践中以上规则却很容易被忽视或不能根据实际情况进行综合分析。下面我们将根据在实践中遇到的实际问题来谈一下索引使用的误区，以便于大家掌握索引建立的方法。</P>
<P>　　1、主键就是聚集索引</P>
<P>　　这种想法笔者认为是极端错误的，是对聚集索引的一种浪费。虽然SQL SERVER默认是在主键上建立聚集索引的。</P>
<P>　　通常，我们会在每个表中都建立一个ID列，以区分每条数据，并且这个ID列是自动增大的，步长一般为1。我们的这个办公自动化的实例中的列Gid就是如此。此时，如果我们将这个列设为主键，SQL SERVER会将此列默认为聚集索引。这样做有好处，就是可以让您的数据在数据库中按照ID进行物理排序，但笔者认为这样做意义不大。</P>
<P>　　显而易见，聚集索引的优势是很明显的，而每个表中只能有一个聚集索引的规则，这使得聚集索引变得更加珍贵。</P>
<P>　　从我们前面谈到的聚集索引的定义我们可以看出，使用聚集索引的最大好处就是能够根据查询要求，迅速缩小查询范围，避免全表扫描。在实际应用中，因为ID号是自动生成的，我们并不知道每条记录的ID号，所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。其次，让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则；当然，这种情况只是针对用户经常修改记录内容，特别是索引项的时候会负作用，但对于查询速度并没有影响。</P>
<P>　　在办公自动化系统中，无论是系统首页显示的需要用户签收的文件、会议还是用户进行文件查询等任何情况下进行数据查询都离不开字段的是“日期”还有用户本身的“用户名”。</P>
<P>　　通常，办公自动化的首页会显示每个用户尚未签收的文件或会议。虽然我们的where语句可以仅仅限制当前用户尚未签收的情况，但如果您的系统已建立了很长时间，并且数据量很大，那么，每次每个用户打开首页的时候都进行一次全表扫描，这样做意义是不大的，绝大多数的用户1个月前的文件都已经浏览过了，这样做只能徒增数据库的开销而已。事实上，我们完全可以让用户打开系统首页时，数据库仅仅查询这个用户近3个月来未阅览的文件，通过“日期”这个字段来限制表扫描，提高查询速度。如果您的办公自动化系统已经建立的2年，那么您的首页显示速度理论上将是原来速度8倍，甚至更快。</P>
<P>　　在这里之所以提到“理论上”三字，是因为如果您的聚集索引还是盲目地建在ID这个主键上时，您的查询速度是没有这么高的，即使您在“日期”这个字段上建立的索引（非聚合索引）。下面我们就来看一下在1000万条数据量的情况下各种查询的速度表现（3个月内的数据为25万条）：</P>
<P>　　（1）仅在主键上建立聚集索引，并且不划分时间段：</P>
<P>Select gid,fariqi,neibuyonghu,title from tgongwen</P>
<P>　　用时：128470毫秒（即：128秒）</P>
<P>　　（2）在主键上建立聚集索引，在fariq上建立非聚集索引：</P>
<P>select gid,fariqi,neibuyonghu,title from Tgongwen</P>
<P>where fariqi&gt; dateadd(day,-90,getdate())</P>
<P>　　用时：53763毫秒（54秒）</P>
<P>　　（3）将聚合索引建立在日期列（fariqi）上：</P>
<P>select gid,fariqi,neibuyonghu,title from Tgongwen</P>
<P>where fariqi&gt; dateadd(day,-90,getdate())</P>
<P>　　用时：2423毫秒（2秒）</P>
<P>　　虽然每条语句提取出来的都是25万条数据，各种情况的差异却是巨大的，特别是将聚集索引建立在日期列时的差异。事实上，如果您的数据库真的有1000万容量的话，把主键建立在ID列上，就像以上的第1、2种情况，在网页上的表现就是超时，根本就无法显示。这也是我摒弃ID列作为聚集索引的一个最重要的因素。</P>
<P>　　得出以上速度的方法是：在各个select语句前加：declare @d datetime</P>
<P>set @d=getdate()</P>
<P>并在select语句后加：</P>
<P>select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate())</P>
<P>　　2、只要建立索引就能显著提高查询速度</P>
<P>　　事实上，我们可以发现上面的例子中，第2、3条语句完全相同，且建立索引的字段也相同；不同的仅是前者在fariqi字段上建立的是非聚合索引，后者在此字段上建立的是聚合索引，但查询速度却有着天壤之别。所以，并非是在任何字段上简单地建立索引就能提高查询速度。</P>
<P>　　从建表的语句中，我们可以看到这个有着1000万数据的表中fariqi字段有5003个不同记录。在此字段上建立聚合索引是再合适不过了。在现实中，我们每天都会发几个文件，这几个文件的发文日期就相同，这完全符合建立聚集索引要求的：“既不能绝大多数都相同，又不能只有极少数相同”的规则。由此看来，我们建立“适当”的聚合索引对于我们提高查询速度是非常重要的。</P>
<P>　　3、把所有需要提高查询速度的字段都加进聚集索引，以提高查询速度</P>
<P>　　上面已经谈到：在进行数据查询时都离不开字段的是“日期”还有用户本身的“用户名”。既然这两个字段都是如此的重要，我们可以把他们合并起来，建立一个复合索引（compound index）。</P>
<P>　　很多人认为只要把任何字段加进聚集索引，就能提高查询速度，也有人感到迷惑：如果把复合的聚集索引字段分开查询，那么查询速度会减慢吗？带着这个问题，我们来看一下以下的查询速度（结果集都是25万条数据）：（日期列fariqi首先排在复合聚集索引的起始列，用户名neibuyonghu排在后列）</P>
<P>　　（1）select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi&gt;'2004-5-5' </P>
<P>　　查询速度：2513毫秒</P>
<P>　　（2）select gid,fariqi,neibuyonghu,title from Tgongwen where fariqi&gt;'2004-5-5' and neibuyonghu='办公室'</P>
<P>　　查询速度：2516毫秒</P>
<P>　　（3）select gid,fariqi,neibuyonghu,title from Tgongwen where neibuyonghu='办公室'</P>
<P>　　查询速度：60280毫秒</P>
<P>　　从以上试验中，我们可以看到如果仅用聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询速度是几乎一样的，甚至比用上全部的复合索引列还要略快（在查询结果集数目一样的情况下）；而如果仅用复合聚集索引的非起始列作为查询条件的话，这个索引是不起任何作用的。当然，语句1、2的查询速度一样是因为查询的条目数一样，如果复合索引的所有列都用上，而且查询结果少的话，这样就会形成“索引覆盖”，因而性能可以达到最优。同时，请记住：无论您是否经常使用聚合索引的其他列，但其前导列一定要是使用最频繁的列。</P>
<P>（四）其他书上没有的索引使用经验总结</P>
<P>　　1、用聚合索引比用不是聚合索引的主键速度快</P>
<P>　　下面是实例语句：（都是提取25万条数据）</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'</P>
<P>　　使用时间：3326毫秒</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid&lt;=250000</P>
<P>　　使用时间：4470毫秒</P>
<P>　　这里，用聚合索引比用不是聚合索引的主键速度快了近1/4。</P>
<P>　　2、用聚合索引比用一般的主键作order by时速度快，特别是在小数据量情况下</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by fariqi</P>
<P>　　用时：12936</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen order by gid</P>
<P>　　用时：18843</P>
<P>　　这里，用聚合索引比用一般的主键作order by时，速度快了3/10。事实上，如果数据量很小的话，用聚集索引作为排序列要比使用非聚集索引速度快得明显的多；而数据量如果很大的话，如10万以上，则二者的速度差别不明显。</P>
<P>　　3、使用聚合索引内的时间段，搜索时间会按数据占整个数据表的百分比成比例减少，而无论聚合索引使用了多少个</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&gt;'2004-1-1'</P>
<P>　　用时：6343毫秒（提取100万条） </P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&gt;'2004-6-6'</P>
<P>　　用时：3170毫秒（提取50万条）</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16'</P>
<P>　　用时：3326毫秒（和上句的结果一模一样。如果采集的数量一样，那么用大于号和等于号是一样的）</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&gt;'2004-1-1' and fariqi&lt;'2004-6-6'</P>
<P>　　用时：3280毫秒</P>
<P>　　4 、日期列不会因为有分秒的输入而减慢查询速度</P>
<P>　　下面的例子中，共有100万条数据，2004年1月1日以后的数据有50万条，但只有两个不同的日期，日期精确到日；之前有数据50万条，有5000个不同的日期，日期精确到秒。</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&gt;'2004-1-1' order by fariqi</P>
<P>　　用时：6390毫秒</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi&lt;'2004-1-1' order by fariqi</P>
<P>　　用时：6453毫秒</P>
<P>　　（五）其他注意事项</P>
<P>　　“水可载舟，亦可覆舟”，索引也一样。索引有助于提高检索性能，但过多或不当的索引也会导致系统低效。因为用户在表中每加进一个索引，数据库就要做更多的工作。过多的索引甚至会导致索引碎片。</P>
<P>　　所以说，我们要建立一个“适当”的索引体系，特别是对聚合索引的创建，更应精益求精，以使您的数据库能得到高性能的发挥。</P>
<P>　　当然，在实践中，作为一个尽职的数据库管理员，您还要多测试一些方案，找出哪种方案效率最高、最为有效。</P>
<P>二、改善SQL语句</P>
<P>　　很多人不知道SQL语句在SQL SERVER中是如何执行的，他们担心自己所写的SQL语句会被SQL SERVER误解。比如：</P>
<P>select * from table1 where name='zhangsan' and tID &gt; 10000</P>
<P>　　和执行:</P>
<P>select * from table1 where tID &gt; 10000 and name='zhangsan'</P>
<P>　　一些人不知道以上两条语句的执行效率是否一样，因为如果简单的从语句先后上看，这两个语句的确是不一样，如果tID是一个聚合索引，那么后一句仅仅从表的10000条以后的记录中查找就行了；而前一句则要先从全表中查找看有几个name='zhangsan'的，而后再根据限制条件条件tID&gt;10000来提出查询结果。</P>
<P>　　事实上，这样的担心是不必要的。SQL SERVER中有一个“查询分析优化器”，它可以计算出where子句中的搜索条件并确定哪个索引能缩小表扫描的搜索空间，也就是说，它能实现自动优化。</P>
<P>　　虽然查询优化器可以根据where子句自动的进行查询优化，但大家仍然有必要了解一下“查询优化器”的工作原理，如非这样，有时查询优化器就会不按照您的本意进行快速查询。</P>
<P>　　在查询分析阶段，查询优化器查看查询的每个阶段并决定限制需要扫描的数据量是否有用。如果一个阶段可以被用作一个扫描参数（SARG），那么就称之为可优化的，并且可以利用索引快速获得所需数据。</P>
<P>　　SARG的定义：用于限制搜索的一个操作，因为它通常是指一个特定的匹配，一个值得范围内的匹配或者两个以上条件的AND连接。形式如下：</P>
<P>列名 操作符 &lt;常数 或 变量&gt;</P>
<P>或</P>
<P>&lt;常数 或 变量&gt; 操作符列名</P>
<P>　　列名可以出现在操作符的一边，而常数或变量出现在操作符的另一边。如：</P>
<P>Name=’张三’</P>
<P>价格&gt;5000</P>
<P>5000&lt;价格</P>
<P>Name=’张三’ and 价格&gt;5000</P>
<P>　　如果一个表达式不能满足SARG的形式，那它就无法限制搜索的范围了，也就是SQL SERVER必须对每一行都判断它是否满足WHERE子句中的所有条件。所以一个索引对于不满足SARG形式的表达式来说是无用的。</P>
<P>　　介绍完SARG后，我们来总结一下使用SARG以及在实践中遇到的和某些资料上结论不同的经验：</P>
<P>　　1、Like语句是否属于SARG取决于所使用的通配符的类型</P>
<P>　　如：name like ‘张%’ ，这就属于SARG</P>
<P>　　而：name like ‘%张’ ,就不属于SARG。</P>
<P>　　原因是通配符%在字符串的开通使得索引无法使用。</P>
<P>　　2、or 会引起全表扫描</P>
<P>Name=’张三’ and 价格&gt;5000 符号SARG，而：Name=’张三’ or 价格&gt;5000 则不符合SARG。使用or会引起全表扫描。</P>
<P>　　3、非操作符、函数引起的不满足SARG形式的语句</P>
<P>　　不满足SARG形式的语句最典型的情况就是包括非操作符的语句，如：NOT、!=、&lt;&gt;、!&lt;、!&gt;、NOT EXISTS、NOT IN、NOT LIKE等，另外还有函数。下面就是几个不满足SARG形式的例子：</P>
<P>ABS(价格)&lt;5000</P>
<P>Name like ‘%三’</P>
<P>　　有些表达式，如：</P>
<P>WHERE 价格*2&gt;5000</P>
<P>　　SQL SERVER也会认为是SARG，SQL SERVER会将此式转化为：</P>
<P>WHERE 价格&gt;2500/2</P>
<P>　　但我们不推荐这样使用，因为有时SQL SERVER不能保证这种转化与原始表达式是完全等价的。</P>
<P>　　4、IN 的作用相当与OR</P>
<P>　　语句：</P>
<P>Select * from table1 where tid in (2,3)</P>
<P>　　和</P>
<P>Select * from table1 where tid=2 or tid=3</P>
<P>　　是一样的，都会引起全表扫描，如果tid上有索引，其索引也会失效。</P>
<P>　　5、尽量少用NOT</P>
<P>　　6、exists 和 in 的执行效率是一样的</P>
<P>　　很多资料上都显示说，exists要比in的执行效率要高，同时应尽可能的用not exists来代替not in。但事实上，我试验了一下，发现二者无论是前面带不带not，二者之间的执行效率都是一样的。因为涉及子查询，我们试验这次用SQL SERVER自带的pubs数据库。运行前我们可以把SQL SERVER的statistics I/O状态打开。</P>
<P>　　（1）select title,price from titles where title_id in (select title_id from sales where qty&gt;30)</P>
<P>　　该句的执行结果为：</P>
<P>　　表 'sales'。扫描计数 18，逻辑读 56 次，物理读 0 次，预读 0 次。</P>
<P>　　表 'titles'。扫描计数 1，逻辑读 2 次，物理读 0 次，预读 0 次。</P>
<P>　　（2）select title,price from titles where exists (select * from sales where sales.title_id=titles.title_id and qty&gt;30)</P>
<P>　　第二句的执行结果为：</P>
<P>　　表 'sales'。扫描计数 18，逻辑读 56 次，物理读 0 次，预读 0 次。</P>
<P>　　表 'titles'。扫描计数 1，逻辑读 2 次，物理读 0 次，预读 0 次。</P>
<P>　　我们从此可以看到用exists和用in的执行效率是一样的。</P>
<P>　　7、用函数charindex()和前面加通配符%的LIKE执行效率一样</P>
<P>　　前面，我们谈到，如果在LIKE前面加上通配符%，那么将会引起全表扫描，所以其执行效率是低下的。但有的资料介绍说，用函数charindex()来代替LIKE速度会有大的提升，经我试验，发现这种说明也是错误的：</P>
<P>select gid,title,fariqi,reader from tgongwen where charindex('刑侦支队',reader)&gt;0 and fariqi&gt;'2004-5-5'</P>
<P>　　用时：7秒，另外：扫描计数 4，逻辑读 7155 次，物理读 0 次，预读 0 次。</P>
<P>select gid,title,fariqi,reader from tgongwen where reader like '%' + '刑侦支队' + '%' and fariqi&gt;'2004-5-5'</P>
<P>　　用时：7秒，另外：扫描计数 4，逻辑读 7155 次，物理读 0 次，预读 0 次。</P>
<P>　　8、union并不绝对比or的执行效率高</P>
<P>　　我们前面已经谈到了在where子句中使用or会引起全表扫描，一般的，我所见过的资料都是推荐这里用union来代替or。事实证明，这种说法对于大部分都是适用的。</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or gid&gt;9990000</P>
<P>　　用时：68秒。扫描计数 1，逻辑读 404008 次，物理读 283 次，预读 392163 次。</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' </P>
<P>union</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where gid&gt;9990000</P>
<P>　　用时：9秒。扫描计数 8，逻辑读 67489 次，物理读 216 次，预读 7499 次。</P>
<P>　　看来，用union在通常情况下比用or的效率要高的多。</P>
<P>　　但经过试验，笔者发现如果or两边的查询列是一样的话，那么用union则反倒和用or的执行速度差很多，虽然这里union扫描的是索引，而or扫描的是全表。</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' or fariqi='2004-2-5'</P>
<P>　　用时：6423毫秒。扫描计数 2，逻辑读 14726 次，物理读 1 次，预读 7176 次。</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi='2004-9-16' </P>
<P>union</P>
<P>select gid,fariqi,neibuyonghu,reader,title from Tgongwen where&nbsp; fariqi='2004-2-5'</P>
<P>　　用时：11640毫秒。扫描计数 8，逻辑读 14806 次，物理读 108 次，预读 1144 次。</P>
<P>　　9、字段提取要按照“需多少、提多少”的原则，避免“select *”</P>
<P>　　我们来做一个试验：</P>
<P>select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc</P>
<P>　　用时：4673毫秒</P>
<P>select top 10000 gid,fariqi,title from tgongwen order by gid desc</P>
<P>　　用时：1376毫秒</P>
<P>select top 10000 gid,fariqi from tgongwen order by gid desc</P>
<P>　　用时：80毫秒</P>
<P>　　由此看来，我们每少提取一个字段，数据的提取速度就会有相应的提升。提升的速度还要看您舍弃的字段的大小来判断。</P>
<P>　　10、count(*)不比count(字段)慢</P>
<P>　　某些资料上说：用*会统计所有列，显然要比一个世界的列名效率低。这种说法其实是没有根据的。我们来看：</P>
<P>select count(*) from Tgongwen</P>
<P>　　用时：1500毫秒</P>
<P>select count(gid) from Tgongwen </P>
<P>　　用时：1483毫秒</P>
<P>select count(fariqi) from Tgongwen</P>
<P>　　用时：3140毫秒</P>
<P>select count(title) from Tgongwen</P>
<P>　　用时：52050毫秒</P>
<P>　　从以上可以看出，如果用count(*)和用count(主键)的速度是相当的，而count(*)却比其他任何除主键以外的字段汇总速度要快，而且字段越长，汇总的速度就越慢。我想，如果用count(*)， SQL SERVER可能会自动查找最小字段来汇总的。当然，如果您直接写count(主键)将会来的更直接些。</P>
<P>　　11、order by按聚集索引列排序效率最高</P>
<P>　　我们来看：（gid是主键，fariqi是聚合索引列）</P>
<P>select top 10000 gid,fariqi,reader,title from tgongwen</P>
<P>　　用时：196 毫秒。 扫描计数 1，逻辑读 289 次，物理读 1 次，预读 1527 次。</P>
<P>select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc</P>
<P>　　用时：4720毫秒。 扫描计数 1，逻辑读 41956 次，物理读 0 次，预读 1287 次。</P>
<P>select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc</P>
<P>　　用时：4736毫秒。 扫描计数 1，逻辑读 55350 次，物理读 10 次，预读 775 次。</P>
<P>select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc</P>
<P>　　用时：173毫秒。 扫描计数 1，逻辑读 290 次，物理读 0 次，预读 0 次。</P>
<P>select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc</P>
<P>　　用时：156毫秒。 扫描计数 1，逻辑读 289 次，物理读 0 次，预读 0 次。</P>
<P>　　从以上我们可以看出，不排序的速度以及逻辑读次数都是和“order by 聚集索引列” 的速度是相当的，但这些都比“order by 非聚集索引列”的查询速度是快得多的。</P>
<P>　　同时，按照某个字段进行排序的时候，无论是正序还是倒序，速度是基本相当的。</P>
<P>　　12、高效的TOP</P>
<P>　　事实上，在查询和提取超大容量的数据集时，影响数据库响应时间的最大因素不是数据查找，而是物理的I/0操作。如：</P>
<P>select top 10 * from (</P>
<P>select top 10000 gid,fariqi,title from tgongwen</P>
<P>where neibuyonghu='办公室'</P>
<P>order by gid desc) as a</P>
<P>order by gid asc</P>
<P>　　这条语句，从理论上讲，整条语句的执行时间应该比子句的执行时间长，但事实相反。因为，子句执行后返回的是10000条记录，而整条语句仅返回10条语句，所以影响数据库响应时间最大的因素是物理I/O操作。而限制物理I/O操作此处的最有效方法之一就是使用TOP关键词了。TOP关键词是SQL SERVER中经过系统优化过的一个用来提取前几条或前几个百分比数据的词。经笔者在实践中的应用，发现TOP确实很好用，效率也很高。但这个词在另外一个大型数据库ORACLE中却没有，这不能说不是一个遗憾，虽然在ORACLE中可以用其他方法（如：rownumber）来解决。在以后的关于“实现千万级数据的分页显示存储过程”的讨论中，我们就将用到TOP这个关键词。</P>
<P>　　到此为止，我们上面讨论了如何实现从大容量的数据库中快速地查询出您所需要的数据方法。当然，我们介绍的这些方法都是“软”方法，在实践中，我们还要考虑各种“硬”因素，如：网络性能、服务器的性能、操作系统的性能，甚至网卡、交换机等。</P>
<P>三、实现小数据量和海量数据的通用分页显示存储过程</P>
<P>　　建立一个web 应用，分页浏览功能必不可少。这个问题是数据库处理中十分常见的问题。经典的数据分页方法是:ADO 纪录集分页法，也就是利用ADO自带的分页功能（利用游标）来实现分页。但这种分页方法仅适用于较小数据量的情形，因为游标本身有缺点：游标是存放在内存中，很费内存。游标一建立，就将相关的记录锁住，直到取消游标。游标提供了对特定集合中逐行扫描的手段，一般使用游标来逐行遍历数据，根据取出数据条件的不同进行不同的操作。而对于多表和大表中定义的游标（大的数据集合）循环很容易使程序进入一个漫长的等待甚至死机。</P>
<P>　　更重要的是，对于非常大的数据模型而言，分页检索时，如果按照传统的每次都加载整个数据源的方法是非常浪费资源的。现在流行的分页方法一般是检索页面大小的块区的数据，而非检索所有的数据，然后单步执行当前行。</P>
<P>　　最早较好地实现这种根据页面大小和页码来提取数据的方法大概就是“俄罗斯存储过程”。这个存储过程用了游标，由于游标的局限性，所以这个方法并没有得到大家的普遍认可。</P>
<P>　　后来，网上有人改造了此存储过程，下面的存储过程就是结合我们的办公自动化实例写的分页存储过程：</P>
<P>CREATE procedure pagination1</P>
<P>(@pagesize int,&nbsp; --页面大小，如每页存储20条记录</P>
<P>@pageindex int&nbsp;&nbsp; --当前页码</P>
<P>)</P>
<P>as</P>
<P>set nocount on</P>
<P>begin</P>
<P>declare @indextable table(id int identity(1,1),nid int)&nbsp; --定义表变量</P>
<P>declare @PageLowerBound int&nbsp; --定义此页的底码</P>
<P>declare @PageUpperBound int&nbsp; --定义此页的顶码</P>
<P>set @PageLowerBound=(@pageindex-1)*@pagesize</P>
<P>set @PageUpperBound=@PageLowerBound+@pagesize</P>
<P>set rowcount @PageUpperBound</P>
<P>insert into @indextable(nid) select gid from TGongwen where fariqi &gt;dateadd(day,-365,getdate()) order by fariqi desc</P>
<P>select O.gid,O.mid,O.title,O.fadanwei,O.fariqi from TGongwen O,@indextable t where O.gid=t.nid</P>
<P>and t.id&gt;@PageLowerBound and t.id&lt;<A href="mailto:=@PageUpperBound"><FONT color=#003366>=@PageUpperBound</FONT></A> order by t.id </P>
<P>end</P>
<P>set nocount off</P>
<P>　　以上存储过程运用了SQL SERVER的最新技术――表变量。应该说这个存储过程也是一个非常优秀的分页存储过程。当然，在这个过程中，您也可以把其中的表变量写成临时表：CREATE TABLE #Temp。但很明显，在SQL SERVER中，用临时表是没有用表变量快的。所以笔者刚开始使用这个存储过程时，感觉非常的不错，速度也比原来的ADO的好。但后来，我又发现了比此方法更好的方法。</P>
<P>　　笔者曾在网上看到了一篇小短文《从数据表中取出第n条到第m条的记录的方法》，全文如下：</P>
<P>从publish 表中取出第 n 条到第 m 条的记录： <BR>SELECT TOP m-n+1 * <BR>FROM publish <BR>WHERE (id NOT IN <BR>　　　　(SELECT TOP n-1 id <BR>　　　　 FROM publish)) </P>
<P>id 为publish 表的关键字 </P>
<P>　　我当时看到这篇文章的时候，真的是精神为之一振，觉得思路非常得好。等到后来，我在作办公自动化系统（ASP.NET+ C#＋SQL SERVER）的时候，忽然想起了这篇文章，我想如果把这个语句改造一下，这就可能是一个非常好的分页存储过程。于是我就满网上找这篇文章，没想到，文章还没找到，却找到了一篇根据此语句写的一个分页存储过程，这个存储过程也是目前较为流行的一种分页存储过程，我很后悔没有争先把这段文字改造成存储过程：</P>
<P>CREATE PROCEDURE pagination2 <BR>( <BR>&nbsp;@SQL nVARCHAR(4000),&nbsp;&nbsp;&nbsp; --不带排序语句的SQL语句 <BR>&nbsp;@Page int,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --页码 <BR>&nbsp;@RecsPerPage int,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --每页容纳的记录数 <BR>&nbsp;@ID VARCHAR(255),&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --需要排序的不重复的ID号 <BR>&nbsp;@Sort VARCHAR(255)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; --排序字段及规则 <BR>) <BR>AS </P>
<P>DECLARE @Str nVARCHAR(4000)</P>
<P>SET @Str='SELECT&nbsp;&nbsp; TOP '+CAST(@RecsPerPage AS VARCHAR(20))+' * FROM (<A href="mailto:'+@SQL+'"><FONT color=#003366>'+@SQL+'</FONT></A>) T WHERE <A href="mailto:T.'+@ID+'NOT"><FONT color=#003366>T.'+@ID+'NOT</FONT></A> IN <BR>(SELECT&nbsp;&nbsp; TOP '+CAST((@RecsPerPage*(@Page-1)) AS VARCHAR(20))+' <A href="mailto:'+@ID+'"><FONT color=#003366>'+@ID+'</FONT></A> FROM (<A href="mailto:'+@SQL+'"><FONT color=#003366>'+@SQL+'</FONT></A>) T9 ORDER BY <A href="mailto:'+@Sort+'"><FONT color=#003366>'+@Sort+'</FONT></A>) ORDER BY <A href="mailto:'+@Sort"><FONT color=#003366>'+@Sort</FONT></A> </P>
<P>PRINT @Str</P>
<P>EXEC sp_ExecuteSql @Str <BR>GO </P>
<P>　　其实，以上语句可以简化为：</P>
<P>SELECT TOP 页大小 *</P>
<P>FROM Table1</P>
<P>WHERE (ID NOT IN</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (SELECT TOP 页大小*页数 id</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FROM 表</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ORDER BY id))</P>
<P>ORDER BY ID</P>
<P>　　但这个存储过程有一个致命的缺点，就是它含有NOT IN字样。虽然我可以把它改造为：</P>
<P>SELECT TOP 页大小 *</P>
<P>FROM Table1</P>
<P>WHERE not exists</P>
<P>(select * from (select top (页大小*页数) * from table1 order by id) b where b.id=a.id )</P>
<P>order by id</P>
<P>　　即，用not exists来代替not in，但我们前面已经谈过了，二者的执行效率实际上是没有区别的。</P>
<P>　　既便如此，用TOP 结合NOT IN的这个方法还是比用游标要来得快一些。</P>
<P>　　虽然用not exists并不能挽救上个存储过程的效率，但使用SQL SERVER中的TOP关键字却是一个非常明智的选择。因为分页优化的最终目的就是避免产生过大的记录集，而我们在前面也已经提到了TOP的优势，通过TOP 即可实现对数据量的控制。</P>
<P>　　在分页算法中，影响我们查询速度的关键因素有两点：TOP和NOT IN。TOP可以提高我们的查询速度，而NOT IN会减慢我们的查询速度，所以要提高我们整个分页算法的速度，就要彻底改造NOT IN，同其他方法来替代它。</P>
<P>　　我们知道，几乎任何字段，我们都可以通过max(字段)或min(字段)来提取某个字段中的最大或最小值，所以如果这个字段不重复，那么就可以利用这些不重复的字段的max或min作为分水岭，使其成为分页算法中分开每页的参照物。在这里，我们可以用操作符“&gt;”或“&lt;”号来完成这个使命，使查询语句符合SARG形式。如：</P>
<P>Select top 10 * from table1 where id&gt;200</P>
<P>　　于是就有了如下分页方案：</P>
<P>select top 页大小 *</P>
<P>from table1 </P>
<P>where id&gt;</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (select max (id) from </P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (select top ((页码-1)*页大小) id from table1 order by id) as T</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; )&nbsp;&nbsp;&nbsp;&nbsp; </P>
<P>&nbsp; order by id</P>
<P>　　在选择即不重复值，又容易分辨大小的列时，我们通常会选择主键。下表列出了笔者用有着1000万数据的办公自动化系统中的表，在以GID（GID是主键，但并不是聚集索引。）为排序列、提取gid,fariqi,title字段，分别以第1、10、100、500、1000、1万、10万、25万、50万页为例，测试以上三种分页方案的执行速度：（单位：毫秒）</P>
<P>页&nbsp; 码 <BR>&nbsp;方案1 <BR>&nbsp;方案2 <BR>&nbsp;方案3 <BR>&nbsp; <BR>1 <BR>&nbsp;60 <BR>&nbsp;30 <BR>&nbsp;76 <BR>&nbsp; <BR>10 <BR>&nbsp;46 <BR>&nbsp;16 <BR>&nbsp;63 <BR>&nbsp; <BR>100 <BR>&nbsp;1076 <BR>&nbsp;720 <BR>&nbsp;130 <BR>&nbsp; <BR>500 <BR>&nbsp;540 <BR>&nbsp;12943 <BR>&nbsp;83 <BR>&nbsp; <BR>1000 <BR>&nbsp;17110 <BR>&nbsp;470 <BR>&nbsp;250 <BR>&nbsp; <BR>1万 <BR>&nbsp;24796 <BR>&nbsp;4500 <BR>&nbsp;140 <BR>&nbsp; <BR>10万 <BR>&nbsp;38326 <BR>&nbsp;42283 <BR>&nbsp;1553 <BR>&nbsp; <BR>25万 <BR>&nbsp;28140 <BR>&nbsp;128720 <BR>&nbsp;2330 <BR>&nbsp; <BR>50万 <BR>&nbsp;121686 <BR>&nbsp;127846 <BR>&nbsp;7168 <BR>&nbsp; </P>
<P>　　从上表中，我们可以看出，三种存储过程在执行100页以下的分页命令时，都是可以信任的，速度都很好。但第一种方案在执行分页1000页以上后，速度就降了下来。第二种方案大约是在执行分页1万页以上后速度开始降了下来。而第三种方案却始终没有大的降势，后劲仍然很足。</P>
<P>　　在确定了第三种分页方案后，我们可以据此写一个存储过程。大家知道SQL SERVER的存储过程是事先编译好的SQL语句，它的执行效率要比通过WEB页面传来的SQL语句的执行效率要高。下面的存储过程不仅含有分页方案，还会根据页面传来的参数来确定是否进行数据总数统计。</P>
<P>-- 获取指定页的数据</P>
<P>CREATE PROCEDURE pagination3</P>
<P>@tblName&nbsp;&nbsp; varchar(255),&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- 表名</P>
<P>@strGetFields varchar(1000) = '*',&nbsp; -- 需要返回的列 </P>
<P>@fldName varchar(255)='',&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- 排序的字段名</P>
<P>@PageSize&nbsp;&nbsp; int = 10,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- 页尺寸</P>
<P>@PageIndex&nbsp; int = 1,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- 页码</P>
<P>@doCount&nbsp; bit = 0,&nbsp;&nbsp; -- 返回记录总数, 非 0 值则返回</P>
<P>@OrderType bit = 0,&nbsp; -- 设置排序类型, 非 0 值则降序</P>
<P>@strWhere&nbsp; varchar(1500) = ''&nbsp; -- 查询条件 (注意: 不要加 where)</P>
<P>AS</P>
<P>declare @strSQL&nbsp;&nbsp; varchar(5000)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- 主语句</P>
<P>declare @strTmp&nbsp;&nbsp; varchar(110)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- 临时变量</P>
<P>declare @strOrder varchar(400)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- 排序类型</P>
<P>&nbsp;</P>
<P>if @doCount != 0</P>
<P>&nbsp; begin</P>
<P>&nbsp;&nbsp;&nbsp; if @strWhere !=''</P>
<P>&nbsp;&nbsp;&nbsp; set @strSQL = "select count(*) as Total from [" + @tblName + "] where "<A href="mailto:+@strWhere"><FONT color=#003366>+@strWhere</FONT></A> </P>
<P>&nbsp;&nbsp;&nbsp; else</P>
<P>&nbsp;&nbsp;&nbsp; set @strSQL = "select count(*) as Total from [" + @tblName + "]"</P>
<P>end&nbsp; </P>
<P>--以上代码的意思是如果@doCount传递过来的不是0，就执行总数统计。以下的所有代码都是@doCount为0的情况</P>
<P>else</P>
<P>begin</P>
<P>&nbsp;</P>
<P>if @OrderType != 0</P>
<P>begin</P>
<P>&nbsp;&nbsp;&nbsp; set @strTmp = "&lt;(select min"</P>
<P>set @strOrder = " order by [" + @fldName +"] desc"</P>
<P>--如果@OrderType不是0，就执行降序，这句很重要！</P>
<P>end</P>
<P>else</P>
<P>begin</P>
<P>&nbsp;&nbsp;&nbsp; set @strTmp = "&gt;(select max"</P>
<P>&nbsp;&nbsp;&nbsp; set @strOrder = " order by [" + @fldName +"] asc"</P>
<P>end</P>
<P>&nbsp;</P>
<P>if @PageIndex = 1</P>
<P>begin</P>
<P>&nbsp;&nbsp;&nbsp; if @strWhere != ''&nbsp;&nbsp; </P>
<P>&nbsp;&nbsp;&nbsp; set @strSQL = "select top " + str(@PageSize) +" "<A href="mailto:+@strGetFields"><FONT color=#003366>+@strGetFields</FONT></A>+ "&nbsp; from [" + @tblName + "] where " + @strWhere + " " + @strOrder </P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; else</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp; set @strSQL = "select top " + str(@PageSize) +" "<A href="mailto:+@strGetFields"><FONT color=#003366>+@strGetFields</FONT></A>+ "&nbsp; from ["+ @tblName + "] "+ @strOrder </P>
<P>--如果是第一页就执行以上代码，这样会加快执行速度</P>
<P>end</P>
<P>else</P>
<P>begin</P>
<P>--以下代码赋予了@strSQL以真正执行的SQL代码</P>
<P>set @strSQL = "select top " + str(@PageSize) +" "<A href="mailto:+@strGetFields"><FONT color=#003366>+@strGetFields</FONT></A>+ "&nbsp; from [" </P>
<P>&nbsp;&nbsp;&nbsp; + @tblName + "] where [" + @fldName + "]" + @strTmp + "(["+ @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["+ @fldName + "] from [" + @tblName + "]" + @strOrder + ") as tblTmp)"+ @strOrder</P>
<P>&nbsp;</P>
<P>if @strWhere != ''</P>
<P>&nbsp;&nbsp;&nbsp; set @strSQL = "select top " + str(@PageSize) +" "<A href="mailto:+@strGetFields"><FONT color=#003366>+@strGetFields</FONT></A>+ "&nbsp; from [" </P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + @tblName + "] where [" + @fldName + "]" + @strTmp + "(["</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + @fldName + "]) from (select top " + str((@PageIndex-1)*@PageSize) + " ["</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + @fldName + "] from [" + @tblName + "] where " + @strWhere + " "</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + @strOrder + ") as tblTmp) and " + @strWhere + " " + @strOrder</P>
<P>end </P>
<P>end&nbsp;&nbsp; </P>
<P>exec (@strSQL)</P>
<P>GO</P>
<P>　　上面的这个存储过程是一个通用的存储过程，其注释已写在其中了。</P>
<P>　　在大数据量的情况下，特别是在查询最后几页的时候，查询时间一般不会超过9秒；而用其他存储过程，在实践中就会导致超时，所以这个存储过程非常适用于大容量数据库的查询。</P>
<P>　　笔者希望能够通过对以上存储过程的解析，能给大家带来一定的启示，并给工作带来一定的效率提升，同时希望同行提出更优秀的实时数据分页算法。</P>
<P>四、聚集索引的重要性和如何选择聚集索引</P>
<P>　　在上一节的标题中，笔者写的是：实现小数据量和海量数据的通用分页显示存储过程。这是因为在将本存储过程应用于“办公自动化”系统的实践中时，笔者发现这第三种存储过程在小数据量的情况下，有如下现象：</P>
<P>　　1、分页速度一般维持在1秒和3秒之间。</P>
<P>　　2、在查询最后一页时，速度一般为5秒至8秒，哪怕分页总数只有3页或30万页。</P>
<P>　　虽然在超大容量情况下，这个分页的实现过程是很快的，但在分前几页时，这个1－3秒的速度比起第一种甚至没有经过优化的分页方法速度还要慢，借用户的话说就是“还没有ACCESS数据库速度快”，这个认识足以导致用户放弃使用您开发的系统。</P>
<P>　　笔者就此分析了一下，原来产生这种现象的症结是如此的简单，但又如此的重要：排序的字段不是聚集索引！</P>
<P>　　本篇文章的题目是：“查询优化及分页算法方案”。笔者只所以把“查询优化”和“分页算法”这两个联系不是很大的论题放在一起，就是因为二者都需要一个非常重要的东西――聚集索引。</P>
<P>　　在前面的讨论中我们已经提到了，聚集索引有两个最大的优势：</P>
<P>　　1、以最快的速度缩小查询范围。</P>
<P>　　2、以最快的速度进行字段排序。</P>
<P>　　第1条多用在查询优化时，而第2条多用在进行分页时的数据排序。</P>
<P>　　而聚集索引在每个表内又只能建立一个，这使得聚集索引显得更加的重要。聚集索引的挑选可以说是实现“查询优化”和“高效分页”的最关键因素。</P>
<P>　　但要既使聚集索引列既符合查询列的需要，又符合排序列的需要，这通常是一个矛盾。</P>
<P>　　笔者前面“索引”的讨论中，将fariqi，即用户发文日期作为了聚集索引的起始列，日期的精确度为“日”。这种作法的优点，前面已经提到了，在进行划时间段的快速查询中，比用ID主键列有很大的优势。</P>
<P>　　但在分页时，由于这个聚集索引列存在着重复记录，所以无法使用max或min来最为分页的参照物，进而无法实现更为高效的排序。而如果将ID主键列作为聚集索引，那么聚集索引除了用以排序之外，没有任何用处，实际上是浪费了聚集索引这个宝贵的资源。</P>
<P>　　　为解决这个矛盾，笔者后来又添加了一个日期列，其默认值为getdate()。用户在写入记录时，这个列自动写入当时的时间，时间精确到毫秒。即使这样，为了避免可能性很小的重合，还要在此列上创建UNIQUE约束。将此日期列作为聚集索引列。</P>
<P>　　有了这个时间型聚集索引列之后，用户就既可以用这个列查找用户在插入数据时的某个时间段的查询，又可以作为唯一列来实现max或min，成为分页算法的参照物。</P>
<P>　　经过这样的优化，笔者发现，无论是大数据量的情况下还是小数据量的情况下，分页速度一般都是几十毫秒，甚至0毫秒。而用日期段缩小范围的查询速度比原来也没有任何迟钝。</P>
<P>　　聚集索引是如此的重要和珍贵，所以笔者总结了一下，一定要将聚集索引建立在：</P>
<P>　　1、您最频繁使用的、用以缩小查询范围的字段上；</P>
<P>　　2、您最频繁使用的、需要排序的字段上。</P>
<P>　　结束语：</P>
<P>　　希望这篇文章不仅能够给大家的工作带来一定的帮助，也希望能让大家能够体会到分析问题的方法；最重要的是，希望这篇文章能够抛砖引玉，掀起大家的学习和讨论的兴趣，以共同促进。 <BR>　　最后需要说明的是，在试验中，发现用户在进行大数据量查询的时候，对数据库速度影响最大的不是内存大小，而是CPU。在我的P4 2.4机器上试验的时候，查看“资源管理器”，CPU经常出现持续到100%的现象，而内存用量却并没有改变或者说没有大的改变。即使在我们的HP ML 350 G3服务器上试验时，CPU峰值也能达到90%，一般持续在70%左右。 </P>
<P>　　本文的试验数据都是来自我们的HP ML 350服务器。服务器配置：双Inter Xeon 超线程 CPU 2.4G，内存1G，操作系统Windows Server 2003 Enterprise Edition，数据库SQL Server 2000 SP3。</P>
<P>转载完毕.</P>
<P><BR>作者补充: <BR>1.columns in('aa','bb') <BR>他等于columns = 'aa' or columns ='bb' 他先去查询columns ='aa'放在一个临时的空间里,然后等columns ='bb'查询完后,做个or查询得出结果. <BR>至于效率的话，在columns建立索引的话, columns ='aa' or columns ='bb'要来的效率高 <BR>语法分析器会将columns in('aa','bb')转化 <BR>为columns ='aa' or columns ='bb'来执行。我们期望它会根据每个or子句分别查找，再将结果 <BR>相加，这样可以利用columns 上的索引；但实际上（根据showplan）,它却采用了"OR策略" <BR>，即先取出满足每个or子句的行，存入临时数据库的工作表中，再建立唯一索引以去掉 <BR>重复行，最后从这个临时表中计算结果。因此，实际过程没有利用columns 上索引，并且完 <BR>成时间还要受tempdb数据库性能的影响。 </P>
<P><BR>2.效率从高到低 count(1)&gt;count(*)&gt;count([id])&nbsp; </P>
<P>3.select max(cols) from table1 的效率&gt;= select top 1 cols from table1 order by cols desc</P>
<P>4.在where 做并列条件句时,where cols1='aa' and cols2='bb' <BR>如果cols1 ='aa' 占95% cols2占5%的话，把cols2='bb'放在前面 ,因为他在检索cols ='bb'的时候他只需查那5%,然后条件成立的话，去在这5%的纪录里 <BR>去查找cols1 ='aa' </P>
<P>5.避免用if条件句,可以用or来替代. <BR>declare @vsql varchar(200) <BR>set @vsql ='Renaski' <BR>select * from titles where&nbsp; @vsql ='Renaski' or price = 11.9500 </P>
<P>如果@vsql为Renaski则把所有的纪录都选出来,如果不是的话，则只查询price = 11.9500 的纪录.</P>
<P>6.任何对列的操作都将导致表扫描，它包括数据库函数、计算表达式等等，查询时 <BR>要尽可能将操作移至等号右边。 </P>
<P>7.尽量避免使用游标. <BR>如果使用了游标，就要尽量避免在游标循环中再进行表连接的操作 </P>
<P>8.取一个表的纪录数 <BR>Select rows from sysindexes where id=object_id(N'titles') and indid&lt;2 <BR>效率比 <BR>select count(1) from titles来的高. </P>
<P><BR>9.取的一个表的数据信息. <BR>SELECT <BR>表名=case when a.colorder=1 then d.name else '' end, <BR>表说明=case when a.colorder=1 then isnull(f.value,'') else '' end, <BR>字段序号=a.colorder, <BR>字段名=a.name, <BR>标识=case when COLUMNPROPERTY( a.id,a.name,'IsIdentity')=1 then '√'else '' end, <BR>主键=case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in ( <BR>SELECT name FROM sysindexes WHERE indid in( <BR>SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid <BR>))) then '√' else '' end, <BR>类型=b.name, <BR>占用字节数=a.length, <BR>长度=COLUMNPROPERTY(a.id,a.name,'PRECISION'), <BR>小数位数=isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0), <BR>允许空=case when a.isnullable=1 then '√'else '' end, <BR>默认值=isnull(e.text,''), <BR>字段说明=isnull(g.[value],''), <BR>索引名称=isnull(h.索引名称,''), <BR>索引顺序=isnull(h.排序,'') <BR>FROM syscolumns a <BR>left join systypes b on a.xtype=b.xusertype <BR>inner join sysobjects d on a.id=d.id and d.xtype='U' and d.status&gt;=0 <BR>left join syscomments e on a.cdefault=e.id <BR>left join sysproperties g on a.id=g.id and a.colid=g.smallid <BR>left join sysproperties f on d.id=f.id and f.smallid=0 <BR>left join(--这部分是索引信息,如果要显示索引与表及字段的对应关系,可以只要此部分 <BR>select 索引名称=a.name,c.id,d.colid <BR>,排序=case indexkey_property(c.id,b.indid,b.keyno,'isdescending') <BR>when 1 then '降序' when 0 then '升序' end <BR>from sysindexes a <BR>join sysindexkeys b on a.id=b.id and a.indid=b.indid <BR>join (--这里的作用是有多个索引时,取索引号最小的那个 <BR>select id,colid,indid=min(indid) from sysindexkeys <BR>group by id,colid) b1 on b.id=b1.id and b.colid=b1.colid and b.indid=b1.indid <BR>join sysobjects c on b.id=c.id and c.xtype='U' and c.status&gt;=0 <BR>join syscolumns d on b.id=d.id and b.colid=d.colid <BR>where a.indid not in(0,255) <BR>) h on a.id=h.id and a.colid=h.colid <BR>--where d.name='要查询的表' --如果只查询指定表,加上此条件 <BR>order by a.id,a.colorder </P>
<P><BR>10.创建一个表结构. <BR>select * into #b from authors where 1=2; <BR>注意: <BR>#table1 <BR>##table1 <BR>@table1 </P>
<P>局部临时表 <BR>以一个井号（#）开头的那些表名。只有在创建本地临时表的连接上才能看到这些表。 </P>
<P>全局临时表 <BR>以两个井号（##）开头的那些表名。在所有连接上都能看到全局临时表。如果在创建全局临时表的连接断开前没有显式地除去这些表，那么只要所有其它任务停止引用它们，这些表即被除去。当创建全局临时表的连接断开后，新的任务不能再引用它们。当前的语句一执行完，任务与表之间的关联即被除去；因此通常情况下，只要创建全局临时表的连接断开，全局临时表即被除去。 </P>
<P>@和#有和不同：@@在内存，#在硬盘。我的体会是只要方便且数据量不大，使用@@。</P>
<P>11.视图 <BR>他只是记住要连接,关联列的信息,他不存放任何物理数据. <BR>在调用的时候他还是去取各个表中的数据. </P>
<P>12.尽量不要用text属性 <BR>系统为他专门开辟一个空间来存放. <BR>用t-sql/varchar替代 <BR>pl/sql&nbsp; varchar2 替代. </P>
<P>13 <BR>GO语句是个命令识别并通过osql和isql和SQL 查询分析器非T-SQL语句进行识别。 <BR>如果你使用查询分析器作为你的主开发工具，其他语句和库文件将不会识别GO语句作为一个T-SQL命令 </P>
<P>14. <BR>用exec 效率来的高. <BR>declare @sql nvarchar(300) <BR>&nbsp;&nbsp; set @sql='select * from titles' <BR>execute sp_executesql @sql </P>
<P>15,注意你的tempdb,使他自动增长.</P>
<P>16 使用no_log <BR>select * from titles no_logs </P>
<P>17去不重复纪录时,尽量用dictinct</P>
<P>18.尽量避免反复访问同一张或几张表，尤其是数据量较大的表，可以考虑先根据条件提取数据到临时表中，然后再做连接。 </P>
<P><BR>19 尽量使用“&gt;=”，不要使用“&gt;”。 他会找到某个确定的数字进行筛选,而&gt;则没有. </P>
<P>20注意表之间连接的数据类型，避免不同类型数据之间的连接。 </P>
<P>21.可用ASE调优命令：set statistics io on, set statistics time on , set showplan on 等,进行优化</P>
<P>22.truncate table 删除数据 <BR>而不是delete from table </P>
<P><BR>三.死锁 </P>
<P>像SQL server一样的关系数据库使用锁来防止用户“互相踩到对方的脚趾头”。也就是说，锁可以防止用户造成修改数据时的碰撞。当一个用户锁住一段代码时候，其它的用户都不能修改这段数据。另外，一个锁阻止了用户观看未被授权的数据修改。用户必须等待到数据修改并保存之后才能够查看它。数据必须使用不同的方法来加锁。SQL Server 2000使用锁来实现多用户同时修改数据库同一数据时的同步控制 <BR>如果数据量超过200个数据页面（400k），那么系统将会进行锁升级，页级锁会升级成表级锁。 　　 </P>
<P><BR>死锁 <BR>一个数据库的死锁是发生在两个或多于两个访问一些资源的数据库会话中的，并且这些会话相互之间有依赖关系。死锁是可以在任意一个多线程的系统成出现的一个情况，不仅仅局限于关系数据库管理系统。一个多线程系统中的线程可能需要一个或多个资源(例如，锁)。如果申请的资源正在被另外一个线程所使用，那么第一个线程就需要等待持有该资源的线程的释放它所需要的资源。假设等待线程持有一个那个正拥有线程所依赖的资源。下面的这一段代码就可以造成死锁异常现象的发生： <BR>System.Data.SqlClient.SqlException: Transaction (Process ID 12) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction. </P>
<P>当一个SQL Server的调用和另外一个资源发生冲突时就会抛出异常，这个资源持有一个必要的资源。结果是，一个进程就被终止了。当进程的ID号成为系统的唯一标识的时候，这会是一个很平常死锁的消息错误。</P>
<P><BR>锁的类型 <BR>一个数据库系统在许多情况下都有可能锁数据项。其可能性包括： </P>
<P>Rows—数据库表中的一整行 <BR>Pages—行的集合（通常为几kb） <BR>Extents—通常是几个页的集合 <BR>Table—整个数据库表 <BR>Database—被锁的整个数据库表 </P>
<P>除非有其它的说明，数据库根据情况自己选择最好的锁方式。不过值得感谢的是，SQL Server提供了一种避免默认行为的方法。这是由锁提示来完成的。</P>
<P><BR>提示 <BR>或许你有过许多如下的经历：需要重设SQL Server的锁计划，并且加强数据库表中锁范围。Tansact－SQL提供了一系列不同级别的锁提示，你可以在SELECT,INSERT,UPDATE和DELETE中使用它们来告诉SQL Server你需要如何通过重设任何的系统或事务级别来锁表格。可以实现的提示包括： </P>
<P>FASTFIRSTROW—选取结果集中的第一行，并将其优化 <BR>HOLDLOCK—持有一个共享锁直至事务完成 <BR>NOLOCK—不允许使用共享锁或独享锁。这可能会造成数据重写或者没有被确认就返回的情况；因此，就有可能使用到脏数据。这个提示只能在SELECT中使用。 <BR>PAGLOCK—锁表格 <BR>READCOMMITTED—只读取被事务确认的数据。这就是SQL Server的默认行为。 <BR>READPAST—跳过被其它进程锁住的行，所以返回的数据可能会忽略行的内容。这也只能在SELECT中使用。 <BR>READUNCOMMITTED—等价于NOLOCK. <BR>REPEATABLEREAD—在查询语句中，对所有数据使用锁。这可以防止其它的用户更新数据，但是新的行可能被其它的用户插入到数据中，并且被最新访问该数据的用户读取。 <BR>ROWLOCK—按照行的级别来对数据上锁。SQL Server通常锁到页或者表级别来修改行，所以当开发者使用单行的时候，通常要重设这个设置。 <BR>SERIALIZABLE—等价于HOLDLOCK. <BR>TABLOCK—按照表级别上锁。在运行多个有关表级别数据操作的时候，你可能需要使用到这个提示。 <BR>UPDLOCK—当读取一个表的时候，使用更新锁来代替共享锁，并且保持一直拥有这个锁直至事务结束。它的好处是，可以允许你在阅读数据的时候可以不需要锁，并且以最快的速度更新数据。 <BR>XLOCK—给所有的资源都上独享锁，直至事务结束。 </P>
<P>对于数据库死锁，通常可以通过TRACE FLAG 1204、1205、1206，检查ERRORLOG里面的输出，和分析SQLTRACE的执行上下文判断死锁问题的来由。 <BR>TRACEON函数的第三个参数设置为-1，表示不单单针对当前connection，而是针对所有包括未来建立的connection。这样，才够完全，否则只是监视当前已经建立的数据库连接了。 <BR>&nbsp; </P>
<P>执行下面的话可以把死锁记录到Errorlog中：</P>
<P>dbcc traceon (1204, 3605, -1) <BR>go <BR>dbcc tracestatus(-1) <BR>go <BR>&nbsp; <BR>&nbsp;&nbsp; <BR>&nbsp; <BR>得到的输出为： <BR>DBCC 执行完毕。如果 DBCC 输出了错误信息，请与系统管理员联系。 <BR>TraceFlag Status <BR>--------- ------ <BR>1204&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 <BR>1205&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 <BR>3605&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1 </P>
<P>（所影响的行数为 3 行）</P>
<P>DBCC 执行完毕。如果 DBCC 输出了错误信息，请与系统管理员联系。</P>
<P><BR>此后，你可以查看数据库的例行日志，每隔一段时间，数据库都会检查死锁 <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp; ---------------------------------- <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp; Starting deadlock search 1976 </P>
<P>&nbsp;</P>
<P>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp; Target Resource Owner: <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ResType:LockOwner Stype:'OR' Mode: U SPID:55 ECID:0 Ec:(0xAA577570) Value:0x4c25cba0 <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Node:1&nbsp; ResType:LockOwner Stype:'OR' Mode: U SPID:55 ECID:0 Ec:(0xAA577570) Value:0x4c25cba0 <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Node:2&nbsp; ResType:LockOwner Stype:'OR' Mode: U SPID:71 ECID:0 Ec:(0xABF07570) Value:0x9bd0ba00 <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp; <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp; -- next branch -- <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Node:2&nbsp; ResType:LockOwner Stype:'OR' Mode: U SPID:71 ECID:0 Ec:(0xABF07570) Value:0x9bd0ba00 <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp; <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp; <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp; End deadlock search 1976 ... a deadlock was not found. <BR>2004-01-16 18:34:38.50 spid4&nbsp;&nbsp;&nbsp;&nbsp; ---------------------------------- </P>
<P>DBCC TRACEON打开（启用）指定的跟踪标记。 <BR>&nbsp; <BR>注释跟踪标记用于自定义某些控制 Microsoft? SQL Server? 操作方式的特性。跟踪标记在服务器中一直保持启用状态，直到通过执行 DBCC TRACEOFF 语句对其禁用为止。在发出 DBCC TRACEON 语句之前，连入到服务器的新连接看不到任何跟踪标记。一旦发出该语句，该连接就能看到服务器中当前启用的所有跟踪标记（即使这些标记是由其它连接启用）。 <BR>跟踪标记跟踪标记用于临时设置服务器的特定特征或关闭特定行为。如果启动 Microsoft? SQL Server 时设置了跟踪标记 3205，将禁用磁带驱动程序的硬件压缩。跟踪标记经常用于诊断性能问题，或调试存储过程或复杂的计算机系统。 <BR>下列跟踪标记在 SQL Server 中可用。跟踪标记 描述 1204 返回参与死锁的锁的类型以及当前受影响的命令。&nbsp; <BR>实际上可以在“错误 1000 -1999”中找到他们： <BR>1204 19 SQL Server 此时无法获取 LOCK 资源。请在活动用户数较少时重新运行您的语句，或者请求系统管理员检查 SQL Server 锁和内存配置。 <BR>1205 13 事务（进程 ID %1!）与另一个进程已被死锁在资源 {%2!} 上，且该事务已被选作死锁牺牲品。请重新运行该事务。 <BR>1206 18 事务管理器已取消了分布式事务。 </P>
<P>需要指出的是对锁的升级,完全是由系统自行判断的,而非人为.如果要避免死锁的话，其根本还在与数据库的设计上.</P><img src ="http://www.blogjava.net/Titan/aggbug/21744.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/Titan/" target="_blank">Titan</a> 2005-11-28 23:06 <a href="http://www.blogjava.net/Titan/articles/21744.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>