MySQL的架构跟我们javaweb开发是非常类似的,前排发送一个请求到controller->service->dao,在由dao去访问数据库,这就是web开发的三层架构,再看MySQL架构
功能 | MyISAM | MEMORY | InnoDB | Archive |
---|---|---|---|---|
存储限制 | 256TB | RAM | 64TB | None |
支持事务 | No | No | Yes | No |
支持全文索引 | Yes | No | No | No |
支持树索引 | Yes | Yes | Yes | No |
支持哈希索引 | No | Yes | No | No |
支持数据缓存 | No | N/A | Yes | No |
支持外键 | No | No | Yes | No |
详见: http://dev.mysql.com/doc/refman/5.6/en/storage-engines.html
另见MySQL旗下的NDB集群: https://cloud.tencent.com/developer/article/1707955
InnoDB 事务型数据库的首选引擎,支持事务(ACID),支持行级锁和外键。支持数据恢复。MySQL 5.5.5 之后,InnoDB 作为默认存储引擎。
MyISAM 是基于 ISAM 的存储引擎,并对其进行扩展,是在 Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM 拥有较高的插入、查询速度,但不支持事务。支持全文索引
MEMORY 存储引擎将表中的数据存储到内存中,为查询和引用其他数据提供快速访问, MySQL 中使用该引擎作为临时表,存放查询的中间结果。
mysql的全局功能在核心服务层实现,特性功能在数据库引擎层实现。
和大多数的数据库不同, MySQL中有一个存储弓|擎的概念,针对不同的存储需求可以选择最优的存储引擎。
存储引擎就是存储数据,建立索引,更新查询数据等等技术的实现方式。存储引擎是基于表的,而不是基于库的。所以存储引擎也可被称为表类型。
Oracle , SqIServer等数据库只有一种存储引擎。 MySQL提供 了插件式的存储引擎架构。所以MySQL存在多种存储引擎,可以根据需要使用相应引擎,或者编写存储引擎。
MySQL5.0支持的存储引擎包含: InnoDB 、MyISAM 、BDB、MEMORY、 MERGE、EXAMPLE、 NDB Cluster、ARCHIVE、 CSV、
BLACKHOLE、FEDERATED等 ,其中InnoDB和BDB提供事务安全表,其他存储引擎是非事务安全表。
可以通过指定show engines,来查询当 前数据库支持的存储引擎
创建新表时如果不指定存储引擎,那么系统就会使用默认的存储引擎, MySQL5.5之前的默认存储引擎是MylSAM,5.5之后就改为了InnoDB。
只有innoDB支持行级锁,并且支持外键,本文重点讲innoDB
隔离级别…
事务特性…
查看数据库的默认存储引擎:show variables like ’ storage_engine% ’
### 2.2 InnoDB
InnoDB存储予|擎是Mysq|的默认存储引擎。InnoDB存储弓 |擎提供了具有提交、回滚、崩溃恢复能力的事务安全。但是对比MyISAM的存储引擎, InnoDB写的处理效率差一些,并且会占用更多的磁盘空间以保留数据和索引。
测试:先准备一个表city
创建两个服务在顺便插入一条数据
接下来我们要验证一下事务是否真的存在
首先手动开启事务
start transaction
在插入一条数据
这是因为mysql数据库默认的隔离级别是可重复读,所以这个时候你并没有提交,需要自己手动提交
这就是事务控制,它的特点之一
MySQL支持外键的存储引擎只有InnoDB,在创建外键的时候,要求父表必须有对应的索引, 子表在创建外键的时候,也会自动的创建对
应的索引。由于篇幅关系,本文暂且不讲外键约束事例
索引(在MySQL中也叫做"键key") 是存储引擎用于快速找到记录的一种数据结构。索引相当于书的目录,快速定位到记录所在位置,用额外的数据存储来加快查询。
索引需要额外的数据存储,索引是在数据之外的一份独立存储。具体来说,innodb的数据文件和索引文件都是统一存放在ibdata文件table_name.ibd)中。因此查询和维护索引都是需要额外的运算和IO。
当然,索引本身的数据结构也极大影响索引的查询速度的。
索引优点如下:
1.索引大大减少了服务器需要扫描的数据量。
2.索引可以帮助服务器避免排序和临时表。
3.索引可以将随机I/O变为顺序I/O.
缺点如下:
索引是存储引擎层的,而常用的数据引擎是innodb,因此以下讨论都是以innodb为例。
从存储形式分为:聚簇和非聚簇索引(区分在于是否是主键,主键就是聚簇)
从数据结构分为:B-tree索引,哈希索引,空间数据索引,全文索引等
从附加约束分为:普通索引、唯一索引、主键索引
从涉及到的列分为:单列索引,多列索引(或称组合索引)
从效果来看还有 覆盖索引(多列索引时索引列覆盖了查询条件和返回值)
注意,不同角度对索引的定义是不同的,不可混在一起讨论。
drop table if exists emp;
-- 创建时添加 可以选择unique 或者不写, primary key也是主键索引
create table emp(
emp_id int not null auto_increment primary key comment '自增ID',
emp_no int not null comment '工号',
name varchar(256) not null comment '名字',
unique key (emp_no,name)
);
-- 事后加唯一索引,这里是多个字段的联合索引
create unique index emp_no_name_idx on emp2(emp_no,name);
drop table if exists emp;
create table emp(
emp_id int not null auto_increment primary key comment '自增ID',
emp_no int not null comment '工号',
name varchar(256) not null comment '名字',
index(emp_no,name(10))
);
-- 加普通索引,这里是多个字段的联合索引,name是前缀索引
create index emp_no_name_idx on emp2(emp_no,name(10));
--修改
ALTER TABLE table_name ADD INDEX index_name ON (column(length))
--删除
DROP INDEX index_name ON table
innodb使用的是聚簇索引来存储数据。聚簇索引 不是一种独立的索引类型,而是一种存储方式。具体的细节依赖于实现方式,但innodb的聚簇索引实际上在同一个结构中保存了B-Tree索引和数据行。
**当表有聚簇索引时,它的数据行实际上存放在索引所在的叶子页上。**术语“聚簇”表示数据行与相邻的索引紧凑的存储在一起,另一种说法是索引组织表。一个表只能有一个聚簇索引,因为不能把数据行放在两个地方。
innodb默认以主键为聚簇索引。如果没有主键,则选择一个唯一的非空索引代替,如果也没有,innodb会隐式定义一个主键来作为聚簇索引。
innodb只支持主键作为聚簇索引,因此聚簇索引主键索引,非聚簇索引或称二级索引非主键索引。
为了搞清楚聚簇与非聚簇的区别,我们以下一张图来讨论,左边是innodb的聚簇索引,右边是非聚簇索引
二级索引是相对主键索引而言的,本质上的区别是聚簇与非聚簇。主键索引的索引和数据行是放在一起的,而二级索引的索引和数据行是分开,所以当在二级索引定位到数据,需要重新去主键索引(聚簇索引)里去定位数据行,这个过程称为“回表”。回表多了一次磁盘IO,所以性能必然是大大小于不回表的。
因此,高性能索引策略之一,就是能用主键索引查询就一定要用主键索引。在特定业务情况下创建多列索引作为主键索引,也可以显著提高性能,但不是普遍适用。
回表图示
innodb的聚簇索引指的是主键和数据行存放在一起,所以会按照主键顺序插入数据。
如果插入的主键不是顺序的,那么一方面需要遍历查找,如果不在内存中则需要进行磁盘IO;而且,随机插入会导致数据移动,在innodb的聚簇索引实现上,会造成页分裂,即造成大量的数据移动,性能消耗极大。
这里补充下,“页分裂”中的页其实是innodb的物理存储单位。引发页分裂的原因是:因为主键的值是顺序的,所以InnoDB把每一条记录都存储在上一条记录的后面。当达到页的最大填充因子时(InnoDB默认的最大填充因子是页大小的15/16,留出部分空间用于以后修改),下一条记录就会写人新的页中。一旦数据按照这种顺序的方式加载,主键页就会近似于被顺序的记录填满,这也正是所期望的结果(然而,二级索引页可能是不一样的)。
如果主键不是顺序的(比如用UUID),因为新行的主键值不一定比之前插入的大,所以InnoDB无法简单地总是把新行插人到索引的最后,而是需要为新的行寻找合适的位置一通 常是已有数据的中间位置一并且分配空间。这会增加很多的额外工作,并导致数据分布不够优化。
关于具体存储结构查考文章:https://www.percona.com/blog/2017/04/10/innodb-page-merging-and-page-splitting/
译文:https://blog.csdn.net/gududedabai/article/details/103782590
更新的代价也很大,如果将主键从1改为100那就需要强制将整个主键索引和数据行一起移动,其他数据行也需要相应变动。
另外,刚刚提到 二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列。而且每次查找都需要回表,性能会受影响。
所以高性能索引策略之二
B-Tree是一种数据结构,是一种平衡搜索树(B不是Binary而是Balance)。作为树,它的搜索(比链表高很多)和更新(比数组高很多)的效率都比较高。
如下图:
图摘自《高性能MySQL(第三版)》
另一个例子:
CREATE TABLE People (
last_name varchar(50) not null,
first_name varchar(50)not null,
dob date not null,
gender enum( 'm','f') not null,
primary key(last_name, first_name, dob) --添加多列索引
);
补充下硬盘物理存储的相关知识
为什么MySQL选择B+树做索引
1、 B+树的磁盘读写代价更低:B+树的内部节点并没有指向PrimaryKey具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的主键存放在同一盘块中,那么盘块所能容纳的主键数量也越多,一次性读入内存的需要查找的主键也就越多,相对IO读写次数就降低了。
2、B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中主键的索引。所以任何主键的查找必须走一条从根结点到叶子结点的路。所有主键查询的路径长度相同,导致每一个数据的查询效率相当。
3、B+树更便于遍历:由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。
4、B+树更适合基于范围的查询:B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作或者说效率太低。
参考:
Mysql索引BTree、B+Tree详解:https://www.jianshu.com/p/d67c637776d6
【动画】B-tree来由与运作: https://blog.csdn.net/qq_42146775/article/details/101449444
B树与B+树的区别: https://www.jianshu.com/p/7ce804f97967
可以使用B-Tree索引的查询类型。B-Tree 索引适用于全键值、键值范围或键前缀查找。其中键前缀查找只适用于根据最左前缀的查找生了。前面所述的索引对如下类型的查询有效。
全值匹配
全值匹配指的是和索引中的所有列进行匹配,例如前面提到的索引可用于查找姓名为Cuba Allen、出生于1960-01-01的人。
匹配最左前缀
前面提到的索引可用于查找所有姓为Allen的人,即只使用索引的第一列。
key(lastname,firstname,dob)
where lastname=‘Allen’ √
where lastname=‘Allen’ and firstname=‘Cuba’ √
where firstname=‘Cuba’ x
匹配列前缀
也可以只匹配某一列的值的开头部分。例如前面提到的索引可用于查找所有以J开头的姓的人。这里也只使用了索引的第一列。Aus Auster Austing EllenAusting
like Aus% like %Aus
匹配范围值
例如前面提到的索引可用于查找姓在Allen和Barrymore之间的人。这里也只使用了索引的第一列。
精确匹配某一列并范围匹配另外一列
前面提到的索引也可用于查找所有姓为Allen,并且名字是字母K开头(比如Kim、Karl等)的人。即第一列last_name全匹配,第二列first_name 范围匹配。
where lastname=‘Allen’ and firstname like ‘Barr%’ √
where lastname=‘Allen’ and dob = ‘1960-01-01’
只访问索引的查询
B-Tree通常可以支持“只访问索引的查询”,即查询只需要访问索引,而无须访问数据行。后面我们将单独讨论这种“覆盖索引”的优化。
在多列索引情况下,索引失效的情况就是:
name like 'J%'
可以有效,但是 name like '%ames'
则是无效的,这种情况可以通过新建一个列存入反转后的值来优化 (semaJ sema%)唯一索引主键索引是索引吗?是索引,在索引基础上加上了唯一或者主键的限制
适合的数量级才建索引,数据量很小的表(几千)不需要建索引,全表扫描性能最高
使用单独业务无关的自增主键,主键索引需要保证连续性,一般创建一个单纯的自增的int字段作为主键是很好地实践。查询时能用主键的一定要用主键,性能与其他字段差距极大
频繁查询字段考虑加索引,但不是机械的加单列索引,根据业务分析及排序、分组需要添加合适索引(索引用来排序和分组是非常快的,分组或者排序条件的字段也要考虑进来)
优先考虑多列索引而不是单列索引,优点是可能会达到极高效率
考虑合适的索引列顺序,根据最左前缀原则,B+ 树这种索引结构,索引项是按照索引定义里面出现的字段顺序排序的,每次先用最左边的字段来匹配,然后是第二个左边的。字符串的也是一样。所以我们根据具体应用场景通过合理的调整索引顺序,可以提高索引的效率。
对于长字符串考虑前缀索引(不是索引前缀),如果字符串过长,直接索引会造成很大成本影响效率,可以考虑创建一个前缀索引,可以大大提高索引效率,但是会降低索引的选择性。
选择性(唯一性)太差的字段不适合单独创建索引,即使频繁作为查询条件,因为索引筛选结束后仍然访问大量数据行;
更新非常频繁的字段不适合创建索引,因为的索引的创建和维护也需要工作量,更新频繁所带来的索引维护工作的性能消耗也很大
-- actor+1 已经不是单独的列了,改写成actor_id=4就可以
SELECT actor_id FROM sakila.actor WHERE actor_id+1=5;
-- 用作了函数参数
SELECT ... WHERE TO DAYS(CURRENT.DATE) - TO_DAYS(date_col) <= 10;
name like 'J%'
可以有效,但是 name like '%ames'
则是无效的,这种情况可以通过新建一个列存入反转后的值来优化