数据库索引:藏在数据深处的那本“目录大全“
请你设想这样一个场景。你手里捧着一本厚达上千页、收录了几万个词条的大词典现在你想查一个特定的词比如数据库这个词的释义。你会怎么做我想没有人会傻到从第一页开始一页一页地往后翻直到撞见这个词为止。那样的话运气不好可能要翻上大半天累得手酸眼花。聪明的做法是翻到词典最前面或最后面的目录或索引页那里按照拼音或笔画的顺序整整齐齐地列着每个词以及它所在的页码。你只需在目录里飞快地定位到数据库看一眼后面标注的页码然后直接翻到那一页几秒钟就大功告成。这本词典里的目录就是我们日常生活中最常见的索引。它本身并不是词典的正文内容却能让我们在浩如烟海的词条中以惊人的速度找到目标。数据库的世界里同样面临着大海捞针般的查找难题。一张表里可能存放着成千上万、甚至上亿条记录当我们想从中找出某几条特定的数据时如果没有任何辅助手段数据库就只能像那个笨办法一样从头到尾一条条地翻查慢得让人无法忍受。而解决这个难题的利器正是数据库中一项极为重要的高级特性它的名字恰恰也叫索引。今天我们就来生动形象地认识这位藏在数据深处的查找高手看一看数据库索引这本目录大全究竟有着怎样的神奇本领与精妙门道。一、没有索引的烦恼大海捞针的全表扫描要体会索引的好处我们得先想象一下没有索引时的窘境。假设有一张员工表里面密密麻麻地存放着一家大公司十万名员工的信息包括员工编号、姓名、部门、薪水等等。现在经理想查一查名叫张三的这位员工的资料。在没有索引的情况下数据库会怎么干呢它别无他法只能采取一种最原始、最笨拙的方式那就是从员工表的第一条记录开始一条一条地往下看看看这一条的姓名是不是张三不是就看下一条再不是就再看下一条如此一直查下去直到找到张三或者把全部十万条记录都翻遍为止。这种把整张表从头到尾彻底翻查一遍的方式在数据库里有一个专门的名字叫做全表扫描。全表扫描的效率有多低是显而易见的。如果张三恰好排在第一条那算他运气好一下就找到了。可如果张三排在第九万九千条或者更糟表里压根就没有张三这个人那数据库就得老老实实地把十万条记录全部看一遍才能给出结果。这就好比在那本厚词典里不用目录从第一页死磕到最后一页。当表里的数据量越来越大时全表扫描的耗时会成正比地急剧增长十万条要查十万次一百万条就要查一百万次这种查找方式简直就是大海捞针慢得令人绝望。而且现实中的查询远比这复杂。一个繁忙的系统每秒钟可能要响应成百上千次查询如果每一次都要靠全表扫描去大海捞针那数据库非被活活拖垮不可。可见全表扫描这种笨办法在面对海量数据时是完全行不通的。我们迫切需要一种聪明的机制能让数据库像我们查词典翻目录一样快速地定位到想要的数据而不必每次都把整张表翻个底朝天。这种机制正是索引。二、索引是什么为数据单独建立的一份目录那么索引到底是什么呢我们可以这样来理解索引是数据库为表中的某一列或某几列单独建立起来的一份排好序的目录。我们继续用词典来打比方。词典的正文是按照词条收录的先后或某种内在逻辑排列的并不便于查找。而词典的目录或索引页则是把所有的词按照拼音顺序整整齐齐地排好每个词后面跟着它在正文里的页码。数据库的索引也是同样的道理。表里的数据本身可能是杂乱无序地存放着的而索引则把我们关心的那一列比如员工姓名单独取出来按照一定的顺序排列好并且在每个姓名旁边记下这条记录在表里的实际存放位置。有了这样一份排好序的姓名索引再查张三就大不一样了。数据库不再去翻那张杂乱的员工表而是先到这份有序的姓名索引里去查。因为索引是排好序的数据库可以用非常高效的方式比如类似查字典时先翻到张字开头的位置那样飞快地在索引里定位到张三然后顺着张三旁边记录的位置信息一步就跳到员工表里张三那条记录的确切位置把完整资料取出来。整个过程根本不需要扫描整张表速度自然快得惊人。所以索引的本质就是用一份额外的、有序的目录来换取查找速度的飞跃。它就像我们给一座杂乱的大仓库编制了一份详尽的物品存放清单清单按物品名称排好序并注明了每样物品在仓库哪个货架哪个位置。今后要取东西先查清单再按图索骥又快又准再也不用在仓库里东翻西找了。索引正是数据库为飞速查找数据而精心准备的那本目录大全。三、索引的奥秘B树的精妙结构了解了索引是一份有序目录之后我们不禁要问数据库究竟是用什么样的结构来组织这份目录的才能做到如此高效的查找呢这就涉及索引背后的精妙数据结构了。最主流、应用最广泛的索引结构是一种叫做B树的结构。我们不必深究它复杂的算法细节只需形象地理解它的精妙之处即可。B树是一种树状的、多层级的结构。我们可以把它想象成一棵倒过来生长的大树根在上面枝叶在下面分成好多层。最顶上是一个根节点它好比一本大书的总目录把数据按范围粗略地分成几大块比如告诉你姓张的大概在某个区域、姓李的在另一个区域。往下一层是中间节点它好比每一章的分目录把范围分得更细一些。最底下一层是叶子节点它好比每一节的详细目录存放着具体的、排好序的索引值以及对应数据的位置。当我们要查找一个数据时从根节点出发根据查找的值决定该往哪一个分支走到了中间节点再进一步判断方向层层向下最终精准地落到叶子节点上某个具体的位置。这个过程就好比查找一本书的内容先看总目录确定在第几篇再看篇目录确定在第几章再看章目录确定在第几节一步步缩小范围最后直达目标页。B树结构的精妙之处在于它非常的扁平。哪怕表里有上千万、上亿条数据这棵树的层数往往也只有寥寥几层。这意味着无论数据量多么庞大从根节点查到叶子节点只需要经过区区几步就能定位到目标。这与全表扫描动辄要查几百万次形成了天壤之别。正是这种层级分明、逐层缩小范围的结构让索引能够在海量数据中实现风驰电掣般的查找。除了B树还有一种叫哈希索引的结构它通过一种特殊的换算能够近乎一步到位地定位数据速度极快但它有个局限只适合精确的等值查找不擅长范围查找。而B树则既能做等值查找又能高效地处理范围查找比如查找薪水在五千到八千之间的所有员工所以B树成为了最为通用和主流的索引结构。四、索引的代价天下没有免费的午餐读到这里你可能会想索引这么神奇能让查找快得飞起那我们干脆给表里的每一列都建上索引岂不是查什么都快了且慢这里有一个非常重要的道理需要明白那就是天下没有免费的午餐索引虽好却也是有代价的绝不能滥用。索引的第一个代价是占用额外的存储空间。索引本身是一份独立于数据的目录它需要实实在在地存放在磁盘上是要占地方的。给一列建索引就要为这一列存一份排好序的目录。如果不加节制地给很多列都建索引那么索引所占用的空间甚至可能超过数据本身造成存储的巨大浪费。这就好比一本薄薄的小册子你却非要给它配上一本厚厚的目录大全本末倒置得不偿失。索引的第二个代价也是更关键的代价是会拖慢数据的增删改操作。我们想想看索引是一份和数据保持同步的有序目录。当我们往表里插入一条新数据时不仅要把数据本身存进去还要相应地把这条数据的信息按顺序插入到每一个相关的索引目录里以保持目录的有序和完整。同样当我们删除或修改数据时所有相关的索引也都得跟着更新。这就好比你每往那座大仓库里新放一件货物或者搬走一件货物都得把那份物品清单也相应地修改一遍重新排好序。如果只有一份清单还好可要是有十份清单那每动一件货物就得改十份清单工作量陡然倍增。表上的索引越多增删改时需要同步维护的目录就越多这些操作就被拖得越慢。所以索引是一把典型的双刃剑。它显著地加快了查询的速度却以占用空间和拖慢增删改为代价。这就要求我们在使用索引时必须审慎地权衡把好钢用在刀刃上而不能一味地贪多求全。五、用好索引把好钢用在刀刃上既然索引有利有弊那么我们究竟该如何用好它做到趋利避害呢这里面是有一些门道和讲究的核心原则就是把索引建在最该建的地方。首先应该在那些经常用作查询条件的列上建立索引。一张表里总有那么几列是我们查询时最常用来作为筛选条件的。比如员工表里的姓名、部门订单表里的顾客编号、下单日期。这些列被频繁地用在查找的条件中给它们建上索引就能让最常见的查询飞快起来收益最大。反过来那些几乎从不用来作为查询条件的列比如员工的备注信息就完全没必要建索引建了也是白白浪费空间、拖慢更新。其次应该在那些取值比较多样、区分度高的列上建索引。所谓区分度高就是这一列的值五花八门、各不相同比如员工编号每个人都不一样。在这样的列上建索引能够非常精准地把目标定位到极少数几条记录索引的威力得到充分发挥。相反如果一列的取值非常单调比如性别这一列翻来覆去就是男和女两种值那么在它上面建索引意义就不大因为通过它筛选往往还是会剩下表里一大半的记录索引帮不上多少忙。再次要避免给那些频繁进行增删改、却很少查询的表或列建立过多的索引。因为对这类对象而言索引带来的查询收益有限反而会让频繁的增删改操作背上沉重的维护负担得不偿失。此外主键通常会自动建立索引因为主键是用来唯一标识每条记录的查找极为频繁自动建索引顺理成章。总而言之用好索引的精髓就在于深入了解一张表平时究竟是怎么被使用的查询多还是更新多常按哪些列查找然后有的放矢地、恰到好处地建立索引既让常见查询快如闪电又不给系统添加过多的负担。这种因地制宜、恰到好处的拿捏正是数据库优化中一门重要的功夫。六、结语行文至此我们以查词典翻目录作喻生动而完整地认识了数据库索引这本藏在数据深处的目录大全。我们先体会了没有索引时全表扫描那种大海捞针的烦恼明白了海量数据下逐条翻查是何等低效接着理解了索引的本质它是数据库为某些列单独建立的一份排好序的目录能让查找像翻词典一样飞快然后我们领略了索引背后B树那层级分明、逐层缩小范围的精妙结构正是它让海量数据中的查找只需寥寥几步即可完成我们也清醒地认识到索引并非免费的午餐它要占用空间、要拖慢增删改是一把双刃剑最后我们探讨了用好索引的门道懂得了要把索引建在那些常用作查询条件、区分度又高的列上做到把好钢用在刀刃上。回顾这一番对索引的探寻我们能够深切地体会到索引所体现的是一种以空间换时间、以局部冗余换整体效率的工程智慧。它甘愿额外付出一些存储空间甘愿在增删改时多做一些维护工作为的就是换取查询时那石破天惊般的速度飞跃。在绝大多数应用场景里查询的次数远远多于增删改这种取舍无疑是极为划算的。这正是索引能够成为数据库性能优化第一利器的根本原因。更值得我们玩味的是索引的使用之道处处透着一种权衡与适度的辩证智慧。它好但不能滥用它快但有代价。用得恰到好处它就是查询的神兵利器让系统快如疾风用得过滥失当它反而成了拖累让系统不堪重负。这种凡事讲究分寸、追求恰到好处的思维不仅适用于索引更贯穿于整个数据库乃至一切工程技术的精髓之中。当我们今天能够透彻地理解索引的原理、洞悉它的利弊并能根据实际情况明智地决定该不该建、在哪儿建、建几个时我们便真正掌握了这门提升数据库性能的关键功夫。希望大家把索引的这份智慧牢记于心在今后与数据打交道的日子里善用这本神奇的目录大全让自己经手的每一次查询都能在浩瀚的数据海洋中如同翻开词典的目录一般从容不迫、风驰电掣地直达那个想要的答案。