目录
事务和索引都是用在较大的数据量
索引
索引的概念
索引是一种特殊的文件,是为了帮助MySQL高效获取数据的数据结构,类似于数组的下标引用,通过这个下标,通过这个下标就能拿到数组的数据和数组中的下标一样,速度非常快
MySQL数据库插件式的设计——每种索引在不同的存储引擎中的实现方式有可能不同,存储引擎就是MySQL到底如何对数据进行增删改查的不同实现方案
索引的实现方式
- MyISAM :MySQL5.5之前的默认存储引擎,不支持事务,但是性能较高
- InnpDB:MySQL5.5之后的默认存储引擎,支持事务,性能不如MyISAM,支持外键
显示当前版本的存储引擎
为什么需要索引
因为数据库最多的操作就是查看数据,所以外面必须要提高查看的效率,所以引入了索引的这种操作,但是索引的引入会导致拖慢增,删,改的效率,因为创建索引也需要时间和空间的开销
使用非索引方式搜索一个800w的数据
- 创建索引会占用存储空间
添加索引之后的搜索时间
创建索引
- 主键对应主键索引,对于InnoDB是必须有,且只能有一个,主键索引对应着聚簇索引结构
- 外键对应着外键索引
- 唯一约束对应着唯一索引
- 常规索引
删除索引
查看索引
show index from 表名;
索引背后的数据结构
学过的高效的查看数据的数据结构
基于二叉树的子节点太少,引入了B-树——N叉搜索树
B-树
- B树家族是严格的平衡树,左右子树的高度差为0,左树和右树的高度完全相同
- 每个树节点上都有多个值
- 每个节点上的子树的个数,就是当前节点值的个数+1
- 子树的节点值一定要处于父节点的范围
- 树节点保存的是复合值,不仅要保存索引,还需要保存其他的信息,比如学生表(id,name,class_id),索引是id,树中不仅保存id值,还得保存其他的学生信息
聚簇索引
- 主键索引,一个表只有一个聚簇索引,构建索引树的每个节点,除了要保存索引列的信息,还需要保存这条记录的完整内容
- 查询速度快,一个表只有一个聚簇索引,但是保存的信息多,占用空间大
- 算一下聚簇索引的高度为2存储多少数据:以InnoDB为例子,以页为从磁盘读取的单位,为16kb,一个bigint(主键ID的大小)的大小是8b,指针是6b,那么对于2层的B+树,假如一行数据为1kb,(16*1024)/14*16
非聚簇索引
- 普通索引,唯一索引,index创建的索引都是非聚簇索引,一张表可以有多个非聚簇索引,每个节点上除了保存索引列的信息之外,还只需要保存该记录的行号,也就是对应的主键id
- 查找速度慢,需要去通过主键,回表查询,一个表中可以有多个非聚簇索引,保存的信息少,占用空间小
- 比如我们select name from stu where k=3;我们需要去根据这个k去寻找非聚簇索引树上k=3的记录,然后这个节点保存的是id=300,我们还需要去根据id=300,去主键聚簇索引树上寻找,才能拿到完整的数据——回表操作
- 如果我们拿到的是id=300这个主键索引,我们直接就可以在聚簇索引树上找到具体数据
- 所以非聚簇索引相对聚簇索引有着更多的查表操作——读写磁盘操作
B+树
- B+树是B-树的升级版本
- B+树中,子节点中存在的最大值是在父节点出现过的值,这样的目的是为让最底层的叶子节点能包含整个数据的全集
- B+树最底层的叶子节点用链表连接,为了更高效的区间查找
- 非叶子节点只需要保存id属性
- 因为B+树的非叶子节点只需要保存索引列的辅助信息,索引的值,所以非叶子节点占用的空间很小,可以直接放在内存中,减少磁盘I/O
看一个关于索引的现象
- 我们插入的顺序并不是按照 a为 1 2 3 4 ,但是我们查出来的数据是按照a升序排序的,为什么呢?因为我们的取数据是按照页这种概念来取,页是一种单位,比如有表,段,块,页,行
![]()
Mysql的页的概念
- 首先我们知道我们的数据是存储在磁盘中,也就是硬盘中,但是我们的CPU只能跟内存进行直接交互,但是我们的内存是有限的,所以我们需要获得数据,就需要通过IO从磁盘中读取数据到内存中,MySQL中以页这种单位来从内存中来读取数据
- 我们发现数据在页中是按照主键的顺序进行存储的,为什么呢?我们的链接数据的结构是链表,所以查询数据需要遍历这种思想,因为我们一般会主键索引这种概念,我们在查找数据的时候,如果是没序的,比如我们要查-1,我们需要全表查询,才能知道有没有-1这个数,但是如果是有序的,第一个是1,说明1都已经是最小的了,所以我们直接就查询结束了
- 这里就会体现到我们自增主键的好处,因为如果不是自增主键,我们插入的数据,需要取找我们在链表中的位置,但是如果是自增的主键,我们每次主键都是最大的,只需要在链表插入就行
关于页目录的作用
- 我们知道我们的链接数据的结构是链表,链表获得数据只能通过遍历,我们就可以通过目录这种思想来提高查询速度,类似于我们书本里的目录
- 将数据分块,我们的例子就是将1 2分为一个块,然后将4 8分为一个块,页目录记录每个块中的最下值,比如我们要查8,我们就不需要从开头查,因为我们知道8比4大,肯定从4开始查
关于分页存储
- 首先我们知道一个问题,当我们的数据很大的时候,一页也就是16KB,肯定就存不下,那么我们要把所有的页都放入内存,然后查找吗?肯定是不现实的,因为读写IO的耗时比较大
- 所以采用这种方式,这种方式细看就是我们的B+树的结构啊,我们的B+树节点用链表去相连接,然后非叶子节点存储着索引的信息,我们将叶子节点放到磁盘中,然后非叶子节点放在内存中,通过查询非叶子节点中的信息,从磁盘中调用出对应的页
- 比如我们要查找8,我们通过非叶子节点,调用出第二页到内存中,找出对应的信息
- 这种会对应着聚簇索引和非聚簇索引,聚簇索引对应着主键索引,然后叶子节点存储对应的主键和信息,而非聚簇索引只是叶子节点存储对应的索引信息和辅助索引信息(也就是对应的主键ID),然后通过回表查询来获得对应的信息
简化一下
- 这样看就是我们的B+树了
查看是否使用索引
- 利用explain来查看当前的语句是否使用了索引
- select * from t2 where a=3,可以走索引 a>3也可以走索引,就是找到a=3的位置,然后输出后面的数据,a<3也可以,就是找到a=3,找出前面的,这时候就体现我们叶子节点用双链表进行存储的好处
联合索引
- 这不是主键索引,所以对应的是我们的非聚簇索引 ,叶子节点存储着我们的主键信息,通过回表查询我们的所有的信息(前提是需要我们的所有信息)
- 联合索引会引申出最左前缀原则 比如我们的联合索引是 create index id_bcd on t1(bcd);
- 查 等于bcd bc bd b都是可以走索引的,但是d c cd 这种就走不了索引,因为我们的B+S树先对b进行排序的,如果不知道b,就不知道应该往哪走
- 联合索引又引申出来我们的覆盖索引——也就是我们查询的时候需要的数据都是改索引有的,比如select b,c,d,a from t1 where b>1,就不会出现什么索引失效的结果,因为bcda这四个数据都是我们联合索引拥有的,就不需要去聚簇索引中做回表查询了
- select b from t1,其实也可以走索引(常规的是我们通过条件和对应的非叶子节点的页面来走索引),为什么呢?因为我们的非聚簇索引的存的数据比较少,对应着一页中的数据项是更多,也就意味着我们的需要的页更少,表示IO读取次数更少
索引失效的情况
- 数据分布的影响,也就是我们回表查询和全表查询的的比较,比如我们的select * from t1 where b>1,我们创建了联合索引bcd,按理说我说b>1,是可以走联合索引的啊,但是实际可能是不会走索引的,因为我们表中有很多的数据,比1大的数据占大部分,我们的联合索引是一个非聚簇索引,我们找到了b>1的数据,还需要去回表查询(因为我们的联合索引中不包含表的所有数据),这样还不如我们直接去全表扫描,因为我们的索引也会对应着读取磁盘的操作
- select * from t1 order by b asc,c asc,d asc; 我们的联合索引bcd,但是也有可能会失效,为什么呢?我们分析如果走索引,我们需要回表,如果不走索引,我们需要全表查询,然后在内存排序,对这两种方法进行比较
- 还有一种就是null查询(is null is not null),如果null的个数比较少,如果查询 is null,也会有大量的回表,全表查询可能比索引还快
- 不要在索引列上进行运算,比如select * from t1 where a+1=1 a是主键,也不能走索引了
- 字符串不加引号,如果我们的索引是字符串,但是没有写引号,会发生隐式转换,不会使用索引
- 模糊查询的时候,如果只是尾部模糊匹配,索引不会失效,但是头部模糊匹配,索引失效
- or连接的条件,如果前面的字段有索引,但是后面的字段没有索引,那么涉及到的索引不会使用
SQL提示
覆盖索引
- 覆盖索引就是我们在查询数据的时候,比如我们的联合索引是abc(非聚簇索引还存储主键信息),我们查询的数据是abc,所以不需要进行回表查询,效率比较高
前缀索引
- 当字段类型是字符串的时候,有时候需要索引很长的字符串,会让字符串变的很大,这会让索引变得很大,浪费大量的磁盘IO,影响查询效率,此时就只将字符串一部分前缀,建立索引
什么是最左匹配原则?
- 最左匹配原则是用在联合索引的时候
- 对于索引中的字段,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的查询优化器会帮你优化成索引可以识别的形式,但是如果索引是abc,那么abc是全部都能用索引,b bc c 都是不能使用索引的,因为没有a(最左边的字段),但是a*c 这种只会用a的索引
索引建立的原则
- 针对数量量较大,且查询比较频繁的表建立索引
- 针对常作为查询条件,排序,分组操作的字段建立索引
- 尽量选择区分度的列作为索引,尽量建立唯一索引,区分度越高,使用索引效率高
- 如果是字符串,字符串类型的字段,字段的长度较长,使用前缀索引
- 尽量使用联合索引,减少单列索引,查询的时候,联合索引很多时候可以覆盖索引,减少回表查询,节省存储空间
- 控制索引的数量,因为维护索引结构的代价也很大,会影响增删改的效率
事务
事务的引入
事务的概念
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。在不同的环境中,都可以有事务。对应在数据库中,就是数据库事务。事务的四大性质
- 原子性,最核心的性质,一个事务中所有操作,要么全部执行成功,要么全部执行失败(执行失败就会通过rollbock回滚到执行这个事务操作之前的情况)
- 持久性,一个事务执行完之后,这个事务对数据库的所有修改都有永久的效果,不会丢失
- 一致性 ,一个事务在执行前后的数据都是一种合法性的状态(事务永远是从一个一致性状态到另一个一致性状态)
- 隔离性,多个并发的事务访问数据库的时候,事务之间都是相互隔离的,一个事务不应该让其他事务干扰,不同事务之间相互隔离
事务引起的问题
- 脏读,事务A在修改数据,事务B读取了事务A修改后的数据,但是事务A进行了回滚,前面修改的数据不算了,那么事务B读到的数据就是脏数据,这种情况就称脏读
- 不可重复读,同一个事务在多次相同操作查询到数据不同,(在不同的查询时,其他事务的修改对本事务是可见的),比如select name from stu,我查询学生表所有学生的姓名,如果这时候有别的事务添加了几个学生的姓名,那么再select name from stu,得到的数据跟上次查询就不一样了
- 幻读:就是每次查询得到的值是相同的,和其他事务隔离,其他事务的修改数据库对本事务都是不可见的,我们插入一个主键id,在别的事务中插入了信息,我们在本事务中是查询不到的,但是我们在本事务就是插入不进去这个主键
事务的三个操作
- 开启事务 start transaction——>后面的多个sql语句就是一个整体
- 回滚操作 rollback,回滚了上次对数据库做的修改
- 提交事务 commit,就是把开启事务之后的所有SQL语句统一再数据库上进行持久化
MySQL的四个隔离级别
- 读未提交:处在隔离级别的事务可以看到其他还没有提交事务对数据库的修改——RU
- 读已提交,处在该隔离级别的事务可以看到其他事务已经提交对数据库的修改——RC,Oracle数据库的默认的隔离级别
- 可重复读,InnoDB引擎默认的隔离级别,一个事务一旦开启,在该隔离级别下,该事务提交之前,多次查询看到的结果是一样的,无论别的事务怎么修改数据库,在当前事务下都是不可见的
- 串行化:事务的最高隔离级别,所有事务都串行访问数据库,不会发生冲突,不会产生任何事务的问题,就没有并发执行了,串行化其实就是在底层给我们的读操作加上写锁就可以实现了读写阻塞,和写写阻塞
![]()
- 从上到下,事务的隔离级别越高,安全性越强,但是并发性就越差
读已提交和可重复读的实现
MVCC
- 全称Multi-Version Concurrency Control,即
多版本并发控制
,主要是为了提高数据库的并发性能
。以下文章都是围绕InnoDB引擎来讲,因为myIsam不支持事务- 同一行数据平时发生读写请求时,会
上锁阻塞
住。但mvcc用更好的方式去处理读—写请求,做到在发生读—写请求冲突时不用加锁
。- 这个读是指的
快照读
,而不是当前读
,当前读是一种加锁操作,是悲观锁
。当前读
它读取的数据库记录,都是
当前最新
的版本
,会对当前读取的数据进行加锁
,防止其他事务修改数据。是悲观锁
的一种操作。如下操作都是当前读:
select lock in share mode (共享锁)
select for update (排他锁)
update (排他锁)
insert (排他锁)
delete (排他锁)
串行化事务隔离级别
快照读
快照读的实现是基于
多版本
并发控制,即MVCC,既然是多版本,那么快照读读到的数据不一定是当前最新的数据,有可能是之前历史版本
的数据。如下操作是快照读:
- 不加锁的select操作(注:事务级别不是串行化)
快照读与mvcc的关系
MVCC
是“维持一个数据的多个版本,使读写操作没有冲突”的一个抽象概念
。这个概念需要具体功能去实现,这个具体实现就是
快照读
。(具体实现下面讲)数据库并发场景
读-读
:不存在任何问题,也不需要并发控制
读-写
:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写
:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失MVCC解决并发哪些问题?
- mvcc用来解决读—写冲突的无锁并发控制,就是为事务分配
单向增长
的时间戳
。为每个数据修改保存一个版本
,版本与事务时间戳相关联
。- 读操作
只读取
该事务开始前
的数据库快照
。解决问题如下:
并发读-写时
:可以做到读操作不阻塞写操作,同时写操作也不会阻塞读操作。解决
脏读
、幻读
、不可重复读
等事务隔离问题,但不能解决上面的写-写 更新丢失
问题。因此有了下面提高并发性能的
组合拳
:
MVCC + 悲观锁
:MVCC解决读写冲突,悲观锁解决写写冲突
MVCC + 乐观锁
:MVCC解决读写冲突,乐观锁解决写写冲突MVCC的实现原理
- 它的实现原理主要是
版本链
,undo日志
,Read View
来实现的版本链
我们数据库中的每行数据,除了我们肉眼看见的数据,还有几个
隐藏字段
,得开天眼
才能看到。分别是db_trx_id
、db_roll_pointer
、db_row_id
。
db_trx_id 6byte, 最近修改(修改/插入)
事务ID
:记录创建
这条记录/最后一次修改
该记录的事务ID
。db_roll_pointer (版本链关键) 7byte,
回滚指针
,指向这条记录
的上一个版本
(存储于rollback segment里)db_row_id 6byte,隐含的
自增ID
(隐藏主键),如果数据表没有主键
,InnoDB会自动以db_row_id产生一个聚簇索引
。实际还有一个
删除flag
隐藏字段, 记录被更新
或删除
并不代表真的删除,而是删除flag
变了
- 如上图,
db_row_id
是数据库默认为该行记录生成的唯一隐式主键
,db_trx_id
是当前操作该记录的事务ID
,而db_roll_pointer
是一个回滚指针
,用于配合undo日志
,指向上一个旧版本
。
- 对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被
roll_pointer
属性连接成一个链表
,我们把这个链表称之为版本链
,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,在根据ReadView判断版本可见性的时候会用到。- 每次对数据库记录进行改动,都会记录一条
undo日志
,每条undo日志也都有一个roll_pointer
属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来
,串成一个链表
,所以现在的情况就像下图一样:undo日志
- Undo log 主要用于
记录
数据被修改之前
的日志,在表信息修改之前先会把数据拷贝到undo log
里。当事务
进行回滚时
可以通过undo log 里的日志进行数据还原
。Undo log 的用途
保证
事务
进行rollback
时的原子性和一致性
,当事务进行回滚
的时候可以用undo log的数据进行恢复
。用于MVCC
快照读
的数据,在MVCC多版本控制中,通过读取undo log
的历史版本数据
可以实现不同事务版本号
都拥有自己独立的快照数据版本
。undo log主要分为两种:
insert undo log
代表事务在insert新记录时产生的undo log , 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
update undo log(主要)
事务在进行update或delete时产生的undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除
Read View(读视图)
- 事务进行
快照读
操作的时候生产的读视图
(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照
。- 记录并维护系统当前
活跃事务的ID
(没有commit,当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以越新的事务,ID值越大),是系统中当前不应该被本事务
看到的其他事务id列表
。- Read View主要是用来做
可见性
判断的, 即当我们某个事务
执行快照读
的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务
能够看到哪个版本
的数据,既可能是当前最新
的数据,也有可能是该行记录的undo log里面的某个版本
的数据。Read View几个属性
trx_ids
: 当前系统活跃(未提交
)事务版本号集合。
low_limit_id
: 创建当前read view 时“当前系统最大事务版本号
+1”。
up_limit_id
: 创建当前read view 时“系统正处于活跃事务最小版本号
”
creator_trx_id
: 创建当前read view的事务版本号;
<
up_limit_id
||db_trx_id
==creator_trx_id
(显示)如果数据事务ID小于read view中的
最小活跃事务ID
,则可以肯定该数据是在当前事务启之前
就已经存在
了的,所以可以显示
。或者数据的
事务ID
等于creator_trx_id
,那么说明这个数据就是当前事务自己生成的
,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以显示
的。
db_trx_id
>=low_limit_id
(不显示)如果数据事务ID大于read view 中的当前系统的
最大事务ID
,则说明该数据是在当前read view 创建之后才产生
的,所以数据不显示
。如果小于则进入下一个判断
db_trx_id
是否在活跃事务
(trx_ids)中
不存在
:则说明read view产生的时候事务已经commit
了,这种情况数据则可以显示
。
已存在
:则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。MVCC和事务隔离级别
- 上面所讲的
Read View
用于支持RC
(Read Committed,读提交)和RR
(Repeatable Read,可重复读)隔离级别
的实现
。RR、RC生成时机
RC
隔离级别下,是每个快照读
都会生成并获取最新
的Read View
;而在
RR
隔离级别下,则是同一个事务中
的第一个快照读
才会创建Read View
,之后的
快照读获取的都是同一个Read View
,之后的查询就不会重复生成
了,所以一个事务的查询结果每次都是一样的
。解决幻读问题
快照读
:通过MVCC来进行控制的,不用加锁。按照MVCC中规定的“语法”进行增删改查等操作,以避免幻读。
当前读
:通过next-key锁(行锁+gap锁)来解决问题的。RC、RR级别下的InnoDB快照读区别
在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见
而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因
总结
- 从以上的描述中我们可以看出来,所谓的MVCC指的就是在使用
READ COMMITTD
、REPEATABLE READ
这两种隔离级别的事务在执行普通的SEELCT
操作时访问记录的版本链
的过程,这样子可以使不同事务的读-写
、写-读
操作并发执行
,从而提升系统性能
。
MySQL锁的情况
为什么加锁
- 锁是计算机协调多个进程或线程并发访问某一资源的机制。锁保证数据并发访问的一致性、有效性;锁冲突也是影响数据库并发访问性能的一个重要因素。锁是Mysql在服务器层和存储引擎层的的并发控制。
- 加锁是消耗资源的,锁的各种操作,包括获得锁、检测锁是否是否已解除、释放锁等。
- 所以commit或者rollback是会释放锁的
锁的类型
按属性分
- 共享锁:共享锁又称读锁,S锁,当一个事务为其加上读锁,其他事务只能加读锁,而不能加写锁,只有所有的写锁释放之后,才能加读写锁,共享锁的特性是为了并发之下能够并发读取数据,读取数据的时候不支持修改,避免出现了不可重复读的问题 比如select lock in share mode上读锁
- 排他锁:排他锁又称写锁,X锁,当一个事务为其加上了写锁,其他事务不能为其加上任何锁,直到锁释放,其他事务才能加锁,排他锁的特性是为了在并发之下,不允许其他人同时修改,也不允许其他人读取,避免了出现脏数据和脏读的问题,select for update表示加排他锁,select本身是不加锁的
按粒度分
全局锁
- 全局锁就是对整个数据库实例加锁。MySQL 提供了一个加全局读锁的方法,命令是
Flush tables with read lock (FTWRL)
。 当你需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。- 全局锁的典型使用场景是,做全库逻辑备份(mysqldump)
表锁
- 表锁:表锁指的是我们上锁的时候,锁住的是整个表,当下一个事务访问改表的时候,必须等前一个事务释放锁才能对表进行访问,特点是粒度大,加锁简单,容易冲突 lock table 表名 read/write(对应读写锁)
- 元数据锁:作用是为了管理数据库对象的并发访问和确保元数据一致性,也就是保证DDL和DML之间的关系,也就是在操作数据的时候不能改变表结构,反之亦然,保证读写的安全,简单来说就是在改变表结构的时候,会自动加上元数据的写锁,在进行增删改查数据的时候元数据的读锁
- 对于元数据锁,读写锁是互斥的,读锁不是互斥的
- 意向锁:为了解决行锁和表锁的冲突问题,比如我们update ...where id=...,会对这个索引加行锁,如果其他事务想要加表锁lock table read/write,如果加的是表的写锁,那么肯定会出现表锁和行锁的冲突,如果没有意向锁,我们需要遍历全表去查看有没有行锁,很耗时,所以引入了意向锁
- 我们行锁共享锁(排他锁)的时候,会给该表加上一个意向共享锁(排他锁)
行锁
- 行锁:行锁指上锁的时候锁的是表的某一行或者多行记录,其他事务访问该表的时候,只有被锁住的记录不能访问,其他记录是正常访问的,特点是粒度小,加锁麻烦,不同意冲突,相比表锁并发度高,行锁是加在索引上,应用在InnoDB中
- 1增删改,会自动加行级排他锁 2select(其实读的是快照数据),不会加锁 3 select lock in share mode 上行级共享锁 4select for update 上的是行级排他锁
- 记录锁:记录锁也属于行锁的一种,只不过是记录锁的范围只能是一行数据
- 页锁:开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发处理能力一般。比如我们InnoDb中从磁盘拿数据就是以页为单位
- 间隙锁:(Next-Key锁)。也是属于行锁的一种当我们用范围条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做
"间隙(GAP)"
。InnoDB也会对这个"间隙"加锁,其目的是为了防止其他事务插入间隙,间隙锁可以共存,一个事务的间隙锁不会组织另一个事务对同一个间隙加间隙锁,解决了可重复读隔离下的幻读问题
- 临键锁:next-key-lock,也属于行锁的一种,并且是InnoDB的行默认锁算法,总结来说就是记录锁和间隙锁的组合,临键锁会将查询出来的记录锁住,同时也会把该查询范围下的所有间隙空间锁住,再之将相邻的下一个区间锁住
实现方式
- 悲观锁
- 乐观锁
DDL(Data Definition Language):数据定义语言,用来定义数据库对象,库、表、列等;创建、删除、修改:库,表结构;alter drop等
- DML是Data Manipulation Language的缩写,意思是数据操纵语言,是指在SQL语言中,负责对数据库对象运行数据访问工作的指令集,以INSERT、UPDATE、DELETE三种指令为核心,分别代表插入、更新与删除,是开发以数据为中心的应用程序必定会使用到的指令。
- 一般的项目开发的各种参数的基本功能都是CURD。它代表创建(Create)、更新(Update)、读取(Read)和删除(Delete)操作。
使用场景
- 修改数据库的表结构,会自动加上表级锁——元数据锁
- 更新数据为使用索引,行级锁会上升为表级锁
- 更新数据使用索引会使用行级锁
- select... for update 会使用行级锁,加的是写锁
- select...是不加锁的,所以有这个数据被写锁锁定也没事,其实访问的mvcc的历史版本
唯一键加锁和非唯一键加锁,无索引加锁
- 先将唯一索引ID加上id的排他锁
- 然后在对应的主键的索引记录加上排他锁
- 先将对应的非唯一的键进行加锁,然后将对应的主键也加锁,然后加上间隙锁
- 对于无索引的加行锁就是升级为表锁,也就是将行锁和间隙都加上锁
关于死锁
表死锁
行死锁
SQL性能优化
性能分析手段
慢查询日志
- 慢查询日志记录了所有的执行时间超过指定参数(默认10秒)的所有的SQL语句的日志
profile——查询指令的时间
- 因为有些简单的查询,可能时间也很短,但是对于这个场景是非常耗时的
explain