在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。
索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。
建立在表上,是对表中一列或多列值进行排序的一种结构
索引和表中属性是有很大关系,要通过属性排序之后的结果建立结构
存储文件中
InnoDB:.frm 存储表的结构 .ibd 存储表中的数据和索引
MyIsam: .frm 存储表的结构 .myi: 存储索引 .myd:存储数据
普通索引
最基本的索引类型,没有唯一性之类的限制。普通索引可以通过以下几种方式创建:
创建索引,例如CREATE INDEX <索引的名字> ON tablename (列的列表);
修改表,例如ALTER TABLE tablename ADD INDEX [索引的名字] (列的列表); [3]
创建表的时候指定索引,例如CREATE TABLE tablename ( […], INDEX [索引的名字] (列的列表) );
** 唯一索引 **
唯一索引是不允许其中任何两行具有相同索引值的索引。
当现有数据中存在重复的键值时,大多数数据库不允许将新创建的唯一索引与表一起保存。数据库还可能防止添加将在表中创建重复键值的新数据。
例如,如果在 employee 表中职员的姓 (lname) 上创建了唯一索引,则任何两个员工都不能同姓。
对某个列建立UNIQUE索引后,插入新记录时,数据库管理系统会自动检查新纪录在该列上是否取了重复值,在CREATE TABLE 命令中的UNIQE约束将隐式创建UNIQUE索引。
创建唯一索引的几种方式:
创建索引,
CREATE UNIQUE INDEX <索引的名字> ON tablename (列的列表);
修改表,
ALTER TABLE tablename ADD UNIQUE [索引的名字] (列的列表);
创建表的时候指定索引
CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (列的列表) );
全文索引
对文本的内容进行分词,进行搜索
** 单列索引 **
ALTER TABLE people ADD INDEX lname (lname);
将lname列建索引,这样就把范围限制在lname='Liu’的结果集1上,之后扫描结果集1,产生满足fname='Zhiqun’的结果集2,再扫描结果集2,找到 age=26的结果集3,即最终结果。
由于建立了lname列的索引,与执行表的完全扫描相比,效率提高了很多,但我们要求扫描的记录数量仍旧远远超过了实际所需 要的。
虽然我们可以删除lname列上的索引,再创建fname或者age 列的索引,但是,不论在哪个列上创建索引搜索效率仍旧相似。
** 多列索引 **
ALTER TABLE people ADD INDEX lname_fname_age (lame,fname,age);
为了提高搜索效率
注意:上例中我们创建了lname_fname_age多列索引,相当于创建了(lname)单列索引,(lname,fname)组合索引以及(lname,fname,age)组合索引。
创建多列索引时要根据业务需求,把where子句使用最频繁的一列放在最左边
** 空间索引**
必须选择空间类型字段
(1)数据量比较低的时候不需要建立索引。
(2)选择唯一性索引。属性是不重复的 ,索引价值百分之百 属性:建立索引时所选择的的那个属性
索引价值问题:索引价值=distinct(属性)/count(属性)
count(属性):属性总数
distinct(属性):属性是不重复的数量
(3)经常使用的属性上建立索引 建立索引的时候选择哪一个属性,只有使用时也选择那一个属于性
index(a)
select * from user where b >10;//用不上在a字段上建立索引
(4) 4,8 限制索引数目 删除不再使用或者很少使用的索引
(5) 5,7 针对在字符串类型上建立的索引而言 尽量使用数量少的索引
user name vachar(50) char text
index(name(6)); //长度字段是影响比较结果,也就是影响B+树的结构
还影响比较的时间:长度越长比较多的时间越长。
abcdefg 最小
abcdezca 第二小
abcdezcf 最大
(1)在创建表的时候创建索引
//(1)普通索引:
create table index1(id int,
name varchar(20),
sex boolean,
index(id)
);
show create table index1;
explain select * from index1 where id=1;
//(2)创建唯一性索引
create table index2(id int unique,
name varchar(20),
unique index index2_id(id asc)
);
//(3)创建全文索引
create table index3(id int,
info varchar(20),
fulltext index index3_info(info)
)engine=MyIsam;
//(4)创建单列索引
create table index4(id int,
subject varchar(30),
index index4_(subject(10))
);
//(5)创建多列索引
create table index5(id int,
name varchar(20),
sex char(4),
index index4_ns(name,sex)
);
//(6)创建空间索引
create table index6(id int,
space geometry not null,
spatial index index6_sp(space)
)ENGINE=MyISAM;
(2)在已存在的表上创建索引
(1)普通:create index index7 on 表名 (id);
(2)唯一:create unique index index8 on 表名(course_id);
(3)全文:create fulltext index index9_info on index9(info);
(4)单列:create index index10_addr on 表名(address(4));
(5)多列:create index index11_na on 表名(name,address);
(6)空间:create spatial index index12_line on 表名(line);
(3)用Alter Table语句来创建索引
(1)普通:alter table 表名 add index index13_name(name(20));
(2)唯一:alter table 表名 add unique index index14_id(course_id);
(3)全文:alter table 表名 add fulltext index index15_info(info);
(4)单列:alter table 表名 add index index16_addr(address(4));
(5)多列:alter table 表名 add index index17_na(name,address);
(6)空间:alter table 表名 add spatial index index18_line(line);
create table table1(id int primary key);
辅助索引(索引建立在非主属性上)
与主键索引形式相同
多列辅助索引
比较原则:从左往右比 最左比较法
先比较第一个属性,如果第一个属性值相同在比较第二个属性
先以第一个属性建立B+树 如果第一个属性值相同在以第二个属性建立B树
叶子节点存储:建立索引的属性和数据对应的地址(不论是单列还是多列索引)
MyISAM下的索引称作:非聚簇(聚集)型索引无非就是建立索引的属性从一个变成了多个
文件; .myI
要将B+树的结构加载到内存中才能使用
**注意:**并不是将整个B+树的结构加载到内存, 而是一个结点一个结点的加载
首先应该加入内存的就是根节点,剩下的就要根据大小比较后的结果判断应该再去加载哪一个结点
叶子结点:存储的是主键(建立索引的属性)和数据地址
**MyISAM:**数据和索引分在两个文件中存储 索引:.myi 数据:.myd
//id(主键) name age
index(name,age);
select age from student where name=Bob; //只查询一次B+树,就能查询出来
select name from student where age=18; 用不到的
index(name)
select age from student where name=Bob; //查询两次 一次是查询B+树 第二次是根据地址找数
select*from user where 主键 =10;
InnoDB查询一次就可以,原因是主键索引的叶子节点存储主键和这个主键对应的所有数据
MyISAM:查询两次 原因:叶子节点存储建立索引的属性和数据的地址,还需要根据有地址再去查询出
所有的数据。
select * from user where 主键 =10;
如果MyISAM也要只查询一次那么必须建立联合索引
index(id,age); //叶子节点存储:id age 还有数据的地址
index(name)
select age from user where name ="Bob";
树——> 二叉树——>排序二叉树——>多路树——>B树——>B+树
B树的特点 如果我们定义M为B树的分支数量 M肯定是大于2的 最小就是3
1.定义任意非叶子结点最多只有M个儿子
2.根节点所存储的数据为1~M-1,根结点的儿子数为(2,M )
3.除根结点外的非叶子结点的儿子数为(M/2,M)
4.每个结点存放至少M/2-1 (取上整)和至少M-1个关键字 (至少2个关键字)
5.非叶子结点的关键字个数(结点中存储的数据数量)=指向儿子的指针个数(分支数量)-1
6.非叶子结点和叶子结点均存储数据
range(主键) money
A: 1 50
B: 1 100
2 300
B ---> A 转账 50块
update B set money = 60 where range = 1; //多出10块
update A set money = 100 where range = 1;
事务的原子性(Atomic):
rollback; 原子性的实现原理 ——》Undo log
事务是一个不可分割的整体,事务必须具有原子特性,及当数据修改时,要么全执行,要么全不执行,即不允许事务部分的完成。
事务的一致性(Consistency):如果其他三个特性能够保证,一致性一定可以保证事务的一致性实现原理 = 原子性原理 + 隔离性原理 + 持久性原理
一个事务执行之前和执行之后,数据库数据必须保持一致性状态。
数据库的一致性状态必须由用户来负责,由并发控制机制实现。就拿网上购物来说,你只有让商品出库,又让商品进入顾客的购物车才能构成一个完整的事务!由于并发操作带来的数据不一致性包括读脏数据(脏读),不可重复读和虚读(幻读)。
事务的隔离性(Isolation):
4个隔离级别 隔离性实现原理:mysql锁机制 + MVCC机制
事务的隔离性:分级别的 不同级别的隔离性解决不同的问题。 根据不同场景设置不同的隔离级别。
只要将隔离级别设置正确上述三个问题就轻松解决了。
不同的隔离级别解决不同的问题:
隔离级别
隔离级别 脏读 不可重复读 虚/幻读
不支持事务
未提交读 可以 可以 可以
已提交读 不可以 可以 可以
可重复读 不可以 不可以 可以
串行化 不可以 不可以 不可以
实际上只要说清楚每种性质的实现原理即可
学习这些原理之前我们要先知道MySQL的Buffer Pool机制。
InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证,所以我们需要使用mysql中的日志来保证数据持久性的
当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
**undo log**中会记录一条和你执行的sql语句意义相反的sql语句,当你rollback时就执行undo log中的sql语句。
实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的sql语句。InnoDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态
• Record Locks(记录锁):在索引记录上加锁。
共享读锁和排他写锁:
• 共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
• 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的。
行锁和表锁:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
• 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最 低,并发度也最高
MVCC机制:
所谓的mvcc机制就是尽量的不去使用这些锁,一旦这些锁加上之后事务与事务之间就变成了完全的串行执行,在隔离级别中串行化其实就是这样做的。这里又要涉及到我们之前所学的乐观锁和悲观锁。
InnoDB的一致性的非锁定读就是通过在MVCC实现的,Mysql的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。MVCC的实现,是通过保存数据在某一个时间点的快照来实现的,,每一个事务只操作自己版本下的数据。因此每一个事务无论执行多长时间看到的数据,都是一样的。所以MVCC实现可重复读。
把这些机制了解清楚之后,我们来说一下隔离性实现的原理。首先隔离性一共分了4种隔离级别。分别是:未提交读,已提交读,可重复读,和序列化。
已提交读(Read committed):
在RC级别中,数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的(Record Locks)。那么已提交读时如何解决脏读问题的?这个就利用了mvcc机制。
只要没有commit那么修改就是自己版本下的数据。
可重复读(Repeatable read):
数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁(Record Locks)的+Mvcc机制
即使commit操作的也是自己版本数据。
序列化(Serializable):所有锁都加了 没有mvcc机制 效率最低
这个级别很简单,读加共享锁,写加排他锁,读写互斥。使用的悲观锁的理论,
(加的锁就是Next-Key Locks)实现简单,数据更加安全,但是并发能力非常差。如果你的业务并发的特别少或者没有并发,同时又要求数据及时可靠的话,可以使用这种模式。由于InnoDB引擎加的是行锁,并且是加在索引记录上,所以为了不产生幻读在还会加间隙锁。
串行。
持久性+隔离性+原子性共同实现了数据的一致性。
1.得有表和数据 存储引擎:切换到InnoDB
2.开启事务 :相当于是自己控制提交的时间。
由于Mysql在敲下sql语句按下回车之后会自动提交所以第一步开启事务实际上就是关闭自动提交机制。
3.可通过SELECT @@AUTOCOMMIT; 查看MySQL是否已经关闭自动提交。
mysql> select @@autocommit;
±-------------+
| @@autocommit |
±-------------+
| 1 | # 0表示手动提交事务 1表示自动提交事务
±-------------+
1 row in set (0.01 sec)
4.如果未关闭,可使用SET AUTOCOMMIT = 0操作;设置事务提交方式为手动提交事务
5.BEGIN 或 START TRANSACTION 显式地开启一个事务;(可写可不写)
6.COMMIT; 当组成事务的所有sql语句都执行成功,调用commit提交一个事务
7.ROLLBACK; 如果在执行事务的过程当中有一个事务执行失败回滚一个事务到初始的位置commit 和 rollback 事务结束的两种方式。
10条sql 第8条出错
8.SAVEPOINT point1; 设置一个名字为point1的保存点
9.ROLLBACK TO point1; 事务回滚到保存点point1,而不是回滚到初始状态
10. set session transaction isolation level READ COMMITTED; 设置事务的隔离级别
如下4种分别对应,我们之前讲过的4种隔离级别
Read uncommitted 、Read committed 、Repeatable read 、 Serializable 序列化
11.查询事务的隔离级别
mysql> select @@tx_isolation;
±----------------+
| @@tx_isolation |
±----------------+
| REPEATABLE-READ | // MySQL默认工作在“可重复读”的隔离级别 重启恢复默认
±----------------+
1 row in set (0.00 sec)
索引和数据缓存 索引存在文件
explain 发现sql语句用到索引 如果是使用索引的过程比较耗时
MyIsam: key_buffer_size=8M 索引缓存
innodb_buffer_pool_size=8M 索引和数据缓存
如果是因为频繁的创建销毁连接耗时
用户量非常大 100W, 1 创建连接 执行sql 释放连接
query_cache_type=0 关闭缓存
query_cache_type=1 开启缓存
query_cache_size=10 改变缓存大小 改变配置文件中的数据,记得重启服务。
对数据查询修改次数过于频繁。
C / S :cpp epoll+ 线程池 ---- 处理任务 线程池中存储的就是线程
** 线程的作用: **处理具体任务。 工人
** 线程池 : ** thread_cache_size=10
我们之前就提到过MySQL如果不在我们自己的服务器上的时候,我们要进行远程访问。既然要远程访问就离不开网络模型。MySQL所使用的是epoll+ 线程池的模型。这个线程中存放的是能够处理具体任务的线程,当有SQL请求到达MySQL Server的时候,在线程池中取一个线程来执行该SQL请求就可以了,执行完成后,不销毁线程,而是把线程再归还到线程池中,等待下一次任务的处理(当然MySQL会根据用户量,自动加大线程池的数量的)。和MySQL建立连接时出现的问题:
当我们发现由于最大连接数量过低 导致有大量用户无法建立连接。
show variables like ‘%connect%’;
max_connections=151
如果设置连接超时时间过低,用户建立连接速度过慢。就会出现有一些用户无法和mysql正常建立连接。
show global variables like ‘%timeout%’;
在配置文件中添加配置:wait_timeout=600。