本系列文章: ??Mysql(一)三大范式、数据类型、常用函数、事务 ??Mysql(二)Mysql SQL练习题 ??Mysql(三)索引、视图、存储过程、触发器、分区表 ??Mysql(四)存储引擎、锁 ??Mysql(五)Mysql架构、数据库优化、主从复制 ??Mysql(六)慢查询、执行计划、SQL语句优化
一、索引
1.1 索引概述
索引是一种特殊的文件 (InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针 。索引是一种数据结构 。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
??数据是存储在磁盘上的,查询数据时,如果没有索引,会加载所有的数据到内存,依次进行检索,读取磁盘次数较多。有了索引,就不需要加载所有数据,因为 B+树的高度一般在 2-4 层,最多只需要读取 2-4 次磁盘,查询速度大大提升。
- 通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。
索引是一个文件,它是要占据物理空间(存储在磁盘上)的 。
??每种存储引擎(如MyISAM、InnoDB、BDB、MEMORY 等)对每个表至少支持16个索引,总索引长度至少为256字节 。 ??MyISAM和InnoDB存储引擎的表默认创建的都是B+树索引 。 ??MySQL支持前缀索引,即对索引字段的前N个字符创建索引。对于MyISAM存储引擎的表,索引的前缀长度可以达到1000字节长;而对于InnoDB存储引擎的表,索引的前缀长度最长是767字节。 ??MySQL还支持全文本(FULLTEXT)索引,该索引可以用于全文搜索。只限于CHAR、VARCHAR 和 TEXT列。 ??默认情况下,MEMORY存储引擎使用HASH索引 ,但也支持B树索引。 ??用CREATE关键字创建索引示例:
create index cityname on city (city(10));
DROP INDEX index_name ON tbl_name
drop index cityname on city;
1.2 索引的基本原理
??索引用来快速地寻找那些具有特定值的数据。如果没有索引,一般来说执行查询时遍历整张表。 ??索引的原理很简单,就是把无序的数据变成有序的查询 :
- 把创建了索引的列的内容进行排序;
- 对排序结果生成倒排表;
- 在倒排表内容上拼上数据地址链;
- 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据。
1.3 索引的优缺点
- 索引的优点
??可以大大加快数据的检索速度,提高系统的性能 ,这也是创建索引的最主要的原因。 - 索引的缺点
- 时间方面:
创建索引和维护索引要耗费时间 ,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率; - 空间方面:
索引需要占物理空间 。
1.4 索引的创建与删除
CREATE TABLE user_index2 (
id INT auto_increment PRIMARY KEY,
first_name VARCHAR (16),
last_name VARCHAR (16),
id_card VARCHAR (18),
information text,
KEY name (first_name, last_name),
FULLTEXT KEY (information),
UNIQUE KEY (id_card)
);
ALTER TABLE table_name ADD INDEX index_name (column_list);
ALTER TABLE table_name ADD PRIMARY KEY (id);
ALTER TABLE table_name ADD CONSTRAINT pk_n PRIMARY KEY (id);
ALTER TABLE m ADD CONSTRAINT fk_id FOREIGN KEY (id) REFERENCES n(id);
??ALTER TABLE用来创建普通索引、UNIQUE索引或主键索引。 ??其中table_name是要增加索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。 ??索引名index_name可自己命名。另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。
CREATE INDEX index_name ON table_name (column_list);
??CREATE INDEX可对表增加普通索引或UNIQUE索引。(但是,不能创建PRIMARY KEY索引)。
ALTER TABLE table_name DROP KEY index_name
ALTER TABLE table_name DROP PRIMARY KEY
ALTER TABLE m DROP FOREIGN KEY 'fk_id';
1.5 索引分类
??在 InnoDB 里面,索引类型有三种,普通索引、唯一索引(主键索引是特殊的唯一索引)、全文索引。 ??Mysql索引的5种类型:主键索引、唯一索引、普通索引和全文索引、组合索引。这5种类型索引的特点:
?普通索引:仅加速查询; ?唯一索引:加速查询 + 列值唯一(可以有null) ; ?主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个 ; ?组合索引:多列值组成一个索引,专门用于组合搜索 ,其效率大于索引合并; ?全文索引:对文本的内容进行分词 ,进行搜索。
1.5.1 主键索引
??主键是一种唯一性索引,但它必须指定为PRIMARY KEY ,每个表只能有一个主键。主键一般情况可以设置为自增,主键自增时,插入数据时都是在最后追加,不会在表中间插入数据,索引方便维护。 ??创建主键索引的两种方式示例:
create table table_primarykey
( id int primary key auto_increment ,
name varchar(20)
);
create table table_primarykey
( id int auto_increment ,
name varchar(20)
);
alter table table_primarykey
add primary key (id);
1.5.2 唯一索引
??索引列的所有值都只能出现一次,即必须唯一,值可以为空。一个表允许多个列创建唯一索引。创建唯一索引示例:
create unique index 索引名 on 表名(列名1,列名2……)
ALTER TABLE `table_name` ADD UNIQUE (`column`)
1.5.3 普通索引
??基本的索引类型,值可以为空,没有唯一性的限制。示例:
create table table_index
( id int primary key auto_increment ,
name varchar(20) ,
index index_name (name)
);
create table table_index
( id int primary key auto_increment ,
name varchar(20)
);
alter table table_index
add index index_name (name);
1.5.4 全文索引
??全文索引的索引类型为FULLTEXT。全文索引可以在varchar、char、text类型的列上创建。 ??MyISAM支持,Innodb在Mysql5.6之后支持。
??用 like + % 就可以实现模糊匹配了,为什么还要全文索引?like + % 在文本比较少时是合适的,但是对于大量的文本数据检索,是不可想象的。全文索引在大量的数据面前,能比 like + % 快 N 倍,速度不是一个数量级,但是全文索引可能存在精度问题。
??全文索引创建示例:
CREATE TABLE article (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT (title, content)
);
ALTER TABLE articleADD FULLTEXT INDEX fulltext_article (title, content)
1.5.5 组合索引
??多列值组成一个索引,专门用于组合搜索 。 ??如果对多列进行索引(组合索引),列的顺序非常重要,MySQL仅能对索引最左边的前缀进行有效的查找。例如: ??假设存在组合索引(c1,c2),查询语句select * from t1 where c1=1 and c2=2 能够使用该索引。查询语句select * from t1 where c1=1 也能够使用该索引。但是,查询语句select * from t1 where c2=2 不能够使用该索引,因为没有组合索引的引导列,即要想使用c2列进行查找,必需出现c1等于某值。 ??这就是最左匹配原则。简单来说,在两个列上的组合索引,有个前后顺序(c1,c2),在查询c1时可以使用该组合索引,在同时查询c1、c2时也可以使用该索引,但只查询c2时不能使用该索引。 ??组合索引创建示例:
CREATE TABLE `left_test` (
`id` int(11) NOT NULL,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
`e` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `m_index` (`a`,`b`,`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1.6 前缀索引
??有时候需要索引很长的字符列,这会让索引变得大且慢。通常可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。对于BLOB,TEXT,或者很长的VARCHAR类型的列,必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。 ??使用前缀索引时,难度在于:如何定义前缀截取的长度 。 ??其中一种确定前缀长度的做法是:计算完整列的选择性,并使其前缀的选择性接近于完整列的选择性 。 ??计算完整列的选择性:
mysql> select count(distinct city) / count(*) from city_demo;
+
| count(distinct city) / count(*) |
+
| 0.4283 |
+
row in set (0.05 sec)
??可以在一个查询中针对不同前缀长度的选择性进行计算,这对于大表非常有用,在同一个查询中计算不同前缀长度的选择性示例:
mysql> select count(distinct left(city,3))/count(*) as sel3,
-> count(distinct left(city,4))/count(*) as sel4,
-> count(distinct left(city,5))/count(*) as sel5,
-> count(distinct left(city,6))/count(*) as sel6
-> from city_demo;
+
| sel3 | sel4 | sel5 | sel6 |
+
| 0.3367 | 0.4075 | 0.4208 | 0.4267 |
+
1 row in set (0.01 sec)
??可以看见当索引前缀为6时的基数是0.4267,已经接近完整列选择性0.4283。因此在这个例子中,合适的前缀长度是6。创建索引示例:
alter table city_demo add key (city(6));
1.7 覆盖索引
??先通过索引找到主键索引的键值,再通过主键值查出索引里面没有的数据,它比基于主键索引的查询多扫描了一棵索引树,这个过程就叫回表 。 ??例如:select * from user_innodb where name = 'qingshan' ; ??在辅助索引里面,不管是单列索引还是联合索引,如果select的数据列只用从索引中就能够取得,不必从数据区中读取,这时候使用的索引就叫做覆盖索引,这样就避免了回表。
1.8 索引设计的步骤
- 1、整理表上的所有SQL,重点包括select、update、delete操作的where条件所用到的列的组合、关联查询的关联条件等。
- 2、整理所有查询SQL的预期执行频率。
- 3、整理所涉及列的选择度。
- 4、选择合适的主键。没有特别合适的列时,建议使用自增列作为主键。
- 5、优先给那些执行频率最高的SQL创建索引。
- 6、按执行频率排序,依次检查是否需要为每个SQL创建索引。
- 7、索引合并,利用复合索引来降低索引的总数,充分利用最左前缀的原则,让索引尽可能多地复用,同时在保证复用率的情况下,把选择度更高的列放在索引的最左侧。
- 8、上线后,通过慢查询分析、执行计划分析、索引使用统计,来确定索引的实际使用情况,根据情况做出调整。
1.9 设计索引的原则
1.9.1 应该使用索引的情况
- 1、
最适合索引的列是出现在WHERE子句中的列,或连接子句中指定的列 ,而不是出现在SELECT关键字后的选择列表中的列。即:为经常需要排序、分组和联合操作的字段建立索引;为常用作为查询条件的字段建立索引。 - 2、
尽量使用高选择度索引 。索引列的基数越大,索引的效果越好。 - 3、
使用短索引 。如果对字符串列进行索引,应该指定一个前缀长度,只要有可能就应该这样做。 - 4、
利用最左前缀 。即利用最左匹配原则。 - 5、
如果索引字段的值很长 ,那么查询的速度会受到影响,最好使用值的前缀来索引 。 - 6、
对于InnoDB存储引擎的表,尽量手工指定主键(即使用主键索引) 。
??对于InnoDB存储引擎的表,数据默认会按照一定的顺序保存,如果有明确定义的主键,则按照主键顺序保存; ??如果没有主键,但是有唯一索引,那就是按照唯一索引的顺序保存; ??如果既没有主键又没有唯一索引,那么表中会自动生成一个内部列,按照这个列的顺序保存。 ??按照主键或者内部列进行的访问是最快的,所以InnoDB表尽量自己指定主键,当表中同时有几个列都是唯一的,都可以作为主键的时候,要选择最常作为访问条件的列作为主键,提高查询的效率。
- 7、
定义有外键的数据列一定要建立索引 。 - 8、使用联合索引时,
取值离散大的字段(变量各个取值之间的差异程度)的列放到联合索引的前面 。
1.9.2 不应该使用索引的情况
- 1、
不是所有的表都需要键索引 。常见的代码表、配置表等数据量很小的表,除了主键之外,再创建索引没有太大的意义。索引扫描和全表扫描相比,并不会带来性能的大幅提升。大表的查询、更新、删除操作要尽可能通过索引进行。对于大表来说,全表扫描速度较慢。 - 2、
不要过度索引 。因为额外的索引要占磁盘空间,并降低写操作的性能。因此,只需保持所需的索引有利于查询优化。 - 3、
谨慎选择低选择度索引 。对于选择性低并且数据分布均衡的列,因为过滤的结果集大,创建索引的效果通常不好;如果列的选择性低并且数据缝补不均衡,比如男女比例为99:1,那么此时创建对于查询条件为‘女’的过滤结果集就比较小,所以的效率较高。 - 4、
谨慎使用唯一索引 。使用唯一索引,在特定查询的速度上会有提升,同时能满足业务上对唯一性约束的需求。但在Mysql5.6之后,InnoDB引擎新增了Change Buffer特性来提升写入性能,除了主键之外的唯一索引会导致ChangeBuffer无法被使用,对写入性能影响较大。 - 5、
尽量的扩展索引(组合索引),不要新建索引 。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。 - 6、
更新频繁字段不适合创建索引 。 - 7、对于定义为text、image和bit的数据类型的列不要建立索引。
- 8、和适合建索引的条件相反的情况,均布适合键索引。示例:
??where 条件中用不到的字段不适合建立索引; ??表数据较少; ??需要经常增删改的列不适合建索引; ??参与列计算的列不适合建索引; ??区分度不高的字段不适合建立索引,如性别等。
1.10 最左匹配原则
??假设存在组合索引(c1,c2),查询语句select * from t1 where c1=1 and c2=2 能够使用该索引。查询语句select * from t1 where c1=1 也能够使用该索引。但是,查询语句select * from t1 where c2=2 不能够使用该索引,因为没有组合索引的引导列,即要想使用c2列进行查找,必需出现c1等于某值。这就是最左匹配原则。简单来说,在两个列上的组合索引,有个前后顺序(c1,c2),在查询c1时可以使用该组合索引,在同时查询c1、c2时也可以使用该索引,但只查询c2时不能使用该索引。 ??在最左前缀匹配原则中,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的顺序可以任意调整 。
??例如组合索引(a,b,c),组合索引的生效原则是:从前往后依次使用生效,如果中间某个索引没有使用,那么断点前面的索引部分起作用,断点后面的索引没有起作用。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面 。 ??比如:
- where a=3 and b=45 and c=5 … 这种三个索引顺序使用中间没有断点,全部发挥作用;
- where a=3 and c=5… 这种情况下b就是断点,a发挥了效果,c没有效果
- where b=3 and c=4… 这种情况下a就是断点,在a后面的索引都没有发挥作用,这种写法联合索引没有发挥任何效果;
- where b=45 and a=3 and c=5 … 这个跟第一个一样,全部发挥作用,abc只要用上了就行,跟写的顺序无关。
1.11 索引生效和失效条件
1.11.1 索引生效条件
??以下是几个几个能够使用索引的典型场景。
- 1、匹配全指
??对索引中所有列都指定具体值,即对索引中的所有列都有等值匹配的条件,示例:
explain select * from rental
where rental_date = '2021-05-25 17:12:23'
and inventory_id = 373
and customer_id = 343;
- 2、匹配值的范围查询
??对索引的值能够进行范围查询,示例:
explain select * from tental
where customer_id >= 373 and customer_id < 400;
- 3、匹配最左前缀
??仅仅使用索引中的最左列进行查找。 - 4、仅仅对索引进行查询
??当查询的列都在索引的字段中时,查询的效率更高。 - 5、匹配列前缀
??仅仅使用索引中的第一列,并且只包含索引第一列的开头一部分进行查找。示例,查询标题title是以AFRICAN开头的电影信息:
create index idx_title_desc_part on file_text (title(10),description(20));
explain select title from film_text where title like 'AFRICAN%';
- 6、如果列名是索引,那么使用column_name is null就会使用到索引
??总的来说,MySQL只对以下操作符才使用索引:
??< ??<= ??= ??> ??>= ??between ??in ??以及某些时候的like(不以通配符%或_开头的情形) 。
1.11.2 索引失效条件
- 如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)。要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引。
- 对于多列索引,不是使用的第一部分,则不会使用索引。
- like查询是以%开头。
- 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则会出现隐式转换,从而不使用索引。
- 不满足最左原则,则不会使用复合索引。
- 如果Mysql估计使用全表扫描要比使用索引快,则不使用索引。
索引列上使用函数(replace\SUBSTR\CONCAT\sum count avg)、表达式、计算(+ - * /) 。- 判断索引列是否不等于某个值时。
1.12 索引的数据结构
??索引的文件存储形式与存储引擎有关。 ??常见的存储引擎主要有:MyIsam、InnoDB、Memory等。 ?? 索引文件的结构:hash、二叉树、B树、B+树。
1.12.1 InnoDB的逻辑存储结构
??MySQL的存储结构分为5级:表空间、段、簇、页、行。
- 1、表空间(Table Space)
??表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。分为:系统表空间、独占表空间、通用表空间、临时表空间、Undo表空间。 - 2、段(Segment)
??表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等,段是一个逻辑的概念。一个 ibd 文件(独立表空间文件)里面会由很多个段组成。创建一个索引会创建两个段,一个是索引段:leaf node segment,一个是数据段:non-leaf node segment。 ??索引段管理非叶子节点的数据。数据段管理叶子节点的数据。也就是说,一个表的段数,就是索引的个数乘以 2。 - 3、簇(Extent)
??一个段(Segment)又由很多的簇(也可以叫区)组成,每个区的大小是 1MB(64个连续的页)。 ??每一个段至少会有一个簇,一个段所管理的空间大小是无限的,可以一直扩展下去,但是扩展的最小单位就是簇。 - 4、页(Page)
??为了高效管理物理空间,对簇进一步细分,就得到了页。簇是由连续的页(Page)组成的空间,一个簇中有 64 个连续的页。(1MB/16KB=64)。这些页面在物理上和逻辑上都是连续的。 ??InnoDB 也有页的概念(也可以称为块),每个页默认 16KB。页是 InnoDB 存储引擎磁盘管理的最小单位,通过 innodb_page_size 设置。
??一个表空间最多拥有 232 个页,默认情况下一个页的大小为 16KB,也就是说一个表空间最多存储 64TB 的数据。
??操作系统和内存打交道,最小的单位是页 Page。文件系统的内存页通常是 4K。
- 5、行(Row)
??InnoDB 存储引擎是面向行的(row-oriented),也就是说数据的存放按行进行存放。
1.12.2 哈希表
??本质上是个数组,往里面存元素时,先通过哈希函数求得一个int值,再想该int值转换为数组中对应的位置,然后将元素插入。哈希表的数组中其实都是存的k-v键值对,key是真正存放的数据,value一般是某个固定值。 ??哈希表可以完成索引的存储,每次在添加索引的时候需要计算指定列的哈希值,取模运算后计算出下标,将元素插入下标位置。 ??哈希索引的特点:
- 它的时间复杂度是 O(1),查询速度比较快。因为哈希索引里面的数据不是按顺序存储的,所以不能用于排序。
- 我们在查询数据的时候要根据键值计算哈希码,所以它只能支持等值查询(= IN),不支持范围查询(> < >= <= between and)。
??如果字段重复值很多的时候,会出现大量的哈希冲突(采用拉链法解决),效率会降低。 ??哈希表在使用的时候,需要将全部的数据加载到内存,比较耗费内存空间,不是很合适。
- InnoDB 内部使用哈希索引来实现自适应哈希索引特性。
??InnoDB 只支持显式创建 B+Tree 索引,对于一些热点数据页,InnoDB 会自动建立自适应 Hash 索引,也就是在 B+Tree 索引基础上建立 Hash 索引,这个过程对于客户端是不可控制的,隐式的。
1.12.3 B树
??平衡二叉搜索树又被称为AVL树(有别于AVL算法),且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。当插入数据次数过多时,旋转次数较多,影响性能 。 ??红黑树是基于AVL树的一个升级,损失了部分查询性能,来提升插入的性能,在红黑树中最低子树和最高子树之差小于2倍即可,在插入的时候,不需要进行N多次的旋转操作,而且还加入了变色的性能,来满足插入和查询性能的平衡。 ??索引的数据,是放在硬盘上的。当我们用树的结构来存储索引的时候,访问一个节点就要跟磁盘之间发生一次 IO。InnoDB 操作磁盘的最小的单位是一页,大小是 16K。那么,一个树的节点就是 16K 的大小。 ??如果我们一个节点只存一个键值+数据+引用,例如整形的字段,可能只用了十几个或者几十个字节,它远远达不到 16K 的容量,所以访问一个树节点,进行一次 IO 的时候,浪费了大量的空间。所以如果每个节点存储的数据太少,从索引中找到我们需要的数据,就要访问更多的节点,意味着跟磁盘交互次数就会过多。如果是机械硬盘时代,每次从磁盘读取数据需要 10ms 左右的寻址时间,交互次数越多,消耗的时间就越多。
?解决方案是什么呢?
??第一个就是让每个节点存储更多的数据。第二个,节点上的关键字的数量越多,我们的指针数也越多,也就是意味着可以有更多的分叉(我们把它叫做“路数”)。因为分叉数越多,树的深度就会减少(根节点是 0)。 ??前面的结论就是:二叉树以及N多的变种都不能支撑索引,原因是树的深度过深,导致IO次数变多,影响数据读取的效率 。所以此时需要用多叉树(B树)来实现索引。
- B树示例:
??B树的特点:
- 所有键值分布在整棵树中
- 所有有可能在非叶子节点结束在关键字全集内做一次查找,性能逼近二分查找
- 每个节点最多拥有m个子树
- 根节点至少有2个子树
- 分支节点至少拥有m/2颗子树(除根节点也叶子节点之外都是分支节点)
- 所有叶子节点都在同一层、每个节点最多可以拥有m-1个key,并且以升序排列。
??在上面的例子中,假设每个节点占用一个磁盘块,一个节点上有两个升序排列的关键字和是哪个指向子树根节点的指针,指针存储的是子节点所在磁盘块的两个地址。两个关键词划分成的三个范围域对应的是三个指针指向的子树的数据的范围域。以根节点为例,关键字为16和34,P1指针指向的子树的数据范围为小于16,P2指针指向的子树的数据范围为16-34,P3指针指向的子树的数据范围为大于34。 ??查找关键字过程:
- 根据根节点找到磁盘块1,读入内存(磁盘IO第一次)
- 比较关键字28在区间(16,34),找到磁盘块1的指针P2
- 根据P2指针找到磁盘块3,读入内存(磁盘IO第二次)
- 比较关键字28在区间(15,31),找到磁盘块3的指针P2
- 根据P2指针找到磁盘块8,读入内存(磁盘IO第三次)
- 在磁盘块8中的关键字列表中找到关键字28
??B树的缺点:
- 每个节点都有key和data,而每个页存储空间是有限的,如果data比较大的话会导致每个节点存储的key数量变小;
- 当存储的数据量很大的时候会导致深度较大,增大查询时磁盘IO次数,进而影响查询性能。
1.12.4 B+树
- B+树示例:
?InnoDB 底层存储结构为B+树 , B树的每个节点对应innodb的一个page, page大小是固定的,一般设为 16k。其中非叶子节点只有键值,叶子节点包含完成数据。 ?B+树是在B树的基础上做的一种优化,变化如下:
1、B+树每个节点可以包含更多的节点 ,这样做的原因有两个:一是为了降低树的高度,二是将数据范围变成多个区间,区间越多,数据检索越快 2、非叶子节点存储key,叶子节点存储key和数据 3、叶子节点两两指针相互连接 (符合磁盘的预读特性),顺序查询性能更高 。
?在B+树上有两个头指针,一个指向根节点,一个另一个指向关键字最小的叶子节点 ,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+树进行两种查找:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进随机查找。
?Innodb,B+树叶子节点直接放置数据的示例: ?Innodb是通过B+树结构对主键创建索引,然后叶子节点中存储记录,如果没有主键,那么会选择唯一键,如果没有唯一键,那么会生成一个6位的row_id来作为主键。 ?如果创建索引的键是其他字段,那么在叶子节点中存储的是该记录的主键,然后再通过主键索引来找到对应的记录,叫做回表 。 ?MyISAM中的B+树和Inondb相比,差异之处就在于索引和表数据是分开存储的,看个例子: ?InnoDB索引适用场景:
经常更新的表,适合处理多重并发的更新请求 。支持事务 。- 可以从灾难中恢复(通过 bin-log 日志等)。
- 外键约束。只有他支持外键。
- 支持自动增加列属性 auto_increment。
??MySQL 中的 B+Tree 有几个特点:
它的关键字的数量是跟路数(子树数量)相等的 ;B+Tree 的根节点和枝节点中都不会存储数据,只有叶子节点才存储数据 。搜索到关键字不会直接返回,会到最后一层的叶子节点。比如我们搜索 id=28,虽然在第一层直接命中了,但是全部的数据在叶子节点上面,所以我还要继续往下搜索,一直到叶子节点。在 InnoDB 中 B+ 树深度一般为 1-3 层,它就能满足千万级的数据存储 。B+Tree 的每个叶子节点增加了一个指向相邻叶子节点的指针 ,它的最后一个数据会指向下一个叶子节点的第一个数据,形成了一个有序链表的结构。- 它是根据左闭右开的区间 [ )来检索数据。
1.13 B+树索引和Hash索引的区别
??MEMORY存储引擎的表可以选择使用BTREE索引或者HASH索引。
1.13.1 B+树索引和Hash索引的特点
??索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引等。
- 1、B+树索引
??InnoDB存储引擎的默认索引实现为:B+树索引 。 ??B+tree性质:
n棵子tree的节点包含n个关键字 ,不用来保存数据而是保存数据的索引。- 所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
- 所有的非终端结点可以看成是索引部分,结点中仅含其子树中的最大(或最小)关键字。
B+ 树中,数据对象的插入和删除仅在叶节点上进行 。- B+树有2个头指针,一个是树的根节点,一个是最小关键码的叶节点。
- 2、哈希索引
??哈希索引,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快 。 ??简要说下,类似于数据结构中简单实现的Hash表(散列表)一样,当我们在Mysql中用哈希索引时,主要就是通过Hash算法(常见的Hash算法有直接定址法、平方取中法、折叠法、除数取余法、随机数法),将数据库字段数据转换成定长的Hash值,与这条数据的行指针一并存入Hash表的对应位置;如果发生Hash碰撞(两个不同关键字的Hash值相同),则在对应Hash键下以链表形式存储。示例:
1.13.2 B+树索引和Hash索引的适用条件
??索引算法有B+树算法和Hash算法。
- 1、B+树算法
??B+树是最常用的Mysql数据库索引算法,也是Mysql默认的算法。因为它不仅可以被用在=,>,>=,<,<=和between这些比较操作符上,而且还可以用于like操作符,只要它的查询条件是一个不以通配符开头的常量 ,示例(以下查询可以用到索引):
select * from user where name like 'jack%';
????B+树索引:如果以通配符开头,或者没有使用常量,则不会使用索引,例如:
select * from user where name like '%jack';
- 2、哈希算法
??哈希索引只能用于对等比较 ,例如=,<=>(相当于=)操作符。由于是一次定位数据,不像B+树索引需要从根节点到枝节点,最后才能访问到页节点这样多次IO访问,所以检索效率远高于B+树索引。 ??哈希索引也适用于单条查询的情况,因为底层实现是哈希表,无序 。
1.13.3 Hash索引的缺点
- hash索引只能适用于精确的值比较,=,in,或者<>;无法使用范围查询;
- 无法使用索引排序;
- 组合hash索引无法使用部分索引;
- 如果大量索引hash值相同,性能较低。
1.13.4 Hash索引和B+树索引的比较
??Hash索引底层就是Hash表 ,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树。对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。 ??那么可以看出他们有以下的不同:
| 哈希索引 | B+树索引 | 说明 |
---|
是否支持范围查询 | 不支持 | 支持 | 在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。 B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围。 | 是否支持模糊查询 | 不支持 | 支持 | hash函数的不可预测 | 是否能避免回表查询 | 不能 | 在一定条件下可避免 | B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询 | 性能是否稳定 | 不稳定 | 稳定 | 哈希索引性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差。
B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低 。 |
??因此,在大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度 。
1.14 B树索引和B+树索引的区别
在B树中,将键和值存放在非叶子节点和叶子节点 ;在B+树中,非叶子节点都是键,没有值,叶子节点同时存放键和值 。B树的叶子节点各自独立;B+树的叶子节点有一条链相连 。
1.14.1 B树和B+树的好处
- 1、B树的好处
??B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率 。这种特性使得B树在特定数据重复多次查询的场景中更加高效 。 - 2、B+树的好处
??由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围 。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间。
1.14.2 数据库为什么使用B+树而不是B树
B树只适合随机检索,而B+树同时支持随机检索和顺序检索 B+树空间利用率更高,可减少I/O次数,磁盘读写代价更低 ??一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗。B+树的内部结点并没有指向关键字具体信息的指针,只是作为索引使用,其内部结点比B树小,盘块能容纳的结点中关键字数量更多,一次性读入内存中可以查找的关键字也就越多,相对的,IO读写次数也就降低了。而IO读写次数是影响索引检索效率的最大因素;B+树的查询效率更加稳定 ??B树搜索有可能会在非叶子结点结束,越靠近根节点的记录查找时间越短,只要找到关键字即可确定记录的存在,其性能等价于在关键字全集内做一次二分查找。而在B+树中,顺序检索比较明显,随机检索时,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径长度相同,导致每一个关键字的查询效率相当。增删文件(节点)时,效率更高 ??因为B+树的叶子节点包含所有关键字,并以有序的链表结构存储,这样可很好提高增删效率。
1.15 聚簇索引与非聚簇索引
聚簇索引:将数据存储与索引放到了一块 ,找到索引也就找到了数据。非聚簇索引:将数据存储和索引分开结构 ,索引结构的叶子节点指向了数据的对应行,
??Myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因 ??Innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值。 ??何时使用聚簇索引与非聚簇索引:
行为 | 是否应该使用聚簇索引 | 是否应该使用非聚簇索引 |
---|
列经常被分组排序 | 是 | 是 | 返回某范围内的数据 | 是 | 否 | 一个或极少不同值 | 否 | 否 | 小数目的不同值 | 是 | 否 | 大数目的不同值 | 否 | 是 | 频繁更新的列 | 否 | 是 | 外键列 | 是 | 是 | 主键列 | 是 | 是 | 频繁修改索引列 | 否 | 是 |
1.15.1 两者的区别
??根本区别:聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致 。 ??聚集索引表记录的排列顺序和索引的排列顺序一致,所以查询效率快,只要找到第一个索引值记录,其余就连续性的记录在物理也一样连续存放。聚集索引对应的缺点就是修改慢,因为为了保证表中记录的物理和索引顺序一致,在记录插入的时候,会对数据页重新排序。 ??非聚集索引制定了表中记录的逻辑顺序,但是记录的物理和索引不一定一致,两种索引都采用B+树结构,非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针方式。非聚集索引层次多,不会造成数据重排。
1.15.2 非聚簇索引一定会回表查询吗
??此处要先提一句什么是回表:
??非主键索引,我们先通过索引找到主键索引的键值,再通过主键值查出索引里面没有的数据,它比基于主键索引的查询多扫描了一棵索引树,这个过程就叫回表 。
??不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询 。 ??举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。
1.15.3 Innodb中的主键索引和辅助索引
??聚集索引:索引键值的逻辑顺序跟表数据行的物理存储顺序是一致的 。(比如字典的目录是按拼音排序的,内容也是按拼音排序的,按拼音排序的这种目录就叫聚集索引)。 &emsp?在 InnoDB 里面,它组织数据的方式叫做叫做(聚集)索引组织表,主键索引是聚集索引,非主键都是非聚集索引。 &emsp?InnoDB 中,主键索引和辅助索引是有一个主次之分的。辅助索引存储的是辅助索引和主键值。如果使用辅助索引查询,会根据主键值在主键索引中查询,最终取得数据。 ??如果一张表没有主键怎么办?
- 如果定义了主键(PRIMARY KEY),那么 InnoDB 会选择主键作为聚集索引。
- 如果没有显式定义主键,则 InnoDB 会选择第一个不包含有 NULL 值的唯一索引作为主键索引。
- 如果也没有这样的唯一索引,则 InnoDB 会选择内置 6 字节长的 ROWID 作为隐藏的聚集索引,它会随着行数据的写入而主键递增。
1.16 索引的相关问题
1.16.1 百万级别或以上的数据如何删除
??关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。 ??因此,要删除有很多索引的表中数据的建议:
- 所以
想要删除百万数据的时候可以先删除索引 ; - 然后
删除其中无用数据 ; 删除完成后重新创建索引 。
??与直接删除相比,绝对是要快速很多;并且万一删除中断,一切删除会回滚。
1.16.2 查看索引使用情况
??如果索引正在工作,Handler_read_key的值将很高,这个值代表了一个行被索引值读的次数,很低的值表明增加索引得到的性能改善不高,因为索引并不经常使用。 ??Handler_read_rnd_next的值高则意味着查询运行低效,并且应该建立索引补救。这个值的含义是在数据文件中读下一行的请求数。如果正进行大量的表扫描,Handler_read_rnd_next 的值较高,则通常说明表索引不正确或写入的查询没有利用索引。 ??命令示例:
show status like 'Handler_read%';
??结果示例:
1.16.3 索引条件下推
??索引条件下推优化(Index Condition Pushdown (ICP) )是MySQL5.6添加的,用于优化数据查询。不使用索引条件下推优化时存储引擎通过索引检索到数据,然后返回给MySQL服务器,服务器然后判断数据是否符合条件。 ??当使用索引条件下推优化时,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器 。索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。 ??开启 ICP:
set optimizer_switch='index_condition_pushdown=on';
1.16.4 为什么要尽量设定一个主键
??主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键。设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全。
1.1.6.5 主键使用自增ID还是UUID
??推荐使用自增ID,不要使用UUID。 ??因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降。 ??总之,在数据量大一些的情况下,用自增主键性能会好一些 。 ??关于主键是聚簇索引,如果没有主键,InnoDB会选择一个唯一键来作为聚簇索引,如果没有唯一键,会生成一个隐式的主键。
二、视图
2.1 视图的基本操作
??视图,本质上是一种虚拟表,在物理上是不存在的 ,其内容与真实的表相似,包含一系列带有名称的列和行数据。 ??视图的建立和删除不影响基本表,但视图的数据变化会影响到基表,基表的数据变化(增删改)也会影响到视图。 ??创建视图需要create view 权限,并且对于查询涉及的列有select权限;使用create or replace 或者 alter修改视图,那么还需要改视图的drop权限。 ??当视图来自多个基本表时,不允许添加和删除数据 。 ??视图的操作包括创建视图,查看视图,删除视图和修改视图。
CREATE VIEW 视图名 AS SELECT 字段名 FROM 表名;
ALTER VIEW 视图名 AS SELECT 语句
ALTER VIEW 视图名 AS SELECT 视图;
SHOW CREATE VIEW 视图名;
DROP VIEW 视图名;
RENAME TABLE 视图名 TO 新视图名;
2.2 视图的使用场景
??视图根本用途:简化sql查询,提高开发效率。视图的常见使用场景:
- 1、重用SQL语句;
- 2、使用表的部分字段而不是整个表;
- 3、保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限;
- 4、更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
2.3 视图的优点
- 1、查询简单化
??视图能简化用户的操作. - 2、数据安全性
??视图使用户能以多种角度看待同一数据,能够对机密数据提供安全保护。 - 3、逻辑数据独立性
??视图对重构数据库提供了一定程度的逻辑独立性。
2.4 视图的缺点
- 1、性能:从数据库视图查询数据可能会很慢,特别是如果视图是基于其他视图创建的。
- 2、表依赖关系:将根据数据库的基础表创建一个视图。每当更改与其相关联的表的结构时,都必须更改视图。
三、存储过程
??存储过程就是为了以后的使用而保存的一条或者多条MySQL语句的集合。可将视为批文件,虽然他们的作用不仅限于批处理。
3.1 储存过程的优缺点
3.1.1 储存过程的特点
- 通过把处理封装在容易使用的单元中,
简化复杂的操作 ; - 由于不要求反复建立一系列处理步骤,
保证了数据的完整性 。如果所有开发人员和应用程序都使用同一(实验和测试)存储过程,则所使用的代码都是相同的。这一点的延伸就是防止错误。需要执行的步骤越多,出错的可能性就越大,防止错误保证了数据的一致性。 简化对变动的管理 ,如果表名。列名或者业务逻辑等有变化,只需要更改存储过程的代码。使用它的人员甚至不需要知道这些变化。这一点延伸就是安全性,通过存储过程限制对基数据的访问减少了数据讹误的机会。提高性能 。因为使用存储过程比使用单独的 sql 语句更快。灵活性更好 。存在一些只能用在单个请求的 MySQL 元素和特性,存储过程可以使用他们来编写功能更强更灵活的代码。
3.1.2 储存过程的优点
??三个主要的好处:简单、安全、高性能。
3.1.3 储存过程的缺点
- 存储过程的编写更为复杂。
- 可能没有创建存储过程的安全访问权限。许多数据库管理员限制存储过程的创建权限,允许使用,不允许创建。
3.2 执行储存过程
??Call 关键字:Call 接受存储过程的名字以及需要传递给他的任意参数。存储过程可以显示结果,也可以不显示结果。创建存储过程示例:
CREATE PROCEDURE productpricing()
BEGIN
SELECT AVG( prod_price) as priceaverage FROM products;
END;
??创建名为productpricing的储存过程。如果存储过程中需要传递参数,则将他们在括号中列举出来即可。括号必须有。BEGIN 和 END 关键字用来限制存储过程体。上述存储过程体本身是一个简单的 select 语句。注意这里只是创建存储过程并没有进行调用。 ??储存过程的使用:Call productpring(); 。
3.3 使用参数的存储过程
??一般存储过程并不显示结果,而是把结果返回给你指定的变量上。 ??变量:内存中一个特定的位置,用来临时存储数据。 ??示例:
MySQL> CREATE PROCEDURE prod(
out pl decimal(8,2),
out ph decimal(8,2),
out pa decimal(8,2)
)
begin
select Min(prod_price) into pl from products;
select MAx(prod_price) into ph from products;
select avg(prod_price) into pa from products;
end;
call PROCEDURE(@pricelow,@pricehigh,@priceaverage);
select @pricelow;
select @pricehigh;
select @pricelow,@pricehigh,@priceaverage;
??此存储过程接受 3 个参数,pl 存储产品最低价,ph 存储产品最高价,pa 存储产品平均价。每个参数必须指定类型,使用的为十进制,关键字 OUT 指出相应的参数用来从存储过程传出一个值(返回给调用者)。 ??MySQL支持in(传递给存储过程)、out(从存储过程传出,这里所用)和inout(对存储过程传入和传出)类型的参数。存储过程的代码位于begin和end语句内。他们是一系列select语句,用来检索值。然后保存到相对应的变量(通过INTO关键字)。 ??存储过程的参数允许的数据类型与表中使用的类型相同。注意记录集是不被允许的类型,因此,不能通过一个参数返回多个行和列,这也是上面为什么要使用3个参数和3条select语句的原因。 ??调用:为调用此存储过程,必须指定 3 个变量名。如上所示。3 个参数是存储过程保存结果的3个变量的名字。调用时,语句并不显示任何数据,它返回以后可以显示的变量(或在其他处理中使用)。 ??所有的 MySQL 变量都是以@开头 。示例:
CREATE PROCEDURE ordertotal(
IN innumber int,
OUT outtotal decimal(8,2)
)
BEGIN
SELECT Sum(item_price * quantity) FROM orderitems WHERE order_num = innumber INTO outtotal;
end
CALL ordertotal(20005,@total);
select @total;
CALL ordertotal(20009,@total);
select @total;
四、触发器
??在某个表发生更改时自动处理某些语句,这就是触发器。 ??触发器是MySQL响应delete 、update 、insert 、位于begin和end语句之间的一组语句而自动执行的一条MySQL语句。其他的语句不支持触发器。
4.1 创建触发器
??在创建触发器时,需要给出 4 条语句(规则):
- 唯一的触发器名;
- 触发器关联的表;
- 触发器应该响应的活动;
- 触发器何时执行(处理之前或者之后)
??创建触发器语句示例:
CREATE TRIGGER newproduct AFTER INSERT ON products FOR EACH ROW SELECT
'Product added' INTO @info;
??CREATE TRIGGER用来创建名为newproduct的新触发器。触发器可以在一个操作发生前或者发生后执行,这里AFTER INSERT是指此触发器在INSERT语句成功执行后执行。这个触发器还指定FOR EACH ROW, 因此代码对每个插入行都会执行。文本Product added将对每个插入的行显示一次。 ??注意事项:
- 触发器只有表才支持,视图,临时表都不支持触发器。
- 触发器是按照每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器,因此,每个表最多支持六个触发器(insert,update,delete 的 before 和 after)。
- 单一触发器不能与多个事件或多个表关联,所以,你需要一个对insert和update操作执行的触发器,则应该定义两个触发器。
- 触发器失败:如果before触发器失败,则MySQL将不执行请求的操作,此外,如果before触发器或者语句本身失败,MySQL则将不执行after触发器。
4.2 触发器的类别
4.2.1 INSERT触发器
??是在 insert 语句执行之前或者执行之后被执行的触发器。示例:
CREATE TRIGGER neworder AFTER INSERT ON orders FOR EACH ROW SELECT
NEW.order_num;
??创建一个名为neworder的触发器,按照AFTER INSERT ON orders执行。在插入一个新订单到orders表时,MySQL 生成一个新的订单号并保存到order_num中。触发器从NEW.order_num取得这个值并返回它。此触发器必须按照AFTER INSERT执行,因为在BEFORE INSERT语句执行之前,新order_num还没有生成。对于orders的每次插入使用这个触发器总是返回新的订单号。
4.2.2 DELETE触发器
??Delete 触发器在 delete 语句执行之前或者之后执行。示例:
CREATE TRIGGER deleteorder BEFORE DELETE ON orders FOR EACH ROW
BEGIN
INSERT INTO archive_orders(order_num,order_date,cust_id) values
(OLD.order_num,OLD.order_date,OLD.cust_id);
END;
??在任意订单被删除前将执行此触发器,它使用一条INSERT语句将OLD中的值(要被删除的订单) 保存到一个名为archive_orders的存档表中。 ??在这个触发器使用了BEGIN和END语句标记触发器体。这在此例子中并不是必须的,只是为了说明使用BEGIN END块的好处是触发器能够容纳多条SQL语句(在BEGIN END块中一条挨着一条)。
4.2.3 UPDATE触发器
??在update语句执行之前或者之后执行。示例:
CREATE TRIGGER updatevendor BEFORE UPDATE ON vendors FOR EACH ROW SET
NEW.vend_state = Upper(NEW.vemd_state);
??保证州名缩写总是大写(不管UPFATE语句中是否给出了大写),每次更新一行时,NEW.vend_state中的值(将用来更新表行的值)都用Upper(NEW.vend_state)替换。
4.3 总结
- 通常before用于数据的验证和净化(为了保证插入表中的数据确实是需要的数据) ,也适用于update触发器。
- 创建触发器可能需要特殊的安全访问权限,但是触发器的执行时自动的,如果insert,update,或者delete语句能够执行,则相关的触发器也能执行。
- 用触发器来保证数据的一致性(大小写,格式等)。在触发器中执行这种类型的处理的优点就是它总是进行这种处理,而且透明的进行,与客户机应用无关。
- 触发器的一种非常有意义的使用就是创建审计跟踪。使用触发器,把更改(如果需要,甚至还有之前和之后的状态)记录到另外一个表是非常容易的。
- MySQL触发器不支持call语句,无法从触发器内调用存储过程。
五、分区表
??分区表是一个独立的逻辑表,其底层由多个物理子表组成。 ??分区非常适合在以下场景:
1)表非常大以至于无法全部放在内存中,或者只在表的最后部分有热点数据,其他均为历史数据。 2)分区表的数据更容易维护。(批量删除数据->清除整个分区)。 3)分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备。 4)可以使用分区表来避免某些特殊的瓶颈。、比如 InnoDB 的单个索引的互斥访问,ext3 文件系统的 inode 锁竞争。 5)还可以备份和恢复独立的分区。
??分区表也有一些限制:
1)一个表最多只能有1024个分区。 2)如果分区字段有主键或者唯一索引,那么所有主键列和唯一索引列都必须包含进来。 3)分区表中无法使用外键索引。
5.1 分区表原理
??存储引擎管理分区的各个底层表和管理普通表一样,所有的底层表都必须使用相同的存储引擎,分区表的索引只是在各个底层表上各自加一个完全相同的索引。从存储引擎的角度,底层表和一个普通表没有任何不同。
- select
??分区层先打开并锁住所有的底层表,优化器先判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据。 - insert
??当写入一条记录时,分区层先打开并锁住所有的底层表,然后确定哪个分区接收这条记录,再将记录写入对应底层表。 - delete
??当删除一条记录时,分区层先打开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层表进行删除操作。 - update
??当更新一条记录时,分区层先打开并锁住所有的底层表,MySQL 先确定需要更新的记录在哪个分区,然后取出数据并更新,再判断更新后的数据应该放在哪个分区,最后对底层表进行写入操作,并对原数据所在的底层表进行删除操作。
??虽然每个操作都会先打开并锁住所有的底层表,但这并不是分区表在处理过程中是锁住全表的,如果存储引擎能够自己实现行级锁,则会在分区层释放对应表锁,比如InnoDB,这个加锁和解锁的过程与普通InnoDB上的查询类似。
5.2 分区表的常见类型
5.2.1 Range分区
??MySQL将会根据指定的拆分策略,把数据放在不同的表文件上。相当于在文件上被拆成了小块。但对外给客户的感觉还是一张表,是透明的。 ??示例:
CREATE TABLE tbl_new(
id INT NOT NULL PRIMARY KEY,
title VARCHAR(20) NOT NULL DEFAULT ''
)ENGINE MYISAM CHARSET utf8
PARTITION BY RANGE(id)(
PARTITION t0 VALUES LESS THAN(10),
PARTITION t1 VALUES LESS THAN(20),
PARTITION t2 VALUES LESS THAN(MAXVALUE)
);
??0到10放在t0,10到20放在t1,大于20的放在t2。 ??如果要查询id在20以上的,那么会直接去t2分区查找。如果插入的记录的id在20以上,那么会插入到t2分区。 ??此时的物理文件: ??可以看出,普通的InnoDB引擎的表是一个frm和一个ibd文件。分区之后的MyIasm引擎的表有一个frm和par文件,此外每个分区还有一个myi和myd文件。
frm:表的结构信息 par:表的分区信息 myi:表的索引信息 myd:表的数据信息
5.2.2 List分区
??MySQL中的LIST分区在很多方面类似于RANGE分区。和按照RANGE分区一样,每个分区必须明确定义。它们的主要区别在于:
LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值; RANGE分区是从属于一个连续区间值的集合。
??示例:
create table user (
uid int not null,
userName varchar(20),
area_id int
)engine myisam charset utf8
partition by list(area_id) (
partition bj values in (1),
partition sh values in (2),
partition gz values in (3),
partition sz values in (4)
);
5.3 分区表的缺点
- 1、NULL值会使分区过滤无效
??分区的表达式的值可以是NULL;第一个分区是一个特殊分区,如果表达式的值为NULL或非法制,记录都会被存放到第一个分区。WHERE查询时即使看起来可以过滤到只有一个分区,但实际会检查两个分区,即第一个分区。最好是设置分区的列为NOT NULL。 - 2、分区列和索引列不匹配
??如果定义的索引列和分区列不匹配,会导致索引无法进行分区过滤。 ??假设在列a上定义了索引,而在列b上进行分区。因为每个分区都有其独立的索引,所以扫描b上的索引就需要扫描每一个分区内对应的索引。 - 3、选择分区的成本可能很高
??尤其是范围分区,对于回答“这一行属于哪个分区”、“这些符合查询条件的行在哪些分区”这样的问题的成本可能会非常高。其他的分区类型,比如键分区和哈希分区,就没有这样的问题。在批量插入时问题尤其严重。
|