总结一下本身多年来对MySQL的相关知识,作个梳理。mysql
本文用到的MySQL版本:5.7.22sql
咱们开的的各式各样系统中,系统运行须要CPU、内存、I/O、磁盘等等资源。但除了硬资源外,还有最为重要的软资源:数据。数据库
当人们访问操做咱们的系统时,其实归根是对数据的查看与生产。那么对于同一份数据,若是多个用户同时对它查看、修改时会出现什么问题呢?这必然会带来竞争,而为了控制并发的读取、修改数据会对数据形成的不一致、错乱等问题,数据库引入了锁的机制。并发
固然锁的问题解决了并发访问数据的问题,而不可避免的会对系统的性能产生负面影响。总结一下各类锁的使用场景方便在实践中更好的运用它从而提高系统性能。性能
咱们平常开发中用到最多的存储引擎是Innodb 与 MyISAM两种,而 Innodb 如今更可能是首选,所以主要是对 Innodb 的说明,MyISAM 跟可能是做为一个对比的角色。
MySQL的锁能够按照多种方式进行划分,这里使用最经常使用的两种方式进行划分:粒度与使用方式。测试
须要特别说明的是:乐观锁与悲观锁并非数据库中实现的锁机制,是须要咱们本身去实现的。它是一种思想,咱们不只仅能够用在MySQL中,其它地方有须要的也能够用到。而像悲观锁它也是利用数据库现有的机制进行实现的。下面先根据不一样分类对说明相关概念。spa
乐观锁 机制采起了更加宽松的加锁机制。悲观锁大多数状况下依靠数据库的锁机制实现,以保证操做最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销每每没法承受。相对悲观锁而言,乐观锁更倾向于开发运用。设计
悲观锁 具备强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度,所以,在整个数据处理过程当中,将数据处于锁定状态。悲观锁的实现,每每依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)。code
表级锁 是MySQL中锁定粒度最大的一种锁,表示对当前操做的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MyISAM与InnoDB都支持表级锁定。表级锁分为表共享读锁与表独占写锁。blog
行级锁 是Mysql中锁定粒度最细的一种锁,表示只针对当前操做的行进行加锁。行级锁能大大减小数据库操做的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
页级锁 是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。因此取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁。
这里须要说明的是,悲观锁是一种思想,它的实现是使用了 共享锁与排他锁来实现的。所以悲观锁自己并非MySQL实现的锁机制,它是咱们造出来的一个概念。
另外,我看到不少文章在讲悲观锁时,只说排他锁是悲观锁机制,没有说共享锁是什么机制,而我认为共享锁也属于悲观锁,具体缘由日后看。
MyISAM 相关的锁机制我就略过不总结了。
InnoDB 实现了两种类型的行锁,共享锁(S)与排他锁(X)。而后因为 InnoDB引擎又支持表级锁,因此它内部又有意向共享锁(IS)与意向排他锁(IX)。这两种表锁,都是InnoDB内部自动处理,换句话说咱们写代码是没法控制也不须要控制的。咱们能控制的是S与X锁。
在平常操做中,UPDATE、INSERT、DELETE InnoDB会自动给涉及的数据集加排他锁,通常的 SELECT 通常是不加任何锁的。咱们可使用如下方式显示的为 SELECT 加锁。
那么何时该用共享锁何时用排他锁呢?
通常来说,共享锁主要用在须要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操做(包括加锁的事物也只能读)。简单说就是你们均可以读数据,可是没法修改(更新或者删除),所以我认为::共享锁也是悲观锁::的一种实现。
排他锁是与共享锁相对应,自身加排他锁的事物可以本身发起修改操做,其它事物没法再对该数据加共享或者排他锁。
这里须要注意上面说到的一点,因为InnoDB引擎是行锁,无论咱们在这条数据上加了共享锁仍是排他锁,简单的select语句依然可使用的,由于默认在InnoDB中select是不加锁的。
这里还有一点,上图中咱们写到一个 间隙锁,这是什么东西?好比:当咱们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;对于键值在条件范围内但并不存在的记录,叫作“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。它存在的主要目的有一个是为了解决幻读问题,由于RR做为InnoDB的默认事物隔离级别,是存在幻读问题的,而咱们在实际操做中确没有出现,就是由于这里作了处理。
关于乐观锁是如何加锁的,这个不一样系统有不一样的实现,简单来讲,对每个数据维护一个版本号,每次读取时把版本号读取出来,更新时版本号+1。而后更新时将读取的版本号做为条件,若是有其它事物更新了,那么必然会致使版本号变化,由于本次更新不会成功。这种机制最大程度的保证了并发。
mysql> show status like 'innodb_row_lock%'; +-------------------------------+--------+ | Variable_name | Value | +-------------------------------+--------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 218276 | | Innodb_row_lock_time_avg | 18189 | | Innodb_row_lock_time_max | 51058 | | Innodb_row_lock_waits | 12 | +-------------------------------+--------+ 5 rows in set (0.05 sec)
上面的语句可以展现当前系统锁的状况,当系统锁争用比较严重的时候,Innodb_row_lock_waits
和 Innodb_row_lock_time_avg
的值会比较高。上面的数据是因为我作实验致使的。你们能够检查下本身的系统。
咱们经常说InnoDB是行锁,可是这里介绍一下它锁表的状况。
InnoDB行锁是经过索引上的索引项来实现的,这一点MySQL与Oracle不一样,后者是经过在数据中对相应数据行加锁来实现的。InnoDB这种行锁实现特色意味者:只有经过索引条件检索数据,InnoDB才会使用行级锁,不然,InnoDB将使用表锁!
在实际应用中,要特别注意InnoDB行锁的这一特性,否则的话,可能致使大量的锁冲突,从而影响并发性能。
这里咱们实际演示一下,一来让你们了解如何测试锁状况,二来这样用例子也许更容易说明问题。表结构以下:
Create Table: CREATE TABLE `tb_user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL DEFAULT '', `age` mediumint(9) NOT NULL DEFAULT '1', `money` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8
咱们看当where条件不是索引时,若是加了排他锁,对这个表其它行记录也不能再加排他锁了,这明显就是锁住了整个表。而若是条件是索引字段,则它只会对where条件指定的行数据加锁,另外一个事务能够对其它行数据加锁。
有些文章说只有表没有索引才会锁表,经过上面的实验,我以为是不许确的。