赞
踩
上图是一条sql进入mysql经历的大体流程,这里选用的引擎是innodb,innodb引擎支持事物且对update语句是默认开启事物的。
一条sql被mysql执行,要经历一系列流程,示意图如上所示。
mysql作为数据库,需要频繁的读取,对一条sql来说,mysql希望它的查询速度尽可能的快。数据库中的数据为了持久化,最终会被写到磁盘上,这也就导致,磁盘I/O成了数据读取最大的耗时,如果能在读取一条数据时,进行最少的磁盘I/O,就是mysql数据结构选型的核心。mysql最终选择了B+Tree的存储结构。
数据库存储数据到磁盘后,一般来说,是无序的。对于一张表的每条数据,按照主键在逻辑上是顺序的,但每一条数据但插入时间不定,也就导致存储在磁盘时但位置,就不再有序了,那么每读取一条数据都要进行一次寻址,效率无法满足我们但需求。
mysql在这里引入了页的概念,它也是每次读取但最小数据量(默认为16kb)。mysql每次开辟磁盘时,会占用一页的空间,所以每一页内的数据在磁盘上自然是连续的。
B-Tree:
上图是B-Tree的数据机构概念图,数据的存储以key-value的形式,在节点的两侧会存储下层节点的内存地址。假设使用3层树结构,存储表中一行数据需要占用1kb的空间(因为内存地址占用的空间一般为6bit,相对与1kb很小,就不计算了),那么一个节点在mysql数据库中能存储16个数据(这里一个节点对应mysql的一页数据,16kb)。那么,3层数结构满数据能存储16×16×16个数据。
B+Tree:
上图是B+Tree的数据结构概念图,他是B-Tree的变种。除去叶子节点外,其他节点不再存储数据,而且这些节点内的数据都是冗余的,他们都可以在叶子节点内找到。这种树形结构虽然会占用更多的磁盘空间(冗余数据的出现),但相较于B-Tree来说,可以存储更多但数据。同样但以3层树为例,假设key使用的是bigint类型,占用8byte,内存地址存储为占用6byte,那么非叶子结点存储一个数据需要占用14byte的磁盘空间,一个节点约可存储1170个数据。3层树结构可存储:1170×1170×16个数据,明显多余B-Tree。
对于mysql来说,3层树结构意味着,在B-Tree中检索一个数据,只需要进行3次磁盘I/O,而B+Tree能存储更多的数据,B+Tree是优于B-Tree的。那么其他树结构与之相比,树高就成了最大的问题(树高对应磁盘I/O的最大次数)。
mysql支持多种不同的引擎,从sql执行流程图概念图中可以看到,各种引擎是执行器调用的,他们更像是各种不同类型的插槽工具,执行器将sql交给它,它将结果返回。
引擎是建立在数据表上的,也就是说,一个库中,可以包含多张使用了不同引擎的表。
innodb是目前使用最多的,它要求每一张表都必须拥有一个主键,因为它的所有数据都维护在一个以主键ID为key值的B+Tree中,这里,将这样的存储了所有表数据的结构,称为聚簇索引。innodb是支持事物的,也正是因此,它的锁粒度是行锁级别的。
mysql建立索引时,是将索引字段作为key,对应的主键id作为value,创建一颗新的B+Tree。所以过多的索引会导致数据量极度膨胀,增删改的速度明显下降,因为它要维护更多的索引树的数据。因此,一张表不应该建立多个单独的索引,而是尽可能的只使用2-3个联合索引(多个列组成的索引),让他们包含80%的索引需求。如果是使用唯一索引,不在此概念内。因为维护数据的准确要更为重要。
聚簇索引就是主键索引,它包含了一张表中的所有数据。常说的全表扫描就是指的检索聚簇索引,但不同但是,在mysql中,B+Tree但叶子节点,也就是数据但存储位,在手段和末端,会存储下一个叶子节点但内存地址(按key的顺序排列)。所以全表扫描并不会从跟节点起始,而是从叶子节点的最左侧(主键最小值)开始遍历,其他索引也是同理。
上图是联合索引的概念图,它是3个列字段联合创建的索引,这里假设他们分别是(age,color_id,class_id)。索引存储方式是,分别按找索引字段的顺序,也就是先排序age,在age相同的情况下,排序color_id,在color_id相同的情况下,最后排列class_id。这意味着,只有在前一字段相同的情况下,后一字段才是有序的,也就是最左匹配原则。
除去主键索引/聚簇索引外,其他的索引被称为二级索引或辅助索引,它们相比主键索引要小的多,因为它们只存储了索引字段与主键,而主键索引中存储了所有的数据。因此,使用二级索引可以使检索一般可以速度更快,但也要看sql中需求但数据情况,如果索引中但数据不足以满足sql但需求,就需要通过主键回到主键索引中拿去其他数据,这被称为回表,过多但回表可能导致使用二级索引效率并不如单独遍历主键索引。如果一次sql,他所有需求但数据,都能在二级索引中获得,就省去了回表但过程,效率就极大但提升了,这被称为覆盖索引。
一个优秀的索引可以极大的加快mysql的检索速度,对于一个复杂但sql来说,它在执行但时候会被分解为多个简单sql,使用explain关键字,可以方便但看到这些分解但sql语句但执行方式,使用了什么索引等等情况。下面记录了explian返回但字段信息:
mysql在执行sql前,会经历优化阶段,例如使用覆盖索引时,索引顺序写错了,mysql可以智能的调整顺序。但所有可以通过代码优化但sql,都不要浪费mysql的资源。优化阶段,mysql会自行判断对每一条sql(可能会拆解负载sql)的索引使用,它拥有自己的判断机制,会计算sql的预估 cost,通过cost值,大概的判断是使用那一条索引,或者干脆全表扫描。在sql优化阶段,mysql是没有运行sql语句的,得到的所有结果都是一个预估值(基本准确),可以通过trace。
在使用explain的时候,可以同步运行 show warmings;它会返回一条sql最终运行的 类sql语句,通过它,可以分析出sql语句最终执行的,到底是什么。
tarce工具:trace工具会影响mysql性能,生产环境不要开启。tarce工具可以反馈出sql语句执行前,mysql对其进行的优化执行步骤,可以看到cost的计算结果。
声明:sql的优化与它的存储方式息息相关,下面记录的优化策略都是基于数据模型得来的。优化策略并不是绝对的,只是在大多数情况下适用,具体方案要依照explain结果具体分析。
原则上,要避免使用连表。除特殊业务,都应尽可能的使用单表查询。对于关联比较大的表,可以使用冗余字段存储必要的数据,如果不得不使用连表,连表数量也不要高于3张。mysql连表主要使用两种方式:
nested loop join 嵌套循环连接 NLJ:如上图所示,嵌套循环连接的流程,它主要用于有索引的表连接。首先会遍历小表的索引树,将获取的每一条数据到B表的索引中检索。A表的每一条数据,在B中只需要检索一条数据就能得到(检索过程可能经过几次I/O,这里认为的是检索目标需要遍历的行数)。那么它的耗费是:100 + 100。如果没有索引的,那么耗费立即膨胀到:100 × 10000。
block nested loop join 基于块的嵌套循环连接 BNL:如上图所示,它主要用于没有索引的表连接。小表中的数据会写入jion buffer中,如果无法一次写入所有数据,那么会分批多次写入。每次写完成后,用jion buffer 中的数据在A表中遍历比较,将匹配数据返回。它的耗费为:100 × 10000,看起来在无索引时与嵌套循环连接没有区别,但由于缓存但引入,减少了大量但磁盘I/O。
根据mysql连表的两种算法来看,如果不得不使用连表的情况,就务必要使用索引。连接中,小表被称为驱动表,大表被称为被驱动表。当使用 inner jion 时,mysql 会自主判断哪张表为驱动表,当然也可以强行制定,通过 straight_jion 代替 inner jion ,那么关键字的左表,就是驱动表。left_jion 会强制指定左表为驱动表,right_jion 指定右表为驱动表。
innodb支持事物,事物应满足以下原则(原则的解释是个人理解):
innodb对事物进行了4种隔离性:
其中,1、2是不满足事物隔离性的。3,4满足了事物的原则。根据不同的事物类型,可能会出现以下的几种问题:
对于覆盖更新,需要在sql语句中修改,例如要修改一个订单,不要先查询到订单结果后,进行修改,再更新到数据库中。这样操作必然会造成数据错误。而应该在更新中使用 order = order - 1;这样的操作。
脏读和不可重复读,在 repeated read 及以后的隔离性中得到了解决,它们就是不满足隔离性而导致的。
而幻读,repeated read 仍无法解决。例如 我在事物1中新增了一条数据,新增数据的 id 为43,事物提交后,在事物2中,通过select是无法检索到这条数据的。但如果,事物2通过 id = 43 对这一条件对这行数据更新,那么它会成功,并在后续的 select 中,可以检索到这条数据。
事物中,对于改/删/增这样的操作,会添加行锁。
因为 innodb 支持事物,所以必然要有对应的锁来保证事物的实现。innodb 支持 行级锁 ,可以对数据行进行加锁。mysql锁一般会加在索引对应的字段上,如果锁不在索引上,它也不是加在主键上的,那么就会发生锁表。
读锁:共享锁,可以添加多个读锁,当数据被添加了读锁,将不可进行删/改操作。
写锁:非共享锁,写锁具有排他性,当数据被添加了写锁,在锁解除前,无法进行其他操作。
间隙锁:当惊醒范围更新的时候,会出现间隙锁。间隙锁样式如上所示,当我对上图这样当一组数据更新时,如果我更新当范围时14-35,那么从(6,∞]的所有数据,都会被加锁。这个范围内的id,即使还不存在,insert 也不会成功,而是会等待锁解除。范围所跨的所有间隙都会美加锁,组成了间隙锁。
临键锁:它是间隙锁的一种补充,间隙锁的范围是 ( … ] 的,左侧是不被锁定的,而右侧的数据会被加锁。
我们可以通过mysql查看锁的各种情况,来判断是否有问题sql。
mvcc(multi version concurrent controller)多版本并发控制,mysql使用它实现事物隔离,而不需要对 select 语句添加锁。mvcc机制提供给事物读取对内容是一个快照,它目前对状态可能早已经改变了,但在事物结束前,读取对内容是基于快照对。mvcc的实现基于undo日志。
由此可知:在事物中查询是,会对检索到的数据行的事物ID进行比较,按照不同的事物隔离性(mvcc只支持 committed read 和 repeated read),返会相应数据。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。