索引初步接触

Posted on 2007-09-11 11:17 久城 阅读(562) 评论(1)  编辑  收藏 所属分类: 数据库学习
一直不理解索引到底是什么东西,把它跟primary key和unique的概念弄得十分模糊。上午查阅了一些资料,刚刚有些开朗的感觉。

primary key和unique都是针对某一列的一种约束。一种限制,看不见摸不着。

primary key表示该列中的数据唯一,且非空。

unique表示该列中的数据唯一,可为空。

而索引并不是一种约束,而是实际存在在物理空间中的。

索引就同论文中的目录(也叫索引)是一样的。创建了目录,能够方便我们快速的找到我们想看的内容(查询速度快的体现),但是,当论文的内容结构有变化的时候,比如说插入了新的一个章节,那么目录的内容也会跟着变化,如果修改比较频繁的话,那么我们可以想象得到,每修改一次内容都要修改一次索引,会比较麻烦。(频繁插入,更新或删除时遇到的问题的体现)

我们在对表中的某一列进行primary key约束或unique约束的时候,oracle服务器会自动的为这个表创建一个索引(唯一索引,具体参考:http://msdn2.microsoft.com/zh-cn/library/ms187019.aspx)。
设置主键之后,当查询时,实际上起作用的是主键索引而并非是主键。索引也分为很多种类型,我平时用不上,没做细研究。只知道索引可以分为聚集索引和非聚集索引。

在MSDN上的资料:

聚集索引
  • 聚集索引根据数据行的键值在表或视图中排序和存储这些数据行。索引定义中包含聚集索引列。每个表只能有一个聚集索引,因为数据行本身只能按一个顺序排序。
  • 只有当表包含聚集索引时,表中的数据行才按排序顺序存储。如果表具有聚集索引,则该表称为聚集表。如果表没有聚集索引,则其数据行存储在一个称为堆的无序结构中。


    非聚集索引

  • 非聚集索引具有独立于数据行的结构。非聚集索引包含非聚集索引键值,并且每个键值项都有指向包含该键值的数据行的指针。
  • 从非聚集索引中的索引行指向数据行的指针称为行定位器。行定位器的结构取决于数据页是存储在堆中还是聚集表中。对于堆,行定位器是指向行的指针。对于聚集表,行定位器是聚集索引键。
  • 在 SQL Server 2005 中,可以向非聚集索引的叶级别添加非键列以跳过现有的索引键限制(900 字节和 16 键列),并执行完整范围内的索引查询。


    聚集索引和非聚集索引都可以是唯一的。这意味着任何两行都不能有相同的索引键值。另外,索引也可以不是唯一的,即多行可以共享同一键值。


    每当修改了表数据后,都会自动维护表或视图的索引。

    由此可见,设计一个良好的索引能够大大提高表的查询速度,但同时也承担着一些风险。

    索引的工作:
    表是平面的,没有层级关系,在进行查询的时候,就是一行一行的扫。而索引实际上是使用了一个复杂的自平衡B+tree结构,有层级关系,所以使用索引查找数据能够快速准确的定位到相关数据,并根据索引上的指针找到对应的数据记录。因此通过索引查询数据比全表扫描快得多。同样在联结多个表时使用索引也可以提高效率。

    进一步理解:
    索引和表之间是相对独立而又关系密切的,简单的说就是索引是将表的部分字段拿出来重新组织排序并为表查询服务。
    所以当一个表在执行insert,update,delete操作的时候,索引也将会发生变化(怎么变化的跟B+树的结构有关,我不大懂,反正会变化,简单的讲绝大部分情况增加一个记录只会影响一个索引块)。所以当对一个表的insert,update,delete操作比较频繁的时候,我们就要考虑索引的使用与维护的问题。如何使其最优,有待研究。

    最后理解:
    过多的索引会降低数据操作的速度,但会提高查询的速度,这需要自己在数据操作和查询中进行权衡。
    如果进行大量insert和delete之后,会影响一些效率,这时候通常要将索引rebuild一下。
    ALTER INDEX <INDEXNAME> REBUILD <TABLESPACENAME>



  • 欢迎来访!^.^!
    本BLOG仅用于个人学习交流!
    目的在于记录个人成长.
    所有文字均属于个人理解.
    如有错误,望多多指教!不胜感激!

    Feedback

    # re: 索引初步接触  回复  更多评论   

    2007-09-11 11:31 by 久城
    在网上查到的传阅比较广泛的一个实例,感觉对理解索引的应用有很大的帮助。

    具体出处不详。

    如何让你的SQL运行得更快

    ---- 人们在使用SQL时往往会陷入一个误区,即太关注于所得的结果是否正确,而忽略了不同的实现方法之间可能存在的性能差异,这种性能差异在大型的或是复杂的数据库环境中(如联机事务处理OLTP或决策支持系统DSS)中表现得尤为明显。笔者在工作实践中发现,不良的SQL往往来自于不恰当的索引设计、不充份的连接条件和不可优化的where子句。在对它们进行适当的优化后,其运行速度有了明显地提高!下面我将从这三个方面分别进行总结:
    ---- 为了更直观地说明问题,所有实例中的SQL运行时间均经过测试,不超过1秒的均表示为(< 1秒)。
    ---- 测试环境--
    ---- 主机:HP LH II
    ---- 主频:330MHZ
    ---- 内存:128兆
    ---- 操作系统:Operserver5.0.4
    ---- 数据库:Sybase11.0.3


    一、不合理的索引设计

    ----例:表record有620000行,试看在不同的索引下,下面几个 SQL的运行情况:
    ---- 1.在date上建有一非个群集索引
    select count(*) from record where date >
    '19991201' and date < '19991214'and amount >
    2000 (25秒)
    select date,sum(amount) from record group by date
    (55秒)
    select count(*) from record where date >
    '19990901' and place in ('BJ','SH') (27秒)
    ---- 分析:
    ----date上有大量的重复值,在非群集索引下,数据在物理上随机存放在数据页上,在范围查找时,必须执行一次表扫描才能找到这一范围内的全部行。

    ---- 2.在date上的一个群集索引
    select count(*) from record where date >
    '19991201' and date < '19991214' and amount >
    2000 (14秒)
    select date,sum(amount) from record group by date
    (28秒)
    select count(*) from record where date >
    '19990901' and place in ('BJ','SH')(14秒)
    ---- 分析:
    ---- 在群集索引下,数据在物理上按顺序在数据页上,重复值也排列在一起,因而在范围查找时,可以先找到这个范围的起末点,且只在这个范围内扫描数据页,避免了大范 围扫描,提高了查询速度。

    ---- 3.在place,date,amount上的组合索引
    select count(*) from record where date >
    '19991201' and date < '19991214' and amount >
    2000 (26秒)
    select date,sum(amount) from record group by date
    (27秒)
    select count(*) from record where date >
    '19990901' and place in ('BJ, 'SH')(< 1秒)
    ---- 分析:
    ---- 这是一个不很合理的组合索引,因为它的前导列是place,第一和第二条SQL没有引用place,因此也没有利用上索引;第三个SQL使用了place,且引用的所有列都包含在组合索引中,形成了索引覆盖,所以它的速度是非常快的。

    ---- 4.在date,place,amount上的组合索引
    select count(*) from record where date >
    '19991201' and date < '19991214' and amount >
    2000(< 1秒)
    select date,sum(amount) from record group by date
    (11秒)
    select count(*) from record where date >
    '19990901' and place in ('BJ','SH')(< 1秒)
    ---- 分析:
    ---- 这是一个合理的组合索引。它将date作为前导列,使每个SQL都可以利用索引,并且在第一和第三个SQL中形成了索引覆盖,因而性能达到了最优。

    ---- 5.总结:
    ---- 缺省情况下建立的索引是非群集索引,但有时它并不是最佳的;合理的索引设计要建立在对各种查询的分析和预测上。一般来说:
    ---- ①.有大量重复值、且经常有范围查询
    (between, >,< ,>=,< =)和order by
    、group by发生的列,可考虑建立群集索引;
    ---- ②.经常同时存取多列,且每列都含有重复值可考虑建立组合索引;
    ---- ③.组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。


    二、不充份的连接条件:

    ---- 例:表card有7896行,在card_no上有一个非聚集索引,表account有191122行,在account_no上有一个非聚集索引,试看在不同的表连接条件下,两个SQL的执行情况:

    select sum(a.amount) from account a,
    card b where a.card_no = b.card_no(20秒)
    ---- 将SQL改为:
    select sum(a.amount) from account a,
    card b where a.card_no = b.card_no and a.
    account_no=b.account_no(< 1秒)
    ---- 分析:
    ---- 在第一个连接条件下,最佳查询方案是将account作外层表,card作内层表,利用card上的索引,其I/O次数可由以下公式估算为:
    ---- 外层表account上的22541页+(外层表account的191122行*内层表card上对应外层表第一行所要查找的3页)=595907次I/O
    ---- 在第二个连接条件下,最佳查询方案是将card作外层表,account作内层表,利用account上的索引,其I/O次数可由以下公式估算为:
    ---- 外层表card上的1944页+(外层表card的7896行*内层表account上对应外层表每一行所要查找的4页)= 33528次I/O
    ---- 可见,只有充份的连接条件,真正的最佳方案才会被执行。
    ---- 总结:
    ---- 1.多表操作在被实际执行前,查询优化器会根据连接条件,列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表;内外表的选择可由公式:外层表中的匹配行数*内层表中每一次查找的次数确定,乘积最小为最佳方案。
    ---- 2.查看执行方案的方法-- 用set showplanon,打开showplan选项,就可以看到连接顺序、使用何种索引的信息;想看更详细的信息,需用sa角色执行dbcc(3604,310,302)。


    三、不可优化的where子句

    ---- 1.例:下列SQL条件语句中的列都建有恰当的索引,但执行速度却非常慢:
    select * from record where
    substring(card_no,1,4)='5378'(13秒)
    select * from record where
    amount/30< 1000(11秒)
    select * from record where
    convert(char(10),date,112)='19991201'(10秒)
    ---- 分析:
    ---- where子句中对列的任何操作结果都是在SQL运行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引;如果这些结果在查询编译时就能得到,那么就可以被SQL优化器优化,使用索引,避免表搜索,因此将SQL重写成下面这样:
    select * from record where card_no like
    '5378%'(< 1秒)
    select * from record where amount
    < 1000*30(< 1秒)
    select * from record where date= '1999/12/01'
    (< 1秒)
    ---- 你会发现SQL明显快起来!

    ---- 2.例:表stuff有200000行,id_no上有非群集索引,请看下面这个SQL:
    select count(*) from stuff where id_no in('0','1')
    (23秒)
    ---- 分析:
    ---- where条件中的'in'在逻辑上相当于'or',所以语法分析器会将in ('0','1')转化为id_no ='0' or id_no='1'来执行。我们期望它会根据每个or子句分别查找,再将结果相加,这样可以利用id_no上的索引;但实际上(根据showplan),它却采用了"OR策略",即先取出满足每个or子句的行,存入临时数据库的工作表中,再建立唯一索引以去掉重复行,最后从这个临时表中计算结果。因此,实际过程没有利用id_no上索引,并且完成时间还要受tempdb数据库性能的影响。
    ---- 实践证明,表的行数越多,工作表的性能就越差,当stuff有620000行时,执行时间竟达到220秒!还不如将or子句分开:
    select count(*) from stuff where id_no='0'
    select count(*) from stuff where id_no='1'
    ---- 得到两个结果,再作一次加法合算。因为每句都使用了索引,执行时间只有3秒,在620000行下,时间也只有4秒。或者,用更好的方法,写一个简单的存储过程:
    create proc count_stuff as
    declare @a int
    declare @b int
    declare @c int
    declare @d char(10)
    begin
    select @a=count(*) from stuff where id_no='0'
    select @b=count(*) from stuff where id_no='1'
    end
    select @c=@a+@b
    select @d=convert(char(10),@c)
    print @d

    ---- 直接算出结果,执行时间同上面一样快!
    ---- 总结:
    ---- 可见,所谓优化即where子句利用了索引,不可优化即发生了表扫描或额外开销。

    ---- 1.任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。
    ---- 2.in、or子句常会使用工作表,使索引失效;如果不产生大量重复值,可以考虑把子句拆开;拆开的子句中应该包含索引。
    ---- 3.要善于使用存储过程,它使SQL变得更加灵活和高效。
    ---- 从以上这些例子可以看出,SQL优化的实质就是在结果正确的前提下,用优化器可以识别的语句,充份利用索引,减少表扫描的I/O次数,尽量避免表搜索的发生。其实SQL的性能优化是一个复杂的过程,上述这些只是在应用层次的一种体现,深入研究还会涉及数据库层的资源配置、网络层的流量控制以及操作系统层的总体设计。

    1 逻辑数据库和表的设计
      数据库的逻辑设计、包括表与表之间的关系是优化关系型数据库性能的核心。一个好的逻辑数据库设计可以为优化数据库和应用程序打下良好的基础。

      标准化的数据库逻辑设计包括用多的、有相互关系的窄表来代替很多列的长数据表。下面是一些使用标准化表的一些好处。

    A:由于表窄,因此可以使排序和建立索引更为迅速
    B:由于多表,所以多镞的索引成为可能
    C:更窄更紧凑的索引
    D:每个表中可以有少一些的索引,因此可以提高insert update delete等的速度,因为这些操作在索引多的情况下会对系统性能产生很大的影响
    E:更少的空值和更少的多余值,增加了数据库的紧凑性由于标准化,所以会增加了在获取数据时引用表的数目和其间的连接关系的复杂性。太多的表和复杂的连接关系会降低服务器的性能,因此在这两者之间需要综合考虑。
      定义具有相关关系的主键和外来键时应该注意的事项主要是:用于连接多表的主键和参考的键要有相同的数据类型。

      2 索引的设计
    A:尽量避免表扫描
    检查你的查询语句的where子句,因为这是优化器重要关注的地方。包含在where里面的每一列(column)都是可能的侯选索引,为能达到最优的性能,考虑在下面给出的例子:对于在where子句中给出了column1这个列。
    下面的两个条件可以提高索引的优化查询性能!
    第一:在表中的column1列上有一个单索引
    第二:在表中有多索引,但是column1是第一个索引的列
    避免定义多索引而column1是第二个或后面的索引,这样的索引不能优化服务器性能
    例如:下面的例子用了pubs数据库。
    SELECT au_id, au_lname, au_fname FROM authors
    WHERE au_lname = ’White’
    按下面几个列上建立的索引将会是对优化器有用的索引
    ?au_lname
    ?au_lname, au_fname
    而在下面几个列上建立的索引将不会对优化器起到好的作用
    ?au_address
    ?au_fname, au_lname
    考虑使用窄的索引在一个或两个列上,窄索引比多索引和复合索引更能有效。用窄的索引,在每一页上将会有更多的行和更少的索引级别(相对与多索引和复合索引而言),这将推进系统性能。
    对于多列索引,SQL Server维持一个在所有列的索引上的密度统计(用于联合)和在第一个索引上的histogram(柱状图)统计。根据统计结果,如果在复合索引上的第一个索引很少被选择使用,那么优化器对很多查询请求将不会使用索引。
    有用的索引会提高select语句的性能,包括insert,uodate,delete。
    但是,由于改变一个表的内容,将会影响索引。每一个insert,update,delete语句将会使性能下降一些。实验表明,不要在一个单表上用大量的索引,不要在共享的列上(指在多表中用了参考约束)使用重叠的索引。
    在某一列上检查唯一的数据的个数,比较它与表中数据的行数做一个比较。这就是数据的选择性,这比较结果将会帮助你决定是否将某一列作为侯选的索引列,如果需要,建哪一种索引。你可以用下面的查询语句返回某一列的不同值的数目。
    select count(distinct cloumn_name) from table_name
    假设column_name是一个10000行的表,则看column_name返回值来决定是否应该使用,及应该使用什么索引。
    Unique values Index

    5000 Nonclustered index
    20 Clustered index
    3 No index

    镞索引和非镞索引的选择

    <1:>镞索引是行的物理顺序和索引的顺序是一致的。页级,低层等索引的各个级别上都包含实际的数据页。一个表只能是有一个镞索引。由于update,delete语句要求相对多一些的读操作,因此镞索引常常能加速这样的操作。在至少有一个索引的表中,你应该有一个镞索引。
    在下面的几个情况下,你可以考虑用镞索引:
    例如: 某列包括的不同值的个数是有限的(但是不是极少的)
    顾客表的州名列有50个左右的不同州名的缩写值,可以使用镞索引。
    例如: 对返回一定范围内值的列可以使用镞索引,比如用between,>,>=,<,<=等等来对列进行操作的列上。
    select * from sales where ord_date between ’5/1/93’ and ’6/1/93’
    例如: 对查询时返回大量结果的列可以使用镞索引。
    SELECT * FROM phonebook WHERE last_name = ’Smith’

    当有大量的行正在被插入表中时,要避免在本表一个自然增长(例如,identity列)的列上建立镞索引。如果你建立了镞的索引,那么insert的性能就会大大降低。因为每一个插入的行必须到表的最后,表的最后一个数据页。
    当一个数据正在被插入(这时这个数据页是被锁定的),所有的其他插入行必须等待直到当前的插入已经结束。
    一个索引的叶级页中包括实际的数据页,并且在硬盘上的数据页的次序是跟镞索引的逻辑次序一样的。

    <2:>一个非镞的索引就是行的物理次序与索引的次序是不同的。一个非镞索引的叶级包含了指向行数据页的指针。
    在一个表中可以有多个非镞索引,你可以在以下几个情况下考虑使用非镞索引。
    在有很多不同值的列上可以考虑使用非镞索引
    例如:一个part_id列在一个part表中
    select * from employee where emp_id = ’pcm9809f’
    查询语句中用order by 子句的列上可以考虑使用镞索引

    3 查询语句的设计

    SQL Server优化器通过分析查询语句,自动对查询进行优化并决定最有效的执行方案。优化器分析查询语句来决定那个子句可以被优化,并针对可以被优化查询的子句来选择有用的索引。最后优化器比较所有可能的执行方案并选择最有效的一个方案出来。
    在执行一个查询时,用一个where子句来限制必须处理的行数,除非完全需要,否则应该避免在一个表中无限制地读并处理所有的行。
    例如下面的例子,
    select qty from sales where stor_id=7131
    是很有效的比下面这个无限制的查询
    select qty from sales
    避免给客户的最后数据选择返回大量的结果集。允许SQL Server运行满足它目的的函数限制结果集的大小是更有效的。
    这能减少网络I/O并能提高多用户的相关并发时的应用程序性能。因为优化器关注的焦点就是where子句的查询,以利用有用的索引。在表中的每一个索引都可能成为包括在where子句中的侯选索引。为了最好的性能可以遵照下面的用于一个给定列column1的索引。
    第一:在表中的column1列上有一个单索引
    第二:在表中有多索引,但是column1是第一个索引的列不要在where子句中使用没有column1列索引的查询语句,并避免在where子句用一个多索引的非第一个索引的索引。
    这时多索引是没有用的。
    For example, given a multicolumn index on the au_lname, au_fname columns of the authors table in
    the pubs database,
    下面这个query语句利用了au_lname上的索引
    SELECT au_id, au_lname, au_fname FROM authors
    WHERE au_lname = ’White’
    AND au_fname = ’Johnson’
    SELECT au_id, au_lname, au_fname FROM authors
    WHERE au_lname = ’White’
    下面这个查询没有利用索引,因为他使用了多索引的非第一个索引的索引
    SELECT au_id, au_lname, au_fname FROM authors
    WHERE au_fname = ’Johnson’


    只有注册用户登录后才能发表评论。


    网站导航:
     

    Copyright © 久城