赞
踩
目录
7. 为什么索引结构默认使用B+Tree,而不是B-Tree,Hash,二叉树,红黑树?
索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新 数据库表中数据。索引的实现通常使用B树及其变种B+树。更通俗的说,索引就相当于目录。为了方便 查找书中的内容,通过对内容建立索引形成目录。而且索引是一个文件,它是要占据物理空间的。
MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。比如我 们在查字典的时候,前面都有检索的拼音和偏旁、笔画等,然后找到对应字典页码,这样然后就打开字 典的页数就可以知道我们要搜索的某一个key的全部值的信息了。
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引的原理:就是把无序的数据变成有序的查询
索引的工作原理可以通过以下步骤来理解:
索引的优点
可以大大加快数据的检索速度,降低IO成本
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
索引的缺点
时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时
候,索引也要动态的维护,会降低增/改/删的执行效率;
空间方面:索引需要占物理空间。
1、从存储结构上来划分:BTree索引(B-Tree或B+Tree索引),Hash索引,full-index全文索引,RTree索引。这里所描述的是索引存储时保存的形式,
2、从应用层次来分:普通索引,唯一索引,复合索引。
InnoDB存储引擎中,分为:
如果存在主键,主键索引就是聚集索引(叶子节点就是这一行的数据)
如果不存在,将使用的第一个唯一索引作为聚集索引
如果没有主键,或者没有合适的唯一索引,InnoDB会生成一个rowid作为隐藏的聚集索引
其他字段建立的索引就是非聚簇索引,叶子节点就是字段对应的id值,
如果找到id,还要到聚簇索引继续找(回表查询)
都是B+树的数据结构
聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是 相邻地存放在磁盘上的
非聚簇索引:叶子节点不存储数据、存储的是(数据行地址),也就是说根据索引查找到数据行的位置 再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个 目录里面找,找到对应的页码后再去对应的页码看文章
优势:
1、查询通过聚簇索引可以直接获取数据,相比非聚簇索引需要第二次查询(非覆盖索引的情况下)效率 要高
2、聚簇索引对于范围查询的效率很高,因为其数据是按照大小排列的
3、聚簇索引适合用在排序的场合,非聚簇索引不适合
劣势:
1、维护索引很昂贵,特别是插入新行或者主键被更新导至要分页(page split)的时候。建议在大量插入新行后,选在负载较低的时间段,通过OPTIMIZE TABLE优化表,因为必须被移动的行数据可能造成 碎片。使用独享表空间可以弱化碎片
2、表因为使用UUId(随机ID)作为主键,使数据存储稀疏,这就会出现聚簇索引有可能有比全表扫面 更慢,所以建议使用int的auto_increment作为主键
3、如果主键比较大的话,那辅助索引将会变的更大,因为辅助索引的叶子存储的是主键值;过长的主键 值,会导致非叶子节点占用占用更多的物理空间
InnoDB中一定有主键,主键一定是聚簇索引,不手动设置、则会使用unique索引,没有unique索引, 则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引, 辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引, 辅助索引叶子节点存储的不再是行的物理位置,而是主键值
不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进
行回表查询。一个索引包含(覆盖)所有需要查询字段的值,被称之为"覆盖索引"。
举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行 select score from student
where score > 90 的查询时,在索引的叶子节点上,已经包含了score 信息,不会再次进行回表查
询。
using index condition: 查找使用了索引,但是需要回表查询数据
using where ; using index 查找使用了索引,但是需要的数据在索引列中都能找到,所以不需要回表查询
(覆盖索引)
使用select * 容易出现回表查询
select id,username,password from tb_user where username = 'zoey';
建立username和password的联合索引性能更好,不会出现回表查询
因为可能我们索引的字段非常长,比如字符串类型,这既占内存空间,也不利于维护,浪费磁盘IO。所以我们就想,如果只把很长字段的前面的公共部分作为一个索引,就会产生超级加倍的效果。但是,我们需要注意,order by不支持前缀索引 。
create index idx_xxxx on table_name(column(n));
流程是:
先计算完整列的选择性 : select count(distinct col_1)/count(1) from table_1
再计算不同前缀长度的选择性 : select count(distinct left(col_1,4))/count(1) from table_1
找到最优长度之后,创建前缀索引 : create index idx_front on table_1 (col_1(4))
选择性是指不重复的索引值和数据表的记录总数比值,索引选择性越高,查询效率越高
唯一索引的选择性是一,性能也是最好的
B-tree n个key会有n+1个指针,也就是n+1阶树
B+Tree非叶子节点不存放数据,这样就可以存储更多的指针,来降低数的高度。B树则相反,叶子节点和非叶子节点都会都会存储数据,就会导致键值减少,高度就会增加
B+Tree所有的元素都会出现在叶子节点,并且会形成双向链表,适合范围查询
Hash索引:
通过hash算法,将键值换算成哈希值,映射到对应的位置上,然后存储在哈希表,会有哈希冲突,维护一个链表
虽然可以快速定位,但是没有顺序,IO复杂度高;
适合等值查询,如=、in()、<=>,不支持范围查询 ;
因为不是按照索引值顺序存储的,就不能像B+Tree索引一样利用索引完成排序操作 ;
Hash索引在查询等值时快 通常一次就可以;效率比B+Tree好,一次是不出现哈希冲突的情况
如果有大量重复键值得情况下,哈希索引的效率会很低,因为存在哈希碰撞问题 。
支持hash索引的是Memeory引擎
二叉树:顺序插入时会形成链表,查询效率大大降低,大数据量,树的高度就会很高,并且IO代价高。
红黑树:本质上也是二叉树,树的高度随着数据量增加而增加,IO代价高。
MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要索引生效,需要
按照建立索引时的字段顺序挨个使用,否则无法命中索引。
具体原因为:
MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序
为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排
序。(最左匹配原则)
当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对
于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,以此类推。因此在
建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在
前面。此外可以根据特例的查询或者表结构进行单独的调整。
多条件联合查询时,MYSQL优化器会评估哪个字段索引效率更高,会选择该索引完成本次查询
使用得当,可以避免回表查询
如果使用联合索引,要遵循最左前缀法则,指的是查询是从最左侧列开始,不跳过索引中的列,如果越过某一列,
后面的索引就会失效
最左前缀原则就是最左优先,在创建多列索引时,where子句中使用最频繁的一列放在最左边。
mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配(索引失效),比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用
到,a,b,d的顺序可以任意调整。
=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮
你优化成索引可以识别的形式。
查询更快、占用空间更小
非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查
询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值
或者一个空串代替空值;
取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()
函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;
索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大
效率越高。
通常通过索引查询数据比全表扫描要快。但是我们也必须注意到它的代价。 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修 改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需 要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高 查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:
基于一个范围的检索,一般查询返回结果集小于表中记录数的30%。
基于非唯一性索引的检索。
结合B+Tree的特点,自增主键是连续的,在插入过程中尽量减少页分裂,即使要进行页分裂,也只会分裂很少一部分。并且能减少数据的移动,每次插入都是插入到最后。总之就是减少分裂和移动的频率
每个节点可以存储多个key,多个指针,就会有多个子节点,也就是多路,每个指针指向一个磁盘块(页16kb),上面的节点叫非叶子节点,用来查找,下面的节点是叶子节点,用来保存数据,所有的key都会出现在叶子节点,会保存key所对应的数据,叶子节点按照重小到大顺序排序,并且形成双向链表。
首先从跟节点对比,采用二分查找法,定位数据与key的大小,根据指针继续向下查找,直到叶子节点
B树(B-Tree)和B+树(B+Tree)是常用的索引结构,用于优化数据库的数据访问性能。它们在设计和实现上有一些区别。
B树是一种平衡的多路搜索树,它具有以下特点:
而B+树是在B树的基础上进行了改进,它与B树的区别在于:
总结起来,B树和B+树都是用于数据库索引的数据结构,它们在节点结构、指针方式和适用场景上有所不同。B树适合随机访问,而B+树适合范围查询和顺序遍历。根据实际的应用场景和性能需求,可以选择其中之一来优化数据库的查询性能。
通过explain,如以下例子:
EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title='Senior
Engineer' AND from_date='1986-06-26';
key:此字段是 MySQL 在当前查询时所真正使用到的索引。
内连接(Inner Join):内连接是根据连接条件从两个表中筛选出匹配的行,只返回两个表中共有的数据。如果某行在一个表中没有匹配的行,则该行将被忽略。
左连接(Left Join):左连接会返回左边表中的所有行,以及与右边表中匹配的行。如果右边表中没有匹配的行,则对应的列将填充为NULL。
右连接(Right Join):右连接与左连接相似,不同之处在于右连接返回右边表中的所有行,以及与左边表中匹配的行。如果左边表中没有匹配的行,则对应的列将填充为NULL
原子性 Atomicity: 事务是不可分割的最小操作单元,要么全成功,要么全部成功
一致性 Consistency: 事务完成时,必须所有的数据都保持一致状态,事务在执行操作之前和执行之后是一致的,事务如果执行失败,要进行回滚,保证数据之前的状态,如果执行成功,要保证数据是更新后的,通过redolog和undolog来保证
隔离性 Isolation: 数据库系统提供的隔离机制,保证事务不受外部并发操作的影响,可以独立的运行,通过MVCC和数据库锁来保证
持久性 Durability: 事务一旦提交或者回滚,它对数据库中的数据的改变就是永久的
脏读: 一个事务读到了另一个事务还没有提交的数据
不可重复读: 一个事务先后读取同一个数据,但是读取的数据不同
幻读: 一个事务按照条件查询的时候,没有查到数据,想要插入数据,但是在插入时又发现数据存在了
Read uncommited 读未提交 : 脏读,不可重复读,幻读
Read commited 读已提交 不可重复度,幻读
Repeatable Read(默认) 重复读: 幻读
Seralizable 串行化:
InnoDB sql5.5版本后默认的引擎
支持事务 遵循ACID
行级锁 提高并发访问性能
支持外键,保证数据的完整性和正确性
InnoDB引擎的每张表都会有一个表空间文件,存储表的表结构,数据和索引
MyISAM
不支持事务,不支持外键,
支持表锁,不支持行锁
访问速度快
Memory
表数据存放在内存,只能作为临时表使用,访问速度快
hash索引
A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
C一致性由其他三大特性保证、程序代码要保证业务上的一致性
I隔离性由MVCC来保证 MVCC是一种并发控制机制,它允许并发事务在不阻塞彼此的情况下访问数据库。为了实现MVCC,MySQL在每个数据行上都保存了多个版本。
D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可 以从redo log恢复
解决并发访问时,数据访问的一致性,有效性的问题
全局锁对整个数据库加锁,这样整个数据库都陷入了“只读”状态,后序任何的DML、DDL语句更新操作都会被阻塞
典型的使用场景是对数据库的所有数据库的数据备份(mysqldump)(对全部的数据库都会加锁)
加锁: flush tables with read lock
解锁: unlock tables
1、如果在主库中备份数据,那么主库所有的更新操作都会阻塞
2、如果在从库中备份数据,那么从库备份期间,不能接受来自主库的二进制日志(binlog),导致主从延迟
在InnoDB引擎中,我们可以在备份时加上参数--single-transaction参数来完成不加锁的一致性数据备份
锁住某一张表,粒度(锁的作用范围)大,并发性最低,发生锁冲突的概率最高,并发度度最低
表共享读锁:read lock
一旦某一个表增加了读锁,那么这个表无论是当前客户端还是其他客户端,都只能读,不能写
表独占写锁:write-lock
一个客户端对一个表增加了写锁,那么这个客户端可以对这个表进行读和写操作,另外一个客户端对这个表的读和写都是阻塞的
加锁: lock tables read/write
解锁: unlock tables
MDL(meta data lock)是为了保护在并发状态下,元数据和表数据结构的一致性,系统自动控制,无需显示调用,访问一张表会自动加上。避免DML和DDL冲突,保证读写正确性
MySQL5.5引入MDL,
对一张表进行增删改的时候,加MDL读锁(共享shared),
当对表结构进行变更的时候,加MDL写锁(排他exclusive),与其他所有DML语句都是互斥的
如果有事务在进行增删改查的操作,保证其他事务不可以修改表结构
如果有事务正在修改表结构,那么不允许其他事务进行增删改查的操作
在同一个事务内,如果先对一个表加上表锁,再对同一个表的某一行加上行锁,那么最终只会保留行锁,表锁不再生效
避免DML在执行时,加的行锁与表锁的冲突,在InnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查
意向锁分为意向共享锁和意向排他锁
意向共享锁IS:和表级的共享锁读锁兼容,和表级排他锁写锁互斥
意向排他锁IX:和表级的共享锁,表级排他锁都互斥
意向锁之间相互兼容
每一次加锁锁住的是行数据,根据索引列加锁,发生锁冲突的概率最低,并发度最高
InnoDB的数据是基于索引组织的,行锁是通过对索引项加锁来实现的,而不是对记录加的锁,
对于行级锁,分为三类:
行锁:锁定单行记录,防止其他事务对记录修改和删除。RC读已提交,RR可重复读隔离级别下都支持
间隙锁:(两个记录的之前间隙)保证索引记录之间的间隙不变,防止其他事务在这个间隙增加记录,参数幻读。RR隔离级别下都支持的
临键锁:行锁和间隙锁的组合,锁住数据,又锁住间隙。RR隔离级别下都支持的
两种类型的行锁
共享锁(S):一个事务获取了这行的共享锁,另外一个事务仍然可以获取这行数据的共享锁
排他锁(X):一个事务获取了这行的排他锁,另外一个事务不可以获取这行数据的共享锁和排他锁
默认情况下,innoDB在 REPEATABLE READ事务隔离级别运行,InnoDB使用 next-key 锁进行搜索和索引扫描,以防止幻读。
间隙锁/临键锁
默认情况下,lnnoDB在 REPEATABLE READ事务隔离级别运行,innoDB使用 next-key 锁进行搜索和索引扫描,以防止幻读。
1.索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁。
比如更新id为5的记录,但是不存在,会给id为3-8这个间隙加锁,比如3-8之间没有数据,其他事务则不能增加数据
2.索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-keylock 退化为间隙锁
3.索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
间隙锁的唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙不会阻止另一个事务在同一个间隙上采用间隙锁
原子性、一致性、持久性 (redo log 、undo log)
隔离性(锁、MVCC)
redolog 重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性
该日志文件由两部分组成:重做日志缓冲(redo log buffer)及重做日志文件(redo logfile),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中, 用于在刷新脏页到磁盘,发生错误时, 进行数据恢复使用;
undolog 回滚日志 ,回滚行记录到某个特定版本,用来保证事务的原子性、一致性
undo log和redo log记录物理日志不一样,它是逻辑日志;
可以认为当delete一条记录时,undolog中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录;
当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚;
其实在使用分布式事务框架的时候,其底层实现原理也是借助了 undo log的思想,记录了反向操作的sql语句,以便于事务回滚时使用;
在InnoDB存储引擎中,undo log分为:
insert undo log
update undo log
当我们执行一个insert操作,即给表中添加一条记录时,比如下面这条语句:
INSERT INTO user (name) VALUES ("tom");
其实对应的undo log中将会反向生成一条delete的记录
MVCC,全称 Multi-Version Concurrency Control ,即多版本并发控制。MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
MVCC 在 MySQL InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
在学习 MVCC 多版本并发控制之前,我们必须先了解一下,什么是 MySQL InnoDB 下的当前读和快照读?
当前读
像 select lock in share mode (共享锁), select for update; update; insert; delete (排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
快照读
像不加锁的 select 操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC ,可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
说白了 MVCC 就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
MVCC 多版本并发控制是 「维持一个数据的多个版本,使得读写操作没有冲突」 的概念,只是一个抽象概念,并非实现
因为 MVCC 只是一个抽象概念,要实现这么一个概念,MySQL 就需要提供具体的功能去实现它,「快照读就是 MySQL 实现 MVCC 理想模型的其中一个非阻塞读功能」。而相对而言,当前读就是悲观锁的具体功能实现
要说的再细致一些,快照读本身也是一个抽象概念,再深入研究。MVCC 模型在 MySQL 中的具体实现则是由 3 个隐式字段,undo 日志 , Read View 等去完成的,具体可以看下面的 MVCC 实现原理
MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 , Read View 来实现的。所以我们先来看看这个三个 point 的概念
每行记录除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID, DB_ROLL_PTR, DB_ROW_ID 等字段
DB_TRX_ID
6 byte,最近修改(修改/插入)事务 ID:记录创建这条记录/最后一次修改该记录的事务 ID
DB_ROLL_PTR
7 byte,回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里)
DB_ROW_ID
6 byte,隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID产生一个聚簇索引
实际还有一个删除 flag 隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除 flag 变了
undo log 主要分为两种:
insert undo log
代表事务在 insert 新记录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log
事务在进行 update 或 delete 时产生的 undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除
undolog版本链
不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本的链表,链表的头部是最新的的记录数据,尾部是最早的旧记录
什么是 Read View?
什么是 Read View,说白了 Read View 就是事务进行快照读操作的时候生产的读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID (当每个事务开启时,都会被分配一个 ID , 这个 ID 是递增的,所以最新的事务,ID 值越大)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。