本文参考文档:
1. 聚簇索引与非聚簇索引(也叫二级索引)--最清楚的一篇讲解
2. MySQL锁总结
3. MySQL 默认隔离级别是RR,为什么阿里这种大厂会改成RC?
4. 一次诡异的线上数据库的死锁问题排查过程
5. Innodb中的事务隔离级别和锁的关系
本文内容如下:
1.讲解索引结构 聚簇索引与非聚簇索引
2.数据库锁 (待完善 参考Innodb中的事务隔离级别和锁的关系)
3.加锁过程及死锁
一、 聚簇索引与非聚簇索引(也叫二级索引)
通俗点讲
- 聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
- 非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因
聚簇索引具有唯一性
由于聚簇索引是将数据跟索引结构放到一块,因此一个表仅有一个聚簇索引,二级索引可以有多个。
聚簇索引默认是主键,,如果表中没有定义主键,InnoDB 会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB 会隐式定义一个主键来作为聚簇索引。
聚簇索引的优势
看上去聚簇索引的效率明显要低于非聚簇索引,因为每次使用辅助索引检索都要经过两次B+树查找,这不是多此一举吗?聚簇索引的优势在哪?
- 由于行数据和叶子节点存储在一起,同一页中会有多条行数据,访问同一数据页不同行记录时,已经把页加载到了Buffer中,再次访问的时候,会在内存中完成访问,不必访问磁盘。这样主键和行数据是一起被载入内存的,找到叶子节点就可以立刻将行数据返回了,如果按照主键Id来组织数据,获得数据更快。
- 辅助索引使用主键作为"指针"而不是使用地址值作为指针的好处是,减少了当出现行移动或者数据页分裂时辅助索引的维护工作,使用主键值当作指针会让辅助索引占用更多的空间,换来的好处是InnoDB在移动行时无须更新辅助索引中的这个"指针" 。也就是说行的位置(实现中通过16K的Page来定位)会随着数据库里数据的修改而发生变化(前面的B+树节点分裂以及Page的分裂),使用聚簇索引就可以保证不管这个主键B+树的节点如何变化,辅助索引树都不受影响。
- 聚簇索引适合用在排序的场合,非聚簇索引不适合
- 取出一定范围数据的时候,使用用聚簇索引
- 二级索引需要两次索引查找,而不是一次才能取到数据,因为存储引擎第一次需要通过二级索引找到索引的叶子节点,从而找到数据的主键,然后在聚簇索引中用主键再次查找索引,再找到数据
- 可以把相关数据保存在一起。例如实现电子邮箱时,可以根据用户 ID 来聚集数据,这样只需要从磁盘读取少数的数据页就能获取某个用户的全部邮件。如果没有使用聚簇索引,则每封邮件都可能导致一次磁盘 I/O。
二、数据库锁
从锁粒度分类,分为表锁和行锁,innodb中使用行锁
从操作动作分为S锁读锁(共享锁) 和 X锁写锁(排他锁)
InnoDB加锁方法:
- 意向锁是 InnoDB 自动加的, 不需用户干预。
- 对于 UPDATE、 DELETE 和 INSERT 语句, InnoDB
会自动给涉及数据集加排他锁(X); - 对于普通 SELECT 语句,InnoDB 不会加任何锁;
事务可以通过以下语句显式给记录集加共享锁或排他锁: -
- 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。 其他 session 仍然可以查询记录,并也可以对该记录加 share mode 的共享锁。但是如果当前事务需要对该记录进行更新操作,则很有可能造成死锁。
- 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。其他 session 可以查询该记录,但是不能对该记录加共享锁或排他锁,而是等待获得锁
RC数据库隔离级别中(不可重复复读),不存在GAP间隙锁 和 next-key锁
RR数据库隔离级别中,使用GAP间隙锁 和 next-key锁, 解决了可重复复读和幻读
三、加锁过程及死锁
在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。(对非主键索引加锁,若此非主键索引结构中存储的主键值是多个,那需要将多个主键索引全部锁定)
事务在以非主键索引为where条件进行Update的时候,会先对该非主键索引加锁,然后再查询该非主键索引对应的主键索引都有哪些,再对这些主键索引进行加锁。
死锁的发生与否,并不在于事务中有多少条SQL语句,死锁的关键在于:两个(或以上)的Session加锁的顺序不一致