文章目录
- 一、索引
- 1.索引构成
- 2.如何查找
- 3.最左匹配原则
- 4.覆盖索引
- 5.减少冗余索引和重复索引
- 1.冗余索引
- 2.重复索引
- 6.索引适用情况和注意事项
- 1.适用情况
- 2.注意事项
- 二、Explain执行计划
- 1.Explain语句
- 三、隔离级别与MVCC
- 1.事前准备
- 2.四个事务并发的问题
- 1.脏写
- 2.脏读
- 3.不可重复读
- 4.幻读
- 3.MVCC原理
- 1.版本链
- 2.关于脏写
- 3.ReadView
- 判断方法
- 4.MVCC小结
- 四、锁
- 1.解决并发问题的两种方式
- 解决方案
- 1.MVCC进行控制
- 2.读写操作都加锁
- 2.一致性读
- 3.锁定读
- 1.共享锁和排他锁
- 2.锁定读
一、索引
1.索引构成
索引构成分为五部分
- record_type:类型,0,1,2,3分别代表普通记录,目录项纪录,最小纪录,最大纪录
- next_record:记录偏移量
- 列的值
- 其他信息
如图所示:
2.如何查找
1.先二分查找目录项,比如要查找的纪录是20,(那么这个纪录就在页9)
2.再二分查找这些纪录,进而找到对应的值
为了避免出现多个页列的名称一直,在插入的时候不知道新的数据出现在哪里,我们设置除了二级索引列新增了一个主键作为区别
3.最左匹配原则
比如我们建立复合索引(name,birth,phone),这个索引就是先按照name
,再birth
再phone
4.覆盖索引
查找的列中只包含索引列,防止回表查询
5.减少冗余索引和重复索引
1.冗余索引
CREATE TABLE person_info(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
birthday DATE NOT NULL,
phone_number CHAR(11) NOT NULL,
country varchar(100) NOT NULL,
PRIMARY KEY (id),
KEY idx_name_birthday_phone_number (name(10), birthday, phone_number),
KEY idx_name (name(10))
);
2.重复索引
CREATE TABLE repeat_index_demo (
c1 INT PRIMARY KEY,
c2 INT,
UNIQUE uidx_c1 (c1),
INDEX idx_c1 (c1)
);
6.索引适用情况和注意事项
1.适用情况
1.全值匹配
2.范围查询
3.精确查询某一行并范围查询另一行
4.模糊查询左边的列
5.用于排序
6.用于分组
2.注意事项
- 用于搜索,排序和分组创建索引
- 为列的基数大的创建索引
- 索引列的类型小
- 索引列如果在表达式单独出现可以创建索引(不要2* index<4这种)
- 避免
聚簇索引
页分裂,尽量让索引auto_increment
- 删除冗余和重复索引
- 使用覆盖索引
二、Explain执行计划
1.Explain语句
三、隔离级别与MVCC
1.事前准备
CREATE TABLE hero (
number INT,
name VARCHAR(100),
country varchar(100),
PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;
INSERT INTO hero VALUES(1,'刘备','蜀');
2.四个事务并发的问题
1.脏写
事务修改了另外一个事务未提交的数据
2.脏读
事务读到了另外一个事务未提交的数据
3.不可重复读
事务读到了另外一个已经提交的事务修改过的数据,并且其他事务对这个数据修改提交后,该事务都能取到最新的值
对于这个数据被删除被读到的情况也属于不可重复读
4.幻读
事务先根据条件查询出来一些纪录,然后其他事务新增了符合条件的纪录,原来的事务查询该条件,可以把新插入的纪录读出来
如果数据变少了,也就是删除这种情况不是幻读,幻读诗读到了之前没有的值
3.MVCC原理
1.版本链
对于InnoDB
来说,聚簇索引
需要两个必要隐藏列
trx_id
:每次事务对聚簇索引纪录改动,都会被transaction_id
赋值给trx_id
roll_pointer
:每次聚簇索引改动,都会被旧的版本写入undo
日志,方便找到修改之前的信息
2.关于脏写
这种情况不就沦为了 脏写了吗,
InnoDB
保证不会有脏写的情况发生,也就是trx100
更新后,就会给这个事务加锁,知道这个事务commit
,把锁释放之后才可以更新
3.ReadView
核心问题: 需要判断版本链的那些版本是当前事务可见的
m_ids
:在生成ReadView
中活跃读写事务的事务id
列表
min_trx_id
:生成ReadView
中读写事务的最小事务id
,也是m_ids
的min_id
max_trx_id
:生成ReadView
时候系统分配下一个事务id
max_trx_id
不是m_ids
的最大值,比如我们有3个事务1,2,3,事务3已经提交,m_ids=[1,2],但是我们要分配的下一个id是max_trx_id
=4
creator_trx_id
: 生成这个事务的事务id
对事务改动
insert,delete,update
才会分配事务id,只读事务的事务id
默认为0
判断方法
1.trx_id = creator_trx_id
,表示当前事务访问的是自己修改过的纪录可以正常访问
2.trx_id < min_trx_id
, 生成该版本的事务在当前事务生成ReadView
之前提交
3.trx_id > max_trx_id
,生成该本本的事务在当前事务生成ReadView
之后开启
4.min < trx_id < max
,需要查看trx_id
是否在m_ids
列表中,不存在就提交了,可以访问,如果在,说明活跃不可访问
4.MVCC小结
READ COMMITTED
和REPEATABLE READ
在生成ReadView
的时候是不一样的
READ COMMITED
是在每次SELECT
之前都生成一个ReadView
REPEATABLE READ
是在第一次SELECT
生成一个ReadView
四、锁
1.解决并发问题的两种方式
锁结构
trx_id
:锁是哪个事务产生的is_waiting
:当前事务是否在等待
事务T1
改动了这个纪录,生成锁结构
和这个关联,之前这个锁没有被占用,所以is_waiting=false
,这个场景获取锁成功
1.读-读 : 读与读之间是不互斥的
2.写-写:写与写之间出现了脏写,这种情况需要加锁,进行锁的获取和释放
3.读-写,写-读:这种会出现脏读
,不可重复读
,幻读
解决方案
1.MVCC进行控制
我们通过
ReadView
找到适应的版本(历史版本基于undo
日志),只能看到在生成ReadView
之前已经提交的事务,之后的更改是看不到的
2.读写操作都加锁
让
读-写
也像写-写
那样排队执行
2.一致性读
事务利用MVCC
进行的读操作是一致性读
,或者叫快照读
普通SELECT
(plain SELECT
)在READ COMMMITED
,REPEATED READ
情况下都算一致性读
3.锁定读
1.共享锁和排他锁
要想读-读不受限,又要让读-写,写-读,写-写相互阻塞,于是就设计了S锁和X锁
- 共享锁:
S锁
,事务读取纪录之前,需要获取S锁 - 排他锁:
X锁
,事务修改记录前,需要获取纪录的X锁
兼容性:
2.锁定读
我们想在读取纪录的时候获取X锁
就需要用到锁定读
对读取纪录加S锁
:
SELECT ... LOCK IN SHARE MODE
对读取纪录加X锁
SELECT ... FOR UPDATE