存储引擎
存储引擎负责对表中的数据进行读取和写入,常用的存储引擎有InnoDB、MyISAM、Menory等。
InnoDB和MyISAM的区别
| InnoDB | MyISAM | |
|---|---|---|
| 事务 | 支持 | 不支持 |
| 锁 | 支持MVCC行锁、表锁 | 表锁 |
| 外键 | 支持 | 不支持 |
| 存储空间 | 由于需要高速缓存,较大 | 可压缩 |
| 适用场景 | 有一定量的update和Insert | 大量的查询 |
InnoDB存储引擎
读取数据的方式
把数据分成若干个页,当需要从磁盘读取数据的时候,每次会从磁盘加载16KB的数据到内存中。
行格式
常见的行格式有Compact、Redundant、Dynamic、Compressed
索引
索引是帮助MySQL高效获取数据的数据结构。
B+树索引和Hash索引的区别
- B+树是一个平衡的多叉树,Hash索引采用的是一定的哈希算法。
- Hash索引更适用于等值查询,只需要经过一次hash就能找到相应的值,有可能会出现hash碰撞;B+树索引更适用于范围查询检索。
- 哈希索引无法利用索引进行排序
- 哈希索引不支持联合索引的最左匹配原则。
常见的索引种类有:主键索引(primary)、唯一索引(unique)、普通索引(indx)、联合索引。
主键索引又称聚簇索引,其余的都成为非聚簇索引,或者也称为非主键索引,或者二级索引。
聚簇索引和非聚集索引
聚集索引采用的数据结构是B+树。
聚集索引的叶子节点存储的是整行数据,非聚集索引的叶子节点存储的是索引列和主键。
如果要是通过非聚集索引获取整行数据的某个字段值,那么流程应该是这样的:
- 首先通过查询条件找到对应的主键id
- 从id主键的B+树上找到对应id的数据。
- 从整行数据中获取对应的字段值。
这个流程的过程叫做回表。
覆盖索引
覆盖索引,即从非聚簇索引中就能查到记录,不需要再次从主键索引中获取记录。
优点:避免回表的产生,减少I/O,提高性能。
可以通过explain命令,查看Extra中是否有Using index。
建立索引的原则
-
符合最左前缀原则,联合索引要把查询频率最高的列放在最左边。
-
查询频率比较高的列。
-
索引列不能参与计算,最好使用简单的数字类型。
-
主键自增
-
尽量使用覆盖索引进行查询,避免回表带来的性能损耗。
事务
事务的隔离级别
Read uncommitted(未提交读)
即便事务没有提交,但是仍然可以获取到未提交的数据,这是所有隔离级别中最低的一种。
带来的问题:会出现脏读:一个事务读取到另外一个事务未提交更新的数据。
Read committed(已提交读)(RC)
只能读取其他事务已经提交的数据,未提交的数据读取不到。
带来的问题:
- 在同一个事务会话中,可能会出现读取两次结果不同。这就造成了不可重复读
- 范围查询导致查询到的次数不同,这就造成了幻读。
Repetable read(可重复读)(RR)-->InnoDB默认级别
在同一事务内的查询都是事务开始时刻一致的。
一个事务第一次读取某条记录之后,即使其他事务修改了该记录的值并且提交了,该事务读取的值仍然是第一次读取到的值,解决了不可重复读。对于Mysql而言,也解决了幻读。
Serializable(串行化)
其他会话对该表进行写操作,将会被挂起。
版本链
对于每一次记录的改动,都会记录一条undo日志,每条undo日志都会有一个roll_pointer属性,把这些undo日志都连接起来,串成一个链表,就形成了一个版本链。
ReadView
ReadView中主要包含当前系统中有哪些活跃的读写事务,把事务id放到一个列表中,这个列表的名称叫m_ids。
对于 Read committed ,每次读取数据前都会生成一个ReadView。
对于 Repetable Read ,只会在第一次读取数据的时候生成一个ReadView。
MVVC
MVVC(Multi-Version Concurrentcy Contrl),多版本并发控制:在使用read commit 、repetable read这两种隔离级别的不同的事务读-写、写-读操作并发执行,从而提高性能。
锁
读锁和写锁
- S-共享锁:又称读锁。其他事务可以继续加共享锁,但是不能继续加排他锁。
- X-排他锁:又称写锁。一旦加了写锁,其他事务就不能加锁了。
纵轴表示已有的锁,横轴表示尝试获取的锁。
| S-共享锁 | X-排他锁 | |
|---|---|---|
| S-共享锁 | 兼容 | 冲突 |
| X-排他锁 | 冲突 | 冲突 |
兼容性:事务A获得一个锁后,事务B如果也能立即获取到该锁,则成兼容,反之叫冲突。
意向锁
意向锁在InnoDB中是表级锁,用来表示一个事务想要获取什么。
- 意向共享锁:表达一个事务想要获取一张表中的某几行的共享锁。
- 意向排他锁:表达一个事务想要获取一张表中的某几行的排他锁。
自增长锁
自增长锁是一种特殊的表锁机制,可以提升并发插入性能。特点如下:
- 在SQL执行完就释放锁,并不是事务执行完。
- 对于大数据量插入会影响插入性能,因为会堵塞另外一个事务的执行。
InnoDB加锁算法
记录锁(Record-Lock)
记录锁封锁住的是索引记录,而不是我们真正的数据记录。
形成记录锁的方式一
-- id 列为主键列或唯一索引列
SELECT * FROM table WHERE id = 1 FOR UPDATE;
id为1的记录行会被锁住。
需要注意的是:
id列必须为唯一索引列或者主键列,否则该sql加的锁就会变成临建锁。- 查询语句为
精确查询,不能为范围查询或者模糊查询,否则将退化为临建锁。
形成记录锁的方式二
通过主键索引或者唯一索引对数据进行update操作时,也会对该行数据添加记录锁。
-- id 列为主键列或唯一索引列
UPDATE SET age = 50 WHERE id = 1;
间隙锁(Gap Lock)
间隙锁是基于 非唯一索引 ,它锁定的是一段范围的索引记录。
间隙锁基于Next-Key Loking算法,间隙锁锁住的是一个区间,而不仅仅是这个区间的每一条数据。
SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;
这个sql会把所有在(1,10)区间的记录行锁住。id在[2,9]之间的数据行的插入都会被堵塞,但是1和10两条记录并不会锁住。
临建锁(Next-key Lock)
临建锁可以理解为一种特殊的间隙锁,可以理解为一种特殊的算法。
通过临建锁可以解决幻读的问题,每个数据行上的非唯一索引上都会存在一把临建锁,当某个事物持有该数据行的临建锁时,会锁住一段左开右闭区间的数据。
死锁
死锁成因
死锁一般是事务相互等待对方资源,最后形成环路造成的。
如何尽可能避免死锁?
- 以固定的顺序访问表和行。
- 同一个事务中,尽可能做到一次锁定所需要的所有资源。
- 为表添加合理的索引。如果不走索引,将会为表的每一行记录添加上锁,死锁的概率大大增大。
如何定位死锁?
-
通过应用业务日志定位到问题代码,找到相对应的sql。
-
确定数据库隔离级别
执行
select @@global.tx_isolation,可以确定数据库的隔离级别,如果我们数据库的隔离级别是RC,这样就可以很大概率上排除间隙锁造成的死锁嫌疑。 -
执行 show InnoDB status 查看最近死锁的日志。