天天看点

面试必备常见存储引擎与锁的分类

MySQL的四大常见存储引擎

谈到 MyISAM 和 InnoDB 了我们先来了解一下什么是存储引擎吧。 MySQL 中的数据用各种不同的技术存储在文件(或者内存)中,这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能,我们把这些不同的技术以及配套的相关功能称为存储引擎(也称作表类型)。MySQL默认配置了许多不同的存储引擎,我们可以选择不同的存储引擎来满足我们对数据的处理(存储、检索等)需求,以改善我们应用程序的整体功能。正因为 MySQL 存储引擎的多样性,使得 MySQL 深受广大开发者的垂青。

我们的前提条件:我用的是 5.7.24-log 版本,可以在 Navicat 中通过 SELECT VERSION(); 命令查看。那么 MySQL 都有哪些存储引擎呢?我们可以使用sql命令 SHOW ENGINES; 来查看,结果如下:

面试必备常见存储引擎与锁的分类

Engine:表示储存引擎名称;

Support:表示 MySQL 是否支持该存储引擎, DEFAULT 为默认的存储引擎;

Comment:是对该存储引擎的功能描述,例如: InnoDB 支持事务、行级锁定和外键;

Transactions:是否支持事务;

XA:存储引擎是否支持分布式事务;

Savepoints:存储引擎是否支持保存点。

接着让我们来说一下其中比较常见的四大存储引擎吧。

InnoDB

InnoDB 是 MySQL ( MySQL5.5 以后)的默认存储引擎,支持事务、行级锁和外键,被用来处理大量短期事务。如果使用到外键、需求并发程度较高、数据一致性要求较高的话,那么通常选择 InnoDB 引擎,这也是互联网大厂使用 InnoDB 存储引擎的原因。除非有非常特别的原因需要使用其他的存储引擎,否则建议优先考虑 InnoDB 。但是对比 MyISAM , InnoDB 写的处理效率会差一些,并且会占用更多的磁盘空间以保留数据和索引。

MyISAM

MyISAM 提供了大量的特性,包含全文索引、压缩、空间行数等,支持3种不同的存储格式,分别是:静态表、动态表、压缩表。

静态表:表中的字段都是非变长字段,这样每个记录都是固定长度的。优点是存储非常迅速,容易缓存,出现故障容易恢复;缺点是占用的空间通常比动态表多(因为存储时会按照列的宽度定义补足空格),在取数据的时候,默认会把字段后面的空格去掉,如果不注意会把数据本身带的空格也会忽略。

动态表:表中的字段都是变长字段,记录不是固定长度的。这样存储的优点是占用的空间相对较少;缺点是频繁的更新、删除数据容易产生碎片,需要定期执行 OPTIMIZE TABLE 或者 myisamchk -r 命令来改善性能,并且出现故障的时候恢复相对比较困难。

压缩表:压缩表由myisamchk工具创建,占据非常小的空间,因为每条记录都是被单独压缩的,所以只有非常小的访问开支。

在 MyISAM 中,数据文件和索引文件可以放置在不同的目录(在创建表的时候通过 DATA DIRECTORY 和 INDEX DIRECTORY 语句指定文件的绝对路径),平均分配IO,获取更快的访问速度。但是 MyISAM 不支持事务,不支持外键,也不支持行级锁,支持表级锁,有个缺陷就是崩溃后无法恢复。如果应用程序以检索为主,只有少量的插入、更新和删除操作,并且对事物的完整性、并发程度不是很高的话,通常建议选择 MyISAM 存储引擎。

Memory

Memory 存储引擎使用存在内存中的内容来创建表,所以它的访问速度非常快,并且默认使用 HASH 索引。但是一旦服务器关闭或者 mysqld 守护进程崩溃时,所有的 Memory 数据都会丢失,但表还会继续存在,获得速度的同时也带来了一些缺陷。

它要求存储在 Memory 数据表里的数据使用的是长度不变的格式,这意味着不能使用 BLOB 和 TEXT 这样的长度可变的数据类型, VARCHAR 是一种长度可变的类型,但因为它在 MySQL 内部当做长度固定不变的 CHAR 类型,所以可以使用。

服务器需要足够的内存来维持在同一时间内使用的 MEMORY 表,当不再使用 MEMORY 表时,要释放 MEMORY 表所占用的内存,应该执行 DELETE FROM 或 truncate table 或者删除整个表。每个 MEMORY 表中放置的数据量的大小,受到 max_heap_table_size 系统变量的约束,这个系统变量的初始值是16M,同时在创建 MEMORY 表时可以使用 MAX_ROWS 子句来指定表中的最大行数。它通常用于更新不太频繁的小表。

Merge

Merge 存储引擎是一组 MyISAM 表的组合,这些 MyISAM 表结构必须完全相同, Merge 表本身没有数据,对 Merge 类型的表进行查询、更新、删除的操作,实际上是对内部的 MyISAM 表进行的。Merge表在磁盘上保留两个文件,一个是 .frm 文件存储表定义、一个是 .MRG 文件存储 Merge 表的组成等。 MERGE 表的优点在于可以突破对单个 MyISAM 表大小的限制,并且通过将不同的表分布在多个磁盘上,可以有效地改善 MERGE 表的访问效率。

我们可以通过 show create table 表名 命令来查看表使用的引擎,由以下代码可以看出 test 表使用的是 MyISAM 存储引擎。

CREATE TABLE `test` (
  `id` int(1) NOT NULL AUTO_INCREMENT,
  `name` varchar(8) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=46 DEFAULT CHARSET=utf8
           

相对其他数据库而言, MySQL 的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如, MyISAM 和 MEMORY 存储引擎采用的是表级锁( table-level locking ); BDB 存储引擎(5.1之后就不直接支持了,因为 BDB 被 oracle 收购了)采用的是页面锁( page-level locking ),但也支持表级锁; InnoDB 存储引擎既支持行级锁( row-level locking ),也支持表级锁,但默认情况下是采用行级锁。接下来就让我们来了解一下 MyISAM 和 InnoDB 锁的具体分类与使用方法。

MyISAM锁

MyISAM 存储引擎支持的表级锁分为表共享读锁( Table Read Lock )和表独占写锁( Table Write Lock ),以下简称读锁和写锁。先看一下他们的特性:

读锁:不会阻碍其它进程的读,但是会阻碍写,只有当读锁释放之后,才会执行其它进程的写–读锁阻塞写锁,但是不阻塞读锁;

写锁:会阻碍其他进程的读和写,只有当写锁释放,才会执行其它写操作–写锁阻塞读锁和写锁;

接下来让我们用例子来演示一下上边的结论,在演示之前,先让我们来说几个命令吧。

LOCK TABLE 表名 WRITE/READ:给表加写锁或者读锁;

UNLOCK TABLES:给表解锁

演示一:表共享读锁

面试必备常见存储引擎与锁的分类

当 session1 给表 test 加读锁时, session1 只能读取当前表的数据,不可以读其他表,也不可以修改 test 和其他表; session2 可以读取 test 表数据,更新 test 表阻塞,但是可以修改和查询其他表数据。

演示二:表独占写锁

面试必备常见存储引擎与锁的分类

当 session1 给表 test 加写锁时,可以更新 test 表,读 test 表阻塞,但是不可以修改和查询其他表数据; session2 查询和更新 test 表阻塞,但是可以查询和更新其他表。

另外我们还可以使用 show open tables 命令来查看在表缓存中当前被打开的非TEMPORARY表的锁使用情况,其中In_use表示有锁正在使用。

面试必备常见存储引擎与锁的分类

也可以使用 show status like ‘table%’ 命令来查看锁的争夺情况,其中 Table_locks_waited 为等待次数,每等待一次,值就加一,值越大,表示存在越严重的表级锁争用; Table_locks_immediate 为产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁,值加一。

MyISAM 默认是使用 select 语句加读锁,增删改操作加写锁。 MyISAM 是偏读锁,读写调度写优先,不适合做写为主的表的引擎。因为写锁后,其他线程不能做任何操作,大量更新会使查询很难得到锁,从而永远阻塞。

InnoDB锁

上篇文章中我们讲过了记录锁(行锁)、间隙锁和临键锁,这里就不再赘述了。接下来我们按照锁的模式讲一下 InnoDB 里的共享锁、排他锁和意向锁,其中共享锁和排他锁属于行级锁,行级锁都是基于索引项的,如果没有索引项,则添加的是表级锁;意向锁属于表级锁。

共享锁:Shared Locks,简称S锁

若事务T对数据对象A加上S锁,则事务T只能读A;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。可以通过 select … lock in share mode 来加共享锁,通过 Commit 、 Rollback 来释放锁。

排他锁:Exclusive lock,简称X锁,也叫互斥锁

若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁(排他锁不可与其他锁共存),直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。可以通过 select … for update 手动加锁,也可以通过增删改操作自动加锁,通过 Commit 、 Rollback 来释放锁。

意向锁:Intention Locks

说起意向锁,大家先来考虑一下这个问题:假设存在两个事务A和B对表 test 进行操作,首先事务A对第十行数据加了一把读锁,锁住了该行数据,让这一行只能读,不能写;然后事务B想要对该表加一把表级的写锁,那么事务B能否加锁成功呢?思考两秒钟…答案当然是否定的,即事务B无法加锁成功。如果我们假设它加锁成功的话,那么理论上它就能修改表中的任意一行,这将与事务A持有的行级锁(读锁)产生冲突。而数据库想避免这种冲突的话,就需要将事务B的加锁申请给阻塞住,直到事务A的行锁被释放。那么问题来了,数据库是怎么判断这种冲突的呢?我们可以想到两种方案:一、判断表是否已被其他事务用表锁锁表;二、判断表中的每一行是否已被行锁锁住。很显然,如果采用第二种方法的话,需要一行一行去遍历整张表,效率太慢进而造成系统消耗,所以我们选择第一种方法,这也就是意向锁是表锁的原因。

意向锁是放置在资源层次结构的一个级别上的锁,以保护较低级别资源上的共享锁或排它锁,意向锁无法手动创建。如果对任一结点加锁时,必须先对它的上层结点加意向锁也就是如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁。意向锁的执行流程:如果另一个任务试图在该表级别上应用共享或排它锁,则受到由第一个任务控制的表级别意向锁的阻塞,第二个任务在锁定该表前不必检查各个页或行锁,而只需检查表上的意向锁,所以意向锁不是用来给数据加锁的,而是用来判断数据有没有存在锁的标志。下面介绍两种常用的意向锁:意向共享锁( Intent Share Lock ,简称IS锁)、意向排它锁( Intent Exclusive Lock ,简称IX锁)。

意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

意向锁的兼容关系如下:

面试必备常见存储引擎与锁的分类

其他锁简介(了解)

InnoDB还包含插入意向锁、自增锁和空间锁。

插入意向锁( Insert Intention Locks ):是间隙锁的一种,它的目的是为了提高插入性能。在多个事务对同一个索引中的同一个范围区间插入记录时,如果插入的位置不冲突,不会阻塞彼此,主要是不需要去申请排他锁。

AUTO-INC Locks

MySQL

MySQL

innodb_autoinc_lock_mode

simple insert

总结

MySQL 这三种锁的特性可大致归纳如下:

表级锁(偏读):开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

行级锁(偏写):开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。