Mysql(四)存储引擎、锁

文章目录

    • 一、存储引擎
      • 1.1 常用存储引擎及特性对比
      • 1.2 MyISAM
      • 1.3 InnoDB
        • 1.3.1 自动增长列
        • 1.3.2 外键约束
        • 1.3.3 存储方式
      • 1.4 MEMORY
      • 1.5 MyISAM与InnoDB区别
      • 1.6 MyISAM索引与InnoDB索引的区别
      • 1.7 存储引擎选择
      • 1.8 存储引擎优化
      • 1.9 如何怎么针对表设置引擎
      • 1.10 怎么根据引擎选择合适的字符串类型
      • 1.11 一张表,里面有ID自增主键,当插入了17条记录之后,删除了第15、16、17条记录,再把Mysql重启,再插入一条记录,这条记录的ID是18还是15
      • 1.12 count(*)在不同引擎的实现方式
      • 1.13 Innodb如何实现Mysql的事务
    • 二、锁
      • 2.1 表级锁、行级锁和页面锁
      • 2.2 MyISAM锁
        • 2.2.1 表锁的使用
        • 2.2.2 并发插入
        • 2.2.3 MyISAM的锁调度
      • 2.3 InnoDB锁
        • 2.3.1 共享锁和排他锁
        • 2.3.2 意向共享锁和意向排它锁
        • 2.3.3 InnoDB四种锁的兼容情况
        • 2.3.4 间隙锁(Next-Key 锁)
        • 2.3.5 恢复和复制的需要,对InnoDB锁机制的影响
        • 2.3.6 什么时候使用表锁
        • 2.3.7 死锁
      • 2.4 乐观锁和悲观锁
      • 2.5 间隙锁、记录锁、临键锁
      • 2.6 死锁
      • 2.7 Mysql中的锁总结
        • 2.7.1 MyISAM锁
        • 2.7.2 InnoDB锁总结
      • 2.8 隔离级别与锁的关系
      • 2.9 MVCC的实现原理
        • 2.9.1 MVCC实现原理
        • 2.9.2 read view
        • 2.9.3 read view
      • 2.10 快照读和当前读
      • 2.11 共享锁和排他锁
      • 2.12 Mysql锁的相关问题
        • 2.12.1 MySQL中InnoDB引擎的行锁是通过加在什么上完成的
        • 2.12.2 优化高并发事务

本系列文章:
  Mysql(一)三大范式、数据类型、常用函数、事务
  Mysql(二)Mysql SQL练习题
  Mysql(三)索引、视图、存储过程、触发器、分区表
  Mysql(四)存储引擎、锁
  Mysql(五)Mysql架构、数据库优化、主从复制
  Mysql(六)慢查询、执行计划、SQL语句优化

一、存储引擎

  MySQL 5.0支持的存储引擎包括MyISAM、InnoDB、BDB、MEMORY等,其中InnoDB和BDB提供事务安全表,其他存储引擎都是非事务安全表。
  查看系统变量值,命令为:show variables
  查看某个系统变量,命令为:SHOW VARIABLES like '变量名'
  查看Mysql提供的所有存储引擎,命令为:show engines,示例:
Mysql(四)存储引擎、锁_第1张图片

1.1 常用存储引擎及特性对比

特点 MyISAM InnoDB MEMORY MERGE NDB
存储限制 64TB 没有
事务安全 支持
锁机制 表锁 行锁 表锁 表锁 行锁
B树索引 支持 支持 支持 支持 支持
哈希索引 支持 支持
全文索引 支持
集群索引 支持
数据缓存 支持 支持 支持
索引缓存 支持 支持 支持 支持 支持
数据可压缩 支持
空间使用 N/A
内存使用 中等
批量插入的速度
外键 支持

1.2 MyISAM

  MyISAM不支持事务、也不支持外键,其优势是访问的速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用基本上都可以使用这个引擎来创建表
  每个MyISAM在磁盘上存储成3个文件,其文件名都和表名相同,扩展名分别是:

frm(存储表定义);
MYD(MYData,存储数据);
MYI (MYIndex,存储索引)。

  数据文件和索引文件可以放置在不同的目录。
  MyISAM类型的表可能会损坏,原因可能是多种多样的,损坏后的表可能不能访问,会提示需要修复或者访问后返回错误的结果。MyISAM类型的表提供修复的工具,可以用CHECK TABLE语句来检查MyISAM表的健康,并用REPAIR TABLE语句修复一个损坏的MyISAM表。
  表损坏可能导致数据库异常重新启动。

1.3 InnoDB

  InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。对比MyISAM的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。

1.3.1 自动增长列

  InnoDB表的自动增长列可以手工插入,但是插入的值如果是空或者0,则实际插入的将是自动增长后的值。
  可以通过ALTER TABLE *** AUTO_INCREMENT = n;语句强制设置自动增长列的初识值,
默认从1开始,但是该强制的默认值是保留在内存中的,如果该值在使用之前数据库重新启动,那么这个强制的默认值就会丢失,就需要在数据库启动以后重新设置。
  可以使用 LAST_INSERT_ID()函数,查询当前线程最后插入记录使用的值。如果一次插入了多条记录,那么返回的是第一条记录使用的自动增长值。
  对于InnoDB表,自动增长列必须是索引。如果是组合索引,也必须是组合索引的第一
列,但是对于MyISAM表,自动增长列可以是组合索引的其他列,这样插入记录后,自动增长列是按照组合索引的前面几列进行排序后递增的。

1.3.2 外键约束

  MySQL支持外键的存储引擎只有InnoDB,在创建外键的时候,要求父表必须有对应的索引,子表在创建外键的时候也会自动创建对应的索引。
  在创建索引的时候,可以指定在删除、更新父表时,对子表进行的相应操作,包括RESTRICT、CASCADE、SET NULL和NO ACTION。其中RESTRICT和NO ACTION相同,是指限制在子表有关联记录的情况下父表不能更新;CASCADE表示父表在更新或者删除时,更新或者删除子表对应记录;SET NULL则表示父表在更新或者删除的时候,子表的对应字段被 SET NULL。选择后两种方式的时候要谨慎,可能会因为错误的操作导致数据的丢失。
  当某个表被其他表创建了外键参照,那么该表的对应索引或者主键禁止被删除。

1.3.3 存储方式

  InnoDB存储表和索引有以下两种方式:

  • 1 、使用共享表空间存储
      这种方式创建的表的表结构保存在.frm文件中,数据和索引保存在 innodb_data_home_dir和innodb_data_file_path定义的表空间中,可以是多个文件。
  • 2 、使用多表空间存储
      这种方式创建的表的表结构仍然保存在.frm文件中,但是每个表的数据和索引单独保存在.ibd中。如果是个分区表,则每个分区对应单独的.ibd文件,文件名是“表名+分区名”,可以在创建分区的时候指定每个分区的数据文件的位置,以此来将表的IO均匀分布在多个磁盘上。

  要使用多表空间的存储方式,需要设置参数innodb_file_per_table,并重新启动服务后才可以生效,对于新建的表按照多表空间的方式创建,已有的表仍然使用共享表空间存储。如果将已有的多表空间方式修改回共享表空间的方式,则新建表会在共享表空间中创建,但已有的多表空间的表仍然保存原来的访问方式。所以多表空间的参数生效后,只对新建的表生效。
  多表空间的数据文件没有大小限制,不需要设置初始大小,也不需要设置文件的最大限制、扩展大小等参数。
  对于使用多表空间特性的表,可以比较方便地进行单表备份和恢复操作,但是直接复制.ibd文件是不行的,因为没有共享表空间的数据字典信息,直接复制的.ibd文件和.frm文件恢复时是不能被正确识别的,但可以通过以下命令:

	ALTER TABLE tbl_name DISCARD TABLESPACE;
	ALTER TABLE tbl_name IMPORT TABLESPACE;

  将备份恢复到数据库中,但是这样的单表备份,只能恢复到表原来在的数据库中,而不能恢复到其他的数据库中。如果要将单表恢复到目标数据库,则需要通过mysqldump和mysqlimport来实现。

1.4 MEMORY

  MEMORY存储引擎使用存在内存中的内容来创建表。每个MEMORY表只实际对应一个磁盘文件,格式是.frm。MEMORY类型的表访问非常得快,因为它的数据是放在内存中的,并且默认使用HASH索引,但是一旦服务关闭,表中的数据就会丢失掉。
  给MEMORY表创建索引的时候,可以指定使用HASH索引还是BTREE索引
  服务器需要足够内存来维持所有在同一时间使用的MEMORY表,当不再需要MEMORY表的内容之时,要释放被MEMORY表使用的内存,应该执行DELETE FROM或TRUNCATE TABLE,或者整个地删除表(使用 DROP TABLE 操作)。
  每个MEMORY表中可以放置的数据量的大小,受到max_heap_table_size系统变量的约束,这个系统变量的初始值是16MB,可以按照需要加大。此外,在定义MEMORY表的时候,可以通过MAX_ROWS子句指定表的最大行数。
  MEMORY类型的存储引擎主要用在那些内容变化不频繁的代码表,或者作为统计操作的中间结果表,便于高效地对中间结果进行分析并得到最终的统计结果。对MEMORY存储引擎的表进行更新操作要谨慎,因为数据并没有实际写入到磁盘中,所以一定要对下次重新启动服务后如何获得这些修改后的数据有所考虑。

1.5 MyISAM与InnoDB区别

  存储引擎:MySQL中的数据、索引以及其他对象是如何存储的,是一套文件系统的实现。常用的存储引擎有:

  • 1、InnoDB引擎
      Innodb引擎提供了对数据库ACID事务的支持,并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。
  • 2、MyISAM引擎
      不提供事务的支持,也不支持行级锁和外键
  • 3、MEMORY引擎
      所有的数据都在内存中,数据的处理速度快,但是安全性不高。

  MyISAM与InnoDB区别:

MyISAM Innodb
存储结构 每张表被存放在三个文件
frm:表结构
MYD:数据文件
MYI:索引文件
frm:表定义文件
ibd :数据文件
InnoDB表的大小只受限于操作系统文件的大小,一般为2GB
存储空间 MyISAM可被压缩,存储空间较小 InnoDB的表需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引
可移植性、备份及恢复 由于MyISAM的数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作 免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了
记录存储顺序 按记录插入顺序保存 按主键大小有序插入
外键 不支持 支持
事务 不支持 支持
锁支持 支持表级锁 支持行级锁、表级锁
SELECT MyISAM更优
INSERT、UPDATE、DELETE InnoDB更优
select count(*) myisam更快,因为myisam内部维护了一个计数器,可以直接调取。
索引的实现方式 B+树索引,myisam 是堆表 B+树索引,Innodb 是索引组织表
哈希索引 不支持 支持
全文索引 支持 不支持(在5.6之后支持)
索引类型 非聚簇索引 聚簇索引
适合操作类型 大量select 大量insert、delete、update
MVCC 不支持 支持

  Mysql默认引擎:5.1版本之前默认引擎是MyISAM,之后是InnoDB

1.6 MyISAM索引与InnoDB索引的区别

  • 1、InnoDB索引是聚簇索引(表数据和文件放在一起),MyISAM索引是非聚簇索引
      聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中数据也是索引的一部分,每张表只能拥有一个聚簇索引
      非聚簇索引:表数据和索引单独一个文件保存(MyISAM)。其中.frm文件存放表结构,.MYI文件存放索引数据,.MYD存放实际数据。
  • 2、InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。
  • 3、MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据
  • 4、InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效

1.7 存储引擎选择

  如果没有特别的需求,使用默认的Innodb即可。
  MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。
  Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。

1.8 存储引擎优化

  不同的业务表,应该选择不同的存储引擎,例如:查询插入操作多的业务表,用MyISAM。临时数据用Memery。常规的并发大更新多的表用InnoDB
  字段定义原则:使用可以正确存储数据的最小数据类型

  • 1、整数类型
      INT有8种类型(tinyint、smallint、mediumint、int、integer、bigint、bit),不同的类型的最大存储范围是不一样的。比如性别,可以用 TINYINT。
  • 2、字符类型
      变长情况下,varchar更节省空间,但是对于varchar字段,需要一个字节来记录长度。因此固定长度的用char,不要用varchar。
  • 3、非空
      非空字段尽量定义成NOT NULL,提供默认值,或者使用特殊值、空串代替NULL 。NULL 类型的存储、优化、使用都会存在问题
  • 4、不要用外键、 触发器、 视图
      降低了可读性;影响数据库性能,应该把把计算的事情交给程序,数据库专心做存储;数据的完整性应该在程序中检查。
  • 5、大文件存储
      不要用数据库存储图片或者大文件;把文件放在NAS(网络存储器) 上,数据库只需要存储URI(相对路径),在应用中配置NAS服务器地址。
  • 6、表拆分
      将不常用的字段拆分出去,避免列数过多和数据量过大。

1.9 如何怎么针对表设置引擎

  通过ENGINE=xxx设置引擎。示例:

create table person(
	id int primary key auto_increment,
	username varchar(32)
) ENGINE=InnoDB

1.10 怎么根据引擎选择合适的字符串类型

  • MyISAM
      MyISAM数据表,最好使用固定长度的数据列代替可变长度的数据列。
  • MEMORY
      MEMORY数据表都使用固定长度的数据行存储,因此无论使用CHAR或VARCHAR列都没有关系,两者都是作为CHAR类型处理的。
  • InnoDB
      建议使用VRCHAR类型。对于InnoDB数据表,内部的行存储格式没有区分固定长度和可变长度列。因此本质上,使用固定长度的CHAR列不一定比使用可变长度的VARCHAR列简单。因此,主要的性能是数据行使用的存储总量。由于CHAR平均占用的空间多于VARCHAR,因此使用VARCHAR来最小化需要处理的数据行的存储总量和磁盘IO是比较好的。

1.11 一张表,里面有ID自增主键,当插入了17条记录之后,删除了第15、16、17条记录,再把Mysql重启,再插入一条记录,这条记录的ID是18还是15

  1)如果表的存储引擎是MyISAM,那么是18。因为 MyISAM表会把自增主键的最大ID记录到数据文件里,重启MySQL自增主键的最大ID也不会丢失
  2)如果表的存储引擎是InnoDB,那么是15。InnoDB 表只是把自增主键的最大ID记录到内存中,所以重启数据库或者是对表进行OPTIMIZE 操作,都会导致最大ID丢失。

1.12 count(*)在不同引擎的实现方式

  MyISAM:把一个表的总行数存在了磁盘上,执行count(*)的时候直接返回这个数值,效率高。
  InnoDB:执行count(*)的时候,需要把数据一行一行从引擎读出来然后累积计数。

1.13 Innodb如何实现Mysql的事务

Mysql(四)存储引擎、锁_第2张图片
  事务进行过程中,每次sql语句执行,都会记录undo log和redo log,然后更更新数据形成脏页,然后redo log按照时间或者空间等条件进行落盘,undo log和脏页按照checkpoint进行落盘,落盘后相应的redo log就可以删除了。此时,事务还未COMMIT,如果发生崩溃,则首先检查checkpoint记录,使用相应的redo log进行数据和undo log的恢复,然后查看undo log的状态发现事务尚未提交,然后就使用undo log进行行事务回滚。事务执⾏行行COMMIT操作时,会将本事务相关的所有redo log都进行落盘,只有所有redo log落盘成功,才算COMMIT成功。然后内存中的数据脏⻚页继续按照checkpoint进行落盘。如果此时发生了崩溃,则只使用redo log恢复数据。

二、锁

  锁是计算机协调多个进程或线程并发访问某一资源的机制
  相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是:不同的存储引擎支持不同的锁机制:

  1. MyISAM和MEMORY存储引擎采用的是表级锁
  2. InnoDB存储引擎既支持行级锁,也支持表级锁,但默认情况下是采用行级锁

2.1 表级锁、行级锁和页面锁

  • 1、表级锁
      表级锁是mysql锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。
      该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。表锁被大部分的mysql引擎支持,MyISAM和InnoDB都支持表级锁。
      MyISAM只支持表锁,因此性能相对Innodb来说相对降低,而Innodb支持表锁和行锁。
      表级锁的特点:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低
  • 2、行级锁
      行锁的是mysql锁中粒度最小的一种锁,因为锁的粒度很小,所以发生资源争抢的概率也最小,并发性能最大,但是也会造成死锁,每次加锁和释放锁的开销也会变大。
      主要是Innodb使用行锁,Innodb也是Mysql在5.5.5版本之后默认使用的存储引擎。
      行级锁的特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高
  • 3、页面锁
      开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
  • 4、表级锁和行级锁的对比
      仅从锁的角度来说:
  1. 表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;
  2. 行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

  OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易;
  OLAP是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。

  1. OLTP系统强调数据库内存效率,强调内存各种指标的命令率,强调绑定变量,强调并发操作;
  2. OLAP系统则强调数据分析,强调SQL执行市场,强调磁盘I/O,强调分区等。

2.2 MyISAM锁

  MyISAM存储引擎只支持表锁。
  可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁的使用情况。如果Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况。
  MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。锁模式的兼容性:
Mysql(四)存储引擎、锁_第3张图片
  对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对 MyISAM 表的写操作,则会阻塞其他用户对同一表的读和写操作。MyISAM表的读操作与写操作之间,以及写操作之间是串行的。

2.2.1 表锁的使用

  MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预。

2.2.2 并发插入

  在一定条件下,MyISAM表也支持查询和插入操作的并发进行。
  MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。

  当concurrent_insert设置为0时,不允许并发插入。
  当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
  当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。

 ​ ​MyISAM表的读和写是串行的,这是就总体而言的,在一定条件下,MyISAM也支持查询和插入操作的并发执行。
 ​ ​假如session1可以通过lock table mylock read local获取表的read local锁定,session1不能对表进行更新或者插入操作,session1不能查询没有锁定的表,session1不能访问其他session插入的记录。此时session2可以查询该表的记录,session2可以进行插入操作,但是更新会阻塞,。
 ​ ​然后session1通过unlock tables释放锁资源,session1可以查看session2插入的记录,session2获取锁,更新操作完成。

 ​ ​可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺。

mysql> show status like 'table%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Table_locks_immediate | 352   |
| Table_locks_waited    | 2     |
+-----------------------+-------+

 ​ ​如果Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况。

2.2.3 MyISAM的锁调度

  MyISAM存储引擎的读锁和写锁是互斥的,读写操作是串行的。如果一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL会先让写进程获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前。这是因为MySQL认为写请求一般比读请求要重要。这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因。大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。
  开发者可以通过一些设置来调节MyISAM的调度行为。

  1. 通过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。
  2. 通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
  3. 通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。

  另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。

2.3 InnoDB锁

  可以通过检查InnoDB_row_lock状态变量,来分析系统上的行锁的争夺情况。

mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 18702 |
| Innodb_row_lock_time_avg      | 18702 |
| Innodb_row_lock_time_max      | 18702 |
| Innodb_row_lock_waits         | 1     |
+-------------------------------+-------+

  如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors来进一步观察发生锁冲突的表、数据行等,并分析锁争用的原因。
  创建和删除监视器示例:

	CREATE TABLE innodb_monitor(a INT) ENGINE=INNODB;
	DROP TABLE innodb_monitor;

  设置监视器后,在SHOW INNODB STATUS的显示内容中,会有详细的当前锁等待的信息,包括表名、锁类型、锁定记录的情况等,便于进行进一步的分析和问题的确定。打开监视器以后,默认情况下每15秒会向日志中记录监控的内容,如果长时间打开会导致.err 文件变得非常的巨大,所以用户在确认问题原因之后,要记得删除监控表以关闭监视器,或者通过使用“–console”选项来启动服务器以关闭写日志文件。

2.3.1 共享锁和排他锁

  InnoDB实现了以下两种类型的行锁:共享锁和排他锁。

  • 1、共享锁
      共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改
      示例:若事务A对数据对象1加上S锁,则事务A可以读数据对象1但不能修改,其他事务只能再对数据对象1加S锁,而不能加X锁,直到事务A释放数据对象1上的S锁。这保证了其他事务可以读数据对象1,但在事务A释放数据对象1上的S锁之前不能对数据对象1做任何修改。
  • 2、排他锁
      排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁。
      示例:若事务A对数据对象1加上X锁,事务A可以读数据对象1也可以修改数据对象1,其他事务不能再对数据对象1加任何锁,直到事务A释放数据对象1上的锁。这保证了其他事务在事务A释放数据对象1上的锁之前不能再读取和修改数据对象1。
  • 3、共享锁和排他锁的使用
      Mysql InnoDB引擎默认的修改数据语句:update、delete、insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型
      如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。
      所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。
      InnoDB行锁是通过给索引上的索引项加锁来实现的
      InnoDB这种行锁实现特点意味着:InnoDB只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
      1、在不通过索引条件查询的时候,innodb使用的是表锁而不是行锁。
      比如创建一张表:
	create table tab_no_index(id int,name varchar(10)) engine=innodb;
	insert into tab_no_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');

  session1可以通过select * from tab_no_index where id = 1 for update命令,只给一行加了排他锁,但是session2在请求其他行的排他锁的时候,会出现锁等待。原因是在没有索引的情况下,innodb只能使用表锁。
  2、创建带索引的表进行条件查询,innodb使用的是行锁。
  比如创建一张表:

	create table tab_with_index(id int,name varchar(10)) engine=innodb;
	alter table tab_with_index add index id(id);
	insert into tab_with_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');

  此时当一个session对一个行加锁时,不影响另一个session对别的行的操作。

2.3.2 意向共享锁和意向排它锁

  为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意
向锁(Intention Locks),这两种意向锁都是表锁。
  意向共享锁(IS):事务想要在获得表中某些记录的共享锁,需要在表上先加意向共享锁。
  意向互斥锁(IX):事务想要在获得表中某些记录的互斥锁,需要在表上先加意向互斥锁。
  意向共享锁和意向排它锁总称为意向锁。意向锁的出现是为了支持Innodb支持多粒度锁。
  意向锁是表级别锁。

当需要给一个加表锁的时候,需要根据意向锁去判断表中有没有数据行被锁定,以确定是否能加成功。如果意向锁是行锁,那么我们就得遍历表中所有数据行来判断。如果意向锁是表锁,则直接判断一次就知道表中是否有数据行被锁定了。所以说将意向锁设置成表级别的锁的性能比行锁高的多。

  有了意向锁之后,前面例子中的事务A在申请行锁(写锁)之前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的写锁时就会失败,因为表上有意向排他锁之后事务B申请表的写锁时会被阻塞。
  意向锁的作用就是:当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。如果自己需要一个共享锁定,就申请一个意向共享锁。如果需要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。

2.3.3 InnoDB四种锁的兼容情况

  InnoDB2种行锁和2种表锁的兼容情况:
Mysql(四)存储引擎、锁_第4张图片
  如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
  意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

	//共享锁(S)
	SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
	//排他锁(X)
	SELECT * FROM table_name WHERE ... FOR UPDATE

  用SELECT ... IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE方式获得排他锁。

  • InnoDB行锁实现方式
      InnoDB行锁是通过给索引上的索引项加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
      由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
      当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
      即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。

2.3.4 间隙锁(Next-Key 锁)

  当用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key 锁)。
  举例来说,假如emp表中只有101条记录,其empid的值分别是1,2,…,100,101,下面的SQL:

	Select * from emp where empid > 100 for update;

  是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。
  InnoDB 使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求。另外一方面,是为了满足其恢复和复制的需要。
  在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
  特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁。

2.3.5 恢复和复制的需要,对InnoDB锁机制的影响

  MySQL通过BINLOG记录执行成功的INSERT、UPDATE、DELETE等更新数据的SQL语句,并由此实现MySQL数据库的恢复和主从复制。
  MySQL的恢复机制有两个特点:

  • 1、MySQL的恢复是SQL语句级的,也就是重新执行BINLOG中的SQL语句。
  • 2、MySQL的Binlog是按照事务提交的先后顺序记录的,恢复也是按这个顺序进行的。

2.3.6 什么时候使用表锁

  对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个别特殊事务中,也可以考虑使用表级锁。

  • 1、事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
  • 2、事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。

  当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表了。
在InnoDB下,使用表锁要注意以下两点:

  • 1、使用LOCK TABLES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层──MySQL Server负责的,仅当autocommit=0、
    innodb_table_locks=1(默认设置)时,InnoDB层才能知道 MySQL 加的表锁,MySQL Server也才能感知InnoDB加的行锁,这种情况下,InnoDB 才能自动识别涉及表级锁的死锁;否则,InnoDB 将无法自动检测并处理这种死锁。
  • 2 、在用LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。正确的方式示例:
	#如果需要写表 t1 并从表 t 读,可以按如下做:
	SET AUTOCOMMIT=0;
	LOCK TABLES t1 WRITE, t2 READ, ...;
	[do something with tables t1 and t2 here];
	COMMIT;
	UNLOCK TABLES;

2.3.7 死锁

  在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这就决定了在InnoDB中发生死锁是可能的。
  比如,两个事务都需要获得对方持有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。
  发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁等待超时参数innodb_lock_wait_timeout来解决。需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。
  通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。
  以下是几种避免死锁的常用方法。

  • 1、在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。
  • 2、在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。
  • 3、在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。
  • 4、在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT...FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题。
  • 5、当隔离级别为READ COMMITTED时,如果两个线程都先执行SELECT...FOR UPDATE,判断是否存在符合条件的记录,如果没有,就插入记录。此时,只有一个线程能插入成功,另一个线程会出现锁等待,当第 1 个线程提交后,第 2 个线程会因主键重出错,但虽然这个线程出错了,却会获得一个排他锁!这时如果有第 3 个线程又来申请排他锁,也会出现死锁。对于这种情况,可以直接做插入操作,然后再捕获主键重异常,或者在遇到主键重错误时,总是执行ROLLBACK 释放获得的排他锁。

  如果出现死锁,可以用SHOW INNODB STATUS命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的SQL语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。
  处理死锁的两种方式:

  1. 设置超时时间,超时后自动释放。
  2. 发起死锁检测,主动回滚其中一条事务,让其他事务继续执行。

2.4 乐观锁和悲观锁

  • 1、乐观锁
      乐观锁实现方式:一般会使用版本号机制或CAS算法实现
      乐观锁不是数据库自带的,需要我们自己去实现,示例:
  1. SELECT data AS old_data, version AS old_version FROM …;
  2. 根据获取的数据进行业务操作,得到new_data和new_version
  3. UPDATE SET data = new_data, version = new_version WHERE version = old_version
    if (updated row > 0) {
     // 乐观锁获取成功,操作完成
    } else {
     // 乐观锁获取失败,回滚并重试
    }

  乐观锁的优点:乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。
  乐观锁的缺点:乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。
  总结:读用乐观锁,写用悲观锁

  • 2、悲观锁
      悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制(select ... for update)
      当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用
      MySQL中,select for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在MySQL中用悲观锁务必要确定走了索引,而不是全表扫描
      实现悲观锁时,必须先使用set autocommit=0;关闭Mysql的autoCommit属性,因为查询出数据之后就要将该数据锁定。
      悲观锁的使用示例:
  1. 开始事务
    begin; 或者 start transaction;
  2. 查询出商品信息,然后通过for update锁定数据防止其他事务修改
    select status from t_goods where id=1 for update;
  3. 根据商品信息生成订单
    insert into t_orders (id,goods_id) values (null,1);
  4. 修改商品status为2
    update t_goods set status=2;
  5. 提交事务
    commit; --执行完毕,提交事务

  在第2步我们将数据查询出来后直接加上排它锁(X)锁,防止别的事务来修改事务1,直到我们commit后,才释放了排它锁。
  悲观锁的优点:保证了数据处理时的安全性。
  悲观锁的缺点:加锁造成了开销增加,并且增加了死锁的机会。降低了并发性。

  • 3、乐观苏和悲观锁的适用场景
      乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。
      如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
      悲观锁和乐观锁是数据库用来保证数据并发安全防止更新丢失的两种方法,例子在select ... for update前加个事务就可以防止更新丢失。悲观锁和乐观锁大部分场景下差异不大,一些独特场景下有一些差别,一般可以从如下几个方面来判断。
  1. 响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。
  2. 冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率,如果冲突频率大,乐观锁会需要多次重试才能成功,代价比较大。
  3. 重试代价:如果重试代价大,建议采用悲观锁。

2.5 间隙锁、记录锁、临键锁

  间隙锁、记录锁、临键锁都是Innodb的行锁,前面我们说过行锁是基于索引实现的,一旦加锁操作没有操作在索引上,就会退化成表锁。
  间隙锁:作用于非唯一索引上,主要目的,就是为了防止其他事务在间隔中插入数据,以导致“不可重复读”。
  如果把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。
  记录锁:它封锁索引记录,作用于唯一索引上。
  临键锁:作用于非唯一索引上,是记录锁与间隙锁的组合。

2.6 死锁

  死锁是指两个或两个以上事务在执行过程中因争抢锁资源而造成的互相等待的现象。

  如何解决死锁?

  1. 等待事务超时,主动回滚。在InnoDB中,参数innodb_lock_wait_timeout是用来设置超时时间的。
  2. 进行死锁检查,主动回滚某条事务,让别的事务能继续走下去。
  3. 如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
  4. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率。
  5. 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;如果业务处理不好可以用分布式事务锁或者使用乐观锁。
  6. 在更新操作时,我们应该尽量使用主键来更新表字段,这样可以有效避免一些不必要的死锁发生。
  7. 在编程中尽量按照固定的顺序来处理数据库记录,假设有两个更新操作,分别更新两条相同的记录,但更新顺序不一样,有可能导致死锁。
  8. 在允许幻读和不可重复读的情况下,尽量使用RC事务隔离级别,可以避免gap lock(间隙锁) 导致的死锁问题。
  9. 避免长事务,尽量将长事务拆解,可以降低与其它事务发生冲突的概率。

2.7 Mysql中的锁总结

2.7.1 MyISAM锁

  1. 共享读锁之间是兼容的,但共享读锁与排他写锁之间,以及排他写锁之间是互斥的,也就是说读和写是串行的。
  2. 在一定条件下,MyISAM允许查询和插入并发执行,我们可以利用这一点来解决应用中对同一表查询和插入的锁争用问题。
  3. MyISAM默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置LOW_PRIORITY_UPDATES参数,或在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的争用。
  4. 由于表锁的锁定粒度大,读写之间又是串行的,因此,如果更新操作较多,MyISAM表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突。

2.7.2 InnoDB锁总结

   InnoDB是基于索引来完成行锁。示例 select * from tab_with_index where id = 1 for update;
  for update 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,如果 id 不是索引键那么InnoDB将完成表锁。

  在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。
  在了解InnoDB锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:

  1. 尽量使用较低的隔离级别; 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会;
  2. 选择合理的事务大小,小事务发生锁冲突的几率也更小;给记录集显式加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;
  3. 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;
  4. 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响; 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;
  5. 对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。

2.8 隔离级别与锁的关系

  在Read Uncommitted(读未提交)级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突。
  在Read Committed(读已提交)级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁
  在Repeatable Read(可重复读)级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁
  SERIALIZABL(串行化) 是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成

2.9 MVCC的实现原理

  MVCC( Multiversion concurrency control ) 就是同一份数据保留多版本的一种方式,进而实现并发控制。在查询的时候,通过read view和版本链找到对应版本的数据。
  MVCC作用:提升并发性能。对于高并发场景,MVCC比行级锁开销更小。

2.9.1 MVCC实现原理

  MVCC的实现依赖于版本链,版本链是通过表的三个隐藏字段实现。
  DB_TRX_ID:当前事务id,通过事务id的大小判断事务的时间顺序。
  DB_ROLL_PRT:回滚指针,指向当前行记录的上一个版本,通过这个指针将数据的多个版本连接在一起构成undo log版本链。
  DB_ROLL_ID:主键,如果数据表没有主键,InnoDB会自动生成主键。
  每条表记录大概是这样的:

  使用事务更新行记录的时候,就会生成版本链,执行过程如下:

  1. 用排他锁锁住该行;
  2. 将该行原本的值拷贝到 undo log ,作为旧版本用于回滚;
  3. 修改当前行的值,生成一个新版本,更新事务 id,使回滚指针指向旧版本的记录,这样就形成一条版本链。

  举个例子。

  • 1、初始数据如下,其中DB_ROW_ID和DB_ROLL_PTR为空。
  • 2、事务A对该行数据做了修改,将age修改为12。
    Mysql(四)存储引擎、锁_第5张图片
  • 3、之后事务B也对该行记录做了修改,将age修改为8。
  • 4、此时undo log有两行记录,并且通过回滚指针连在一起。

2.9.2 read view

  read view可以理解成将数据在每个时刻的状态拍成“照片”记录下来。在获取某时刻t的数据时,到t时间点拍的“照片”上取数据。
  在read view内部维护一个活跃事务链表,表示生成read view的时候还在活跃的事务。这个链表包含在创建read view之前还未提交的事务,不包含创建read view之后提交的事务。
  不同隔离级别创建read view的时机不同

  • read committed
      每次执行select都会创建新的read_view,保证能读取到其他事务已经提交的修改。
  • repeatable read
      在一个事务范围内,第一次select时更新这个read_view,以后不会再更新,后续所有的select都是复用之前的read_view。这样可以保证事务范围内每次读取的内容都一样,即可重复读。

2.9.3 read view

  DATA_TRX_ID表示每个数据行的最新的事务ID;up_limit_id表示当前快照中的最先开始的事务; low_limit_id表示当前快照中的最慢开始的事务,即最后一个事务。
Mysql(四)存储引擎、锁_第6张图片

  • DATA_TRX_ID   说明在创建 read view 时,修改该数据行的事务已提交,该版本的记录可被当前事务读取到。
  • DATA_TRX_ID>=low_limit_id
      说明当前版本的记录的事务是在创建read view之后生成的,该版本的数据行不可以被当前事务访问。此时需要通过版本链找到上一个版本,然后重新判断该版本的记录对当前事务的可见性。
  • up_limit_id<=DATA_TRX_ID   需要在活跃事务链表中查找是否存在ID为DATA_TRX_ID的值的事务。
      如果存在,因为在活跃事务链表中的事务是未提交的,所以该记录是不可见的。此时需要通过版本链找到上一个版本,然后重新判断该版本的可见性。
      如果不存在,说明事务trx_id已经提交了,这行记录是可见的。

  总结:InnoDB的MVCC是通过read view和版本链实现的,版本链保存有历史版本记录,通过read view判断当前版本的数据是否可见,如果不可见,再从版本链中找到上一个版本,继续进行判断,直到找到一个可见的版本。

2.10 快照读和当前读

  表记录有两种读取方式。
  快照读:读取的是快照版本。普通的SELECT就是快照读。通过mvcc来进行并发控制的,不用加锁。
  当前读:读取的是最新版本。 UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE是当前读。
  快照读情况下,InnoDB通过mvcc机制避免了幻读现象。而mvcc机制无法避免当前读情况下出现的幻读现象。因为当前读每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。

  • MySQL如何避免幻读?
      在快照读情况下,MySQL通过mvcc来避免幻读。
      在当前读情况下,MySQL通过next-key来避免幻读(加行锁和间隙锁来实现的)。
      next-key包括两部分:行锁和间隙锁。行锁是加在索引上的锁,间隙锁是加在索引之间的。
      Serializable隔离级别也可以避免幻读,会锁住整张表,并发性极低,一般不会使用。

2.11 共享锁和排他锁

  SELECT的读取锁定主要分为两种方式:共享锁和排他锁。示例:

	select * from table where id<6 lock in share mode;--共享锁
	select * from table where id<6 for update;--排他锁

  这两种方式主要的不同在于LOCK IN SHARE MODE多个事务同时更新同一个表单时很容易造成死锁。
  申请排他锁的前提是,没有线程对该结果集的任何行数据使用排它锁或者共享锁,否则申请会受到阻塞。在进行事务操作时,MySQL会对查询结果集的每行数据添加排它锁,其他线程对这些数据的更改或删除操作会被阻塞(只能读操作),直到该语句的事务被commit语句或rollback语句结束为止。
  SELECT… FOR UPDATE使用注意事项:

  1. for update仅适用于innodb,且必须在事务范围内才能生效。
  2. 根据主键进行查询,查询条件为like或者不等于,主键字段产生表锁。
  3. 根据非索引字段进行查询,会产生表锁。

2.12 Mysql锁的相关问题

2.12.1 MySQL中InnoDB引擎的行锁是通过加在什么上完成的

  InnoDB行锁是通过给索引上的索引项加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。

2.12.2 优化高并发事务

  • 1、结合业务场景,使用低级别事务隔离
      在高并发业务中,为了保证业务数据的一致性,操作数据库时往往会使用到不同级别的事务隔离。隔离级别越高,并发性能就越低。
  • 2、避免行锁升级表锁
      在InnoDB中,行锁是通过索引实现的,如果不通过索引条件检索数据,行锁将会升级到表锁,表锁是会严重影响到整张表的操作性能的。
  • 3、控制事务的大小,减少锁定的资源量和锁定时间长度

  其实MySQL的并发事务调优和Java的多线程编程调优非常类似,都是可以通过减小锁粒度和减少锁的持有时间进行调优。在MySQL的并发事务调优中,我们尽量在可以使用低事务隔离级别的业务场景中,避免使用高事务隔离级别。

你可能感兴趣的:(【Mysql】,mysql,数据库,b树)