MySQL与InnoDB简单整理

本文是阅读『浅入浅出』MySQL 和 InnoDB后的简单整理。原文链接

1.数据库的定义

原文提出数据库是一个比一堆数据的集合更为复杂的概念,并说明了两个较为容易混淆的概念数据库和实例。

  • 数据库:物理操作文件系统或其他形式文件类型的集合;
  • 实例:MySQL 数据库由后台线程以及一个共享内存区组成;

对上述概念的引用来自于《MySQL技术内幕:InnoDB存储引擎》

1.1 数据库和实例

MySQL 中,实例和数据库往往都是一一对应的,而我们也无法直接操作数据库,而是要通过数据库实例来操作数据库文件,可以理解为数据库实例是数据库为上层提供的一个专门用于操作的接口。
MySQL与InnoDB简单整理_第1张图片
启动一个MySQL实例会产生两个进程:

  1. mysqld:数据库服务守护进程
  2. mysql_safe:用于检查设置mysqld的控制程序,监视mysqld程序并在其错误时对其进行错误检查和重启。

1.2 MySql架构

简单的mysql结构大致如下三层:
MySQL与InnoDB简单整理_第2张图片
第一层:用于建立连接和线程处理;
第二层:包含了大多数MySQL的核心服务,包括了对SQL的解析、分析、优化和缓存等功能。以及视图,存储过程;
第三层:存储引擎,如InnoDB、MyISAM等。

2.数据的存储

本节主要介绍了MySQL种数据的如何存储的。在InnoDB存储引擎中,所有的数据都被逻辑地存放在表空间中。表空间是存储引擎中最高的存储单位,其中表空间又分为多个段(segement),而段又被分为多个区(extent),区又被分为多个页(page),页中又是按行存储的。并且,页是磁盘管理的最小单位。
同一个数据库实例所有表空间都有相同的页大小。且默认为16KB
MySQL与InnoDB简单整理_第3张图片

2.1 如何存储记录

InnoDB存储引擎中有几种文件格式,如COMPACT、REDUNDANT、COMPRESSED、DYNAMIC。COMPACT行记录的第一部分存储数据列长度、REDUNDANT则存储的是偏移量。磁盘组织形式如下:
MySQL与InnoDB简单整理_第4张图片
当 InnoDB 使用 Compact 或者 Redundant 格式存储极长的 VARCHAR 或者 BLOB 这类大对象时,我们并不会直接将所有的内容都存放在数据页节点中,而是将行数据中的前 768 个字节存储在数据页中,后面会通过偏移量指向溢出页。

2.2 数据页结构

页是InnoDB管理数据的最小磁盘单位,而B树的节点存储的就是表数据的页面。页面的数据结构如下图所示:
在这里插入图片描述
page header/dictionary主要为页面状态信息
file header/ tail是记录页的头信息
每一个数据页中都包含 Infimum 和 Supremum 这两个虚拟的记录
User Records 就是整个页面中真正用于存放行记录的部分,而 Free Space 就是空余空间了
B+树在查找时,并不会从树种直接找到记录。而是根据索引获取记录所在的页,将整个页加载到内存中,再通过page dictionary中的稀疏索引找到对应的记录。

3. 索引

索引时数据库中比较重要的概念。MySQL数据库中通过B+Tree实现索引,B+树是平衡树,所以查找长度为树的高度。因此索引优化能够对搜索效率带来极大提升。

聚集索引和辅助索引

数据库中的 B+ 树索引可以分为:

  • 聚集索引(clustered index):按照表中主键的顺序构建一颗B+树,并在叶子节点中存放表的行数据(全部行数据);
  • 辅助索引(secondary index):所有非聚集索引都为辅助索引。同时,辅助索引也是通过B+树实现的,但是其叶子节点并不包含全部行数据,仅包含索引列中的所有键和一个书签信息。
    聚集索引用于组织表信息;辅助索引用于加速数据查找。

4. 锁

MySQL中的锁主要分为乐观锁和悲观锁。
乐观锁:认为对同一个数据的并发操作,是不会发生数据修改的。一般用在并发读操作中。
悲观锁:认为对同一个数据的并发操作,一定会发生修改。所以会采用加锁的方式。一般用在并发写操作中。
InnoDB使用的存储引擎使用的就是悲观锁,同时按照锁粒度划分可分为行锁和表锁。

4.1 并发控制机制

乐观锁和悲观锁其实都是并发控制的机制,同时它们在原理上就有着本质的差别

  • 乐观锁是一种思想,它其实并不是一种真正的『锁』,它会先尝试对资源进行修改,在写回时判断资源是否进行了改变,如果没有发生改变就会写回,否则就会进行重试,在整个的执行过程中其实都没有对数据库进行加锁
  • 悲观锁就是一种真正的锁了,它会在获取资源前对资源进行加锁,确保同一时刻只有有限的线程能够访问该资源,其他想要尝试获取资源的操作都会进入等待状态,直到该线程完成了对资源的操作并且释放了锁后,其他线程才能重新操作资源;
    乐观锁不会存在死锁的问题,但是由于更新后验证,所以当冲突频率重试成本较高时更推荐使用悲观锁,而需要非常高的响应速度并且并发量非常大的时候使用乐观锁就能较好的解决问题,在这时使用悲观锁就可能出现严重的性能问题;在选择并发控制机制时,需要综合考虑上面的四个方面(冲突频率、重试成本、响应速度和并发量)进行选择。

4.2 锁的种类

  • 共享锁(读锁):当线程读取数据是,有另一个线程对该数据进行读取是被允许的
  • 互斥锁(写锁):当线程占用数据时,其他线程对数据的访问将被不被允许
    数据库中共享锁代表了读操作,互斥锁代表了写操作。所以我们再操作数据时可以并行读,和串行写。

4.3 锁的粒度

共享锁和互斥锁都是对某数据进行加锁,InnoDB支持多种粒度的锁,如表锁和行锁。同时引入了意向锁用来支持多种粒度的锁定。意向锁是一种表级锁。
同时意向锁也分为两类:

  • 意向共享锁:事务想要在获得表中某些记录的共享锁,需要在表上先加意向共享锁;
  • 意向互斥锁:事务想要在获得表中某些记录的互斥锁,需要在表上先加意向互斥锁;
    意向锁并不会阻塞全表扫描的操作。这样就提供了一种方便。比如当一个线程a正在修改表中某行记录时,另一个线程b需要读取表中另一行数据,此时便不需要进行全表扫描。因为a线程修改前先对表加了意向互斥锁,b线程则需要等待a线程释放意向互斥锁后才能获取锁。

4.4 锁的算法

  1. Gap Lock
  2. Record Lock
  3. Next-Key Lock
    (这儿没太理解。需要了解可以看一下原文。后面操作一下补上)

4.5 死锁的发生

首先介绍一下mysql的for update语句。当某线程操作数据时,会有另一线程对数据进行修改操作,则会导致后续的许多错误。(感觉像是脏读的发生)所以此时,在select语句中用for update对数据加锁。同时,在粒度上for update是行级锁。只有在where条件中没有明确指定的主键时为表锁。
所以此时,如果要发生死锁我们可以设想这样的场景。两个会话当中,线程a查询表中id为1的数据并对其进行加锁。线程b查询表中id为2的数据并对其进行加锁,同时线程b期待访问id为1的数据。此时线程a想访问id为2的数据。但线程b正在访问,所以线程a等待,同时也无法释放id为1的数据的锁。两个线程互相持有对方期待数据的锁。并不释放造成死锁。

5.事务与隔离级别

事务是数据库操作中一个重要的概念。事务主要包含ACID四大特性。
原子性(Atomicity):整个事务中的操作要不全部成功要不失败全部回滚。
一致性(Consistency):事务的状态改变可以不透明,但不管在多少并发下,所达到的最终状态必须和串行执行是一样的。
隔离性(Isolation):隔离状态执行事务,就好像在同一时间内只有一个事务执行一样。
持久性(Durability):事务状态改变后,其状态永久保存不会被回滚或改变。
其中在隔离性上又分为了几种不同的隔离级别。

5.1 隔离级别

首先需要介绍一下事务中可能会出现的3种问题:

  1. 脏读:某线程读到了另一个线程种未提交的数据
  2. 不可重复读:一个事务内,多次读取同一个数据。此时另一个事务也访问该数据,但是由于第二个事务的修改,会造成第一个事务多次读取的值不一样。
  3. 幻读:例如一个事务对表中的全部数据进行查询,同时另一个事务对该表进行添加和删除操作。此时第一个事务会发现表中在事务种发生了数据增加/删除,好像发生幻觉一样。
    对应以上3种问题,数据库种有4种隔离级别
  4. Read uncommitted读未提交:最低级别,都有可能发生
  5. Read committed 读已提交:避免脏读
  6. Repeatable read 可重复读:避免不可重复读
  7. Serializable 串行化: 隔离级别最高
    mysql 默认支持repeatable read
    MySQL与InnoDB简单整理_第5张图片

你可能感兴趣的:(数据库)