MySql 数据存储引擎和事务处理

  • 存储引擎(Storage Engine)         

MySQL有一个重要特征,被称为“Pluggable Storage Engine Architecture”(可替换存储引擎架构),也就意味着MySQL数据库提供了多种存储引擎。用户可以根据不同的需求为数据表选择不同的存储引擎,用户也可以根据自己的需要编写自己的存储引擎。MySQL数据库在实际的工作中其实分为了语句分析层存储引擎层,其中语句分析层就主要负责与客户端完成连接并且事先分析出SQL语句的内容和功能,而存储引擎层则主要负责接收来自语句分析层的分析结果,完成相应的数据输入输出和文件操作。简而言之,就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法,其工作模式如下图所示:
 

因为在关系数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称为表类型(即存储和操作此表的类型),也就是说MySQL支持多个存储引擎作为对不同表的类型的处理器。MySQL存储引擎包括处理事务安全表的引擎和处理非事务安全表的引擎。

MySQL提供的存储引擎的种类

存储引擎主要特征
MyISAM高速引擎,拥有较高的插入,查询速度,但不支持事务
InnoDB5.5版本后MySQL的默认数据库,支持事务和行级锁定,比MyISAM处理速度稍慢
ISAMMyISAM的前身,MySQL5.0以后不再默认安装
MRG_MyISAM(MERGE)将多个表联合成一个表使用,在超大规模数据存储时很有用
Memory内存存储引擎,拥有极高的插入,更新和查询效率。但是会占用和数据量成正比的内存空间。只在内存上保存数据,意味着数据可能会丢失
Falcon一种新的存储引擎,支持事物处理,传言可能是InnoDB的替代者
Archive将数据压缩后进行存储,非常适合存储大量的独立的,作为历史记录的数据,但是只能进行插入和查询操作
CSVCSV 存储引擎是基于 CSV 格式文件存储数据(应用于跨平台的数据交换)

实际中使用最多的两种存储引擎是MyISAM和InnoDB

  • MyISAM 引擎

这种引擎是MySQL最早提供的。这种引擎又可以分为静态MyISAM、动态MyISAM 和压缩MyISAM三种:

  1. 静态MyISAM:如果数据表中的各数据列的长度都是预先固定好的,服务器将自动选择这种表类型。因为数据表中每一条记录所占用的空间都是一样的,所以这种表存取和更新的效率非常高。当数据受损时,恢复工作也比较容易做。
  2. 动态MyISAM:如果数据表中出现varchar、xxxtext或xxxBLOB字段时,服务器将自动选择这种表类型。相对于静态MyISAM,这种表存储空间比较小,但由于每条记录的长度不一,所以多次修改数据后,数据表中的数据就可能离散的存储在内存中,进而导致执行效率下降。同时,内存中也可能会出现很多碎片。因此,这种类型的表要经常用optimize table 命令或优化工具来进行碎片整理。
  3. 压缩MyISAM:以上说到的两种类型的表都可以用myisamchk工具压缩。这种类型的表进一步减小了占用的存储,但是这种表压缩之后不能再被修改。另外,因为是压缩数据,所以这种表在读取的时候要先时行解压缩。

当然不管是何种MyISAM表,目前它都不支持事务,行级锁和外键约束的功能,这就意味着有事务处理需求的表,不能使用MyISAM存储引擎。MyISAM存储引擎特别适合在以下几种情况下使用:

  • 选择密集型的表。MyISAM存储引擎在筛选大量数据时非常迅速,这是它最突出的优点。
  • 插入密集型的表。MyISAM的并发插入特性允许同时选择和插入数据。

MyISAM表是独立于操作系统的,这说明可以轻松地将其从Windows服务器移植到Linux服务器;每当我们建立一个MyISAM引擎的表时,就会在本地磁盘上建立三个文件,文件名就是表名。 例如我创建了一个【test】表,那么就会生成以下三个文件:

文件名说明
test.frm存储表定义
test.MYD存储数据
test.MYI存储索引
  • InnoDB引擎

InnoDB表类型可以看作是对MyISAM的进一步更新产品,它提供了事务、行级锁机制和外键约束的功能。InnoDB的表需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。 
使用InnoDB是最理想的选择:

  • 更新密集的表:InnoDB存储引擎特别适合处理多重并发的更新请求。
  • 事务:InnoDB存储引擎是支持事务的标准MySQL存储引擎。
  • 自动灾难恢复:与其它存储引擎不同,InnoDB表能够自动从灾难中恢复。
  • 外键约束:MySQL支持外键的存储引擎只有InnoDB。
  • 支持自动增加列AUTO_INCREMENT属性。

存储引擎相关操作

  1. 确认表中使用的引擎:SHOW CREATE TABLE 表名;[ENGINE=] 后显示的部分就是正在使用的引擎。
  2. 修改表使用的引擎:ALTER TABLE 表名 ENGINE= 新引擎
  3. 修改默认引擎:MySql安装目录找到my.ini文件,找到【default-storage-engine】修改即可。
  • 事务处理(Transaction)

        事务是并发控制的基本单位,事务处理就是将多个更新命令作为一个整体来执行,从而保证数据整合性的机制。其实就是按照给定的事务规则来执行提交或者回滚操作用来确保数据的完整性和一致性。在MySQL中执行命令时,事务通常是被自动提交(COMMIT)的,特别是在存储引擎为MyISAM的情况下,本身它是不支持事务处理的,只要执行了命令,就会被提交。因此要显式地开启一个事务务须使用命令 【BEGIN】 或 【START TRANSACTION】后,才不会自动提交,只有明确执行了【COMMIT】命令后才会被提交,再次之前也就可以执行【ROLLBACK】这样的命令了。

      可以使用[SELECT@@AUTOCOMMIT] 语句确认当前使用的自动提交模式(0:未开启;1:开启).

      修改自动提交模式使用的是【SET】语句:

  • 将自动提交模式设置为OFF的语法是:SET AUTOCOMMIT =0;
  • 将自动提交模式设置为ON的语法是:SET AUTOCOMMIT =1;

简单事务回滚

使用事务回滚主要是使用三个命令,他们的语法如下:

命令解释
BEGIN(或:START TRANSACTION);开启事务
COMMIT;提交整个事务
ROLLBACK;回滚到事务开始的状态

部分回滚-只提交针对数据库的部分操作

简单事务回滚是回滚到事务最开始的状态,其实在事务的处理过程中,我们可以在事务处理过程中定义保存节点(SAVEPOINT),然后回滚到指定的保存点之前的状态,来进行部分事务回滚,工作原理如下图:

 定义保存点,以及回滚到指定保存点前状态的语法如下:

  1. 定义保存点:SAVEPOINT 保存点名; 
  2. 回滚到指定的保存点:ROLLBACK SAVEPOINT 保存点名; 

事务处理的利用范围

下面的四条语句在事务处理中是不可进行事务回滚的,也就是这四条语句一旦执行,就立刻被提交

  • DROP DATABASE;
  • DROP TABLE;
  • DROP;
  • ALTER TABLE;

事务处理的内部动作

事务处理的机制简单说就是留下更新日志,数据库会根据这些日志信息,在必要时将旧的数据取回,或者在发生错误时将数据恢复到原先的状态。与事务处理相关的日志可以分为UNDO日志和REDO日志。

UNDO日志又被称为回滚段(Rollback Segment),在进行数据的插入,更新和删除的场合,保存变更前的数据。

REDO日志根据数据库的不同有时被称为事务处理日志或者日志。事务确定后,由于错误等原因使数据的更新没有正确的反应到数据库中的时候,REDO日志提供了数据恢复用的手段。数据库在执行更新或者变更时,并非立即更新数据文件,而是首先在被称为缓存(Buffer Pool)的内存空间中进行数据的更新,并同时将这些数据保存到UNDO日志文件中,数据库向数据文件写入数据动作的执行是发生在检查点(Checkpoint)时刻非同步进行的,而检查点是按照一定的时间周期触发的,也就是说写入数据文件并非实时的,而是有一定的时间延迟,这就可能会导致内存中数据与磁盘中的数据产生不一致的情况,这样做好处在于当遇到大批量数据写入数据库时,可以显著的减少数据库访问磁盘的次数,从而提高事务的操作效率和性能。

事务要求之一是持久性(Durability),Buffer Pool与磁盘数据的不一致性的情况下发生故障,可能会导致数据无法持久化。为了防止在内存中修改但尚未写入到磁盘的数据,在发生故障重启数据之后产生事务未持久化到磁盘的情况,是通过REDO 日志(write log ahead)先行的方式来保证的。REDO 日志可以在故障重启之后实现“重做”,保证了事物的持久化的特性,但是REDO 日志空间不可能无限制扩大,对于内存中已修改但尚未提交到磁盘的数据,也即脏页,也需要写入磁盘。对于内存中的脏页,什么时候,什么情况下,将多少脏页写入磁盘,是由多方面因素决定的。Checkpoint的工作之一,就是对于内存中的脏页,在一定条件下将脏页刷新到磁盘。

Checkpoint的分类
按照Checkpoint刷新的方式,MySQL中的Checkpoint分为两种,也即Sharp Checkpoint和Fuzzy Checkpoint。

  1. Sharp Checkpoint:在关闭数据库的时候,将Buffer Pool中的脏页全部刷新到磁盘中。
  2. Fuzzy Checkpoint:数据库正常运行时,在不同的时机,将部分脏页写入磁盘,进刷新部分脏页到磁盘,也是为了避免一次刷新全部的脏页造成的性能问题。

事务并发处理

事务是一个不可分割操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务具有四个重要特征,即原子性(Atomicity)、一致性(Consistency)、隔离性 (Isolation)和持久性 (Durability)。其中隔离性与事务并发直接相关,主要用于解决事务的并发安全问题,安全问题主要包括以下几种:

  • 脏读(Dirty Read):一个事务处理过程中读取了另一个事务未提交的数据。
  • 不可重复读取(Non-Repeatable Read):一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔该数据被另一个事务修改并提交了。
  • 幻读(Phantom Read):幻读是事务非独立执行时发生的一种现象,即在一个事务读的过程中,另外一个事务可能插入了新数据记录,影响了该事务读的结果。

MySQL数据库为我们提供了四种隔离级别(TRANSACTION ISOLATION LEVEL),默认的隔离级别为:Repeatable Read。可反复读级别,四种分别为:

  • Serializable (串行化):最高级别,可避免脏读、不可重复读、幻读的发生;
  • Repeatable read (可重复读):可避免脏读、不可重复读的发生;
  • Read committed (读已提交):可避免脏读的发生;
  • Read uncommitted (读未提交):最低级别,任何情况都无法保证。

      √: 可能出现    ×: 不会出现

 脏读不可反复读幻读
Read uncommitted
Read committed×
Repeatable read××
Serializable×××

 

Read uncommitted 读未提交

是限制性最弱的隔离级别。由于该级别忽略其它事务放置的锁。使用READ UNCOMMITTED级别运行的事务,能够读取尚未由其它事务提交的改动后的数据值,即我们所说的脏读。

Read committed 读提交

该级别通过指定语句不能读取其它事务已改动可是尚未提交的数据值。禁止运行脏读。在当前事务中的各个语句运行之间,其它事务仍能够改动、插入或删除数据。从而产生无法反复的读操作。

Repeatable read 可重复读

该级别包含READ COMMITTED,而且另外指定了在当前事务提交之前。其它不论什么事务均不能够改动或删除当前事务已读取的数据。并发性低于 READ COMMITTED。由于已读数据的共享锁在整个事务期间持有,而不是在每一个语句结束时释放。

Serializable 序列化

SERIALIZABLE 是限制性最强的隔离级别,由于该级别锁定整个范围的键。并一直持有锁,直到事务完毕。该级别包含REPEATABLE READ。并添加了在事务完毕之前,其它事务不能向事务已读取的范围插入新行的限制。

隔离级别和性能的关系:

                           è¿éåå¾çæè¿°

           

  从上图中可以看出,以上四种隔离级别中最高的是 Serializable级别,最低的是 Read uncommitted级别。当然,隔离级别越高,事务并发就越安全,但执行效率也就越低。比如,Serializable 这样的级别就是以锁表的方式(类似于Java多线程中的锁)保证并发事务的串行执行,但这时执行效率也降到了最低。

      在解决数据库的事务并发访问问题时,虽然将事务串形化可以保证数据在多事务并发处理下不存在数据不一致的问题,但串行执行使得数据库的处理性能大幅度地下降,常常是我们接受不了的。所以,一般来说,我们常常结合事务隔离级别和其它并发机制来保证事务的并发,以此来兼顾事务并发的效率与安全性。选用何种隔离级别实质上是一种并发安全与并发效率的平衡,应该根据实际情况而定。

MySQL默认事务隔离级别查看:select @@tx_isolation(会话级别);SELECT @@global.tx_isolation;(全局级别)

事务隔离级别修改(单个会话或者所有新进连接的):set [glogal | session] transaction isolation level 隔离级别名称;或 set tx_isolation='隔离级别名称';

命令行用--transaction-isolation选项,或在选项文件里,为所有连接设置默认隔离级别。例如,你可以在my.inf文件的[mysqld]节里类似如下设置该选项:globally 

[mysqld]
transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED
                         | REPEATABLE-READ | SERIALIZABLE}

四种锁

从数据库系统的角度来看,锁分为共享锁和排他锁,从程序员的角度看,锁分为悲观锁和乐观锁。

  • 共享锁-Shared Lock(S锁,  读锁)

共享锁锁定的资源可以被其它用户同一时刻并发的读取同一资源,互不干扰,但其它用户不能修改它。在SELECT 命令执行时,数据库通常会对对象进行共享锁锁定。通常加共享锁的数据页被读取完毕后,共享锁就会立即被释放。

  • 排他/独占锁-Exclusive Lock(X锁,写锁)

独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对它的操作均不会被接受。执行数据更新命令,即INSERT、UPDATE 或DELETE 命令时,数据库会自动使用独占锁。但当对象上有其它锁存在时,无法对其加独占锁。独占锁一直到事务结束才能被释放。

共享锁可以一层套一层得上锁,但排他锁只能加一个。注意:所谓共享锁、排他锁其实均是锁机制本身的策略,通过这两种策略对锁做了区分。

  • 乐观锁

总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号(Version)机制或CAS(Compare and Swap)操作实现,何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。每次不加锁去执行某项操作,如果发生冲突则失败并重试,直到成功为止,其实本质上不算锁,所以很多地方也称之为自旋乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于等于数据库表当前版本号,则予以更新,否则认为是过期数据。
比如Hibernate为乐观锁提供了3种实现:

  1. 基于version
  2. 基于timestamp
  3. 为遗留项目添加添加乐观锁

CAS便是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
  CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 CAS是基于CPU 的 指令来实现的。所以CAS 的操作可认为是无阻塞的,一个线程的失败或挂起不会引起其它线程也失败或挂起。并且由于 CAS 操作是 CPU 原语,所以性能比较好。

  • 悲观锁

总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java线程并发处理中,Synchronized内置锁 就是悲观锁的一种,也称之为 独占锁,加了synchronized关键字的代码基本上就只能以单线程的形式去执行了,它会导致其他需要该资源的线程挂起,直到前面的线程执行完毕释放所资源。

一个典型的依赖数据库的悲观锁调用:select * from test where name=”Gray.z” for update这条sql语句锁定了test 表中所有符合检索条件(name=”Gray.z”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。悲观锁,也是基于数据库的锁机制实现。,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据).

 悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

特别地,乐观锁的理念是:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性;而悲观锁的理念是假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作,其实乐观锁是解决高并发性能的办法。悲观锁更安全,但是并发性能太差,特别是有长事务的时候,并发事务会排很长的队。

根据范围,锁还可以划分成行锁和表锁。

锁策略

锁的开销是较为昂贵的,锁策略其实就是保证了线程安全的同时获取最大的性能之间的平衡策略。

  • Talbe Lock(表锁)

表锁是Mysql最基本的锁策略,也是开销最小的策略,它会锁定整个表;具体情况是:若一个用户正在执行写操作,会获取排他的“写锁”,这是会锁定整个表,阻塞其他用户的读、写操作;

若一个用户正在执行读操作,会先获取共享锁“读锁”,这个锁运行其他读锁并发的对这个表进行读取,互不干扰。只要没有写锁的进入,读锁可以是并发读取统一资源的。

  • Row Lock(行锁)

行锁可以最大限度的支持并发处理,当然也带来了最大开销,顾名思义,行锁的粒度实在每一条行数据。

Intention Locks(意向锁)

InnoDB支持多粒度锁(锁粒度可分为行锁和表锁),允许行锁和表锁共存。例如,一个语句,例如LOCK TABLES…WRITE接受指定表上的独占锁。为了实现多粒度级别的锁定,InnoDB使用了意图锁。

意向锁:表级别的锁。当前先提前声明一个意向,并获取表级别的意向锁(IS或IX),如果获取成功则后续某个时刻(才被允许),该事务可能要对该表的某些行加锁(S或X)了。

对于一个事务,意图锁定之后理想的是指明在该表中对一个行随后需要哪一类型的锁定(共享还是独占)。有两种意图锁被用在InnoDB中(假设事务T 在表R中要求一个已指出的类型的锁):

  1. 意图共享(IS):事务T 意图给表T上单独的tuple设置S 锁定。
  2. 意图独占(IX):事务T 意图给这些tuple设置X 锁定。

举例来说:

SELECT … LOCK IN SHARE MODE,要获取IS锁;An intention shared lock (IS)

SELECT … FOR UPDATE ,要获取IX锁;An intention exclusive lock (IX) i

意向锁协议:

  1. 在假设的事务能够获取表中的行上的共享锁之前,它必须首先获取表上的IS锁或更强的锁。
  2. 在假设的事务能够获取表中的行上的独占锁之前,它必须首先获取表上的IX锁。

这些结果可以方便地用一个锁类型兼容矩阵来总结:

 

X

IX

S

IS

X

冲突

冲突

冲突

冲突

IX

冲突

兼容

冲突

兼容

S

冲突

冲突

兼容

兼容

IS

冲突

兼容

兼容

兼容

意向锁仅表意向,是一种较弱的锁,意向锁之间兼容并行(IS、IX 之间关系兼容并行)。X与IS\IX互斥;S与IX互斥。可以体会到,意向锁是比X\S更弱的锁,存在一种预判的意义!先获取更弱的IX\IS锁,如果获取失败就不必要再花费跟大开销获取更强的X\S锁。

如果一个锁定与现在锁定兼容的话,它被授给一个委托事务。如果一个锁定与现存锁定冲突,它就不被授予一个委托事务。事务等待着直到冲突的现存锁定被释放掉。如果一个锁定请求与现存锁定相冲突,且不能被授予,因为它可能会导致死锁,一个错误产生。

因此,意图锁定不阻碍任何东西,除了完全表请求(比如LOCK TABLES ... WRITE)。IXIS锁定的主要目的是显示某人正锁定一行,或将要在表中锁定一行。

Record Locks

record lock是一个在索引行记录的锁。

比如,SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE,如果c1 上的索引被使用到。防止任何其他事务插入、更新或删除值为t的行。c1是10。

 Gap locks(间隙锁)

间隙锁定是对索引记录之间的间隙进行锁定,或对第一个索引记录之前或最后一个索引记录之后的间隙进行锁定。
例如, SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE; 防止其他事务将值15插入到列t中。,因为该范围内所有现有值之间的间隙都是锁定的。
间隙可以跨越单个索引值、多个索引值,甚至是空的,间隙锁是性能和并发性之间权衡的一部分,用于某些事务隔离级别。

  • 一次封锁or两段锁?

由于有大量的并发訪问,为了预防死锁。一般应用中推荐使用一次封锁法。就是在方法的開始阶段。已经预先知道会用到哪些数据,然后所有锁住,在方法执行之后,再所有解锁。

这样的方式能够有效的避免循环死锁,但在数据库中却不适用,由于在事务開始阶段,数据库并不知道会用到哪些数据。
数据库遵循的是两段锁协议,将事务分成两个阶段,加锁阶段和解锁阶段(所以叫两段锁)

  1. 加锁阶段:在该阶段能够进行加锁操作。在对不论什么数据进行读操作之前要申请并获得S锁(共享锁,其他事务能够继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其他事务不能再获得不论什么锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续运行。
  2. 解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段。在该阶段仅仅能进行解锁操作不能再进行加锁操作。

事务                       加锁/解锁处理
begin。 
insert into test ..... 加insert相应的锁
update test set... 加update相应的锁
delete from test .... 加delete相应的锁
commit; 事务提交时,同一时候释放insert、update、delete相应的锁
这样的方式尽管无法避免死锁。可是两段锁协议能够保证事务的并发调度是串行化(串行化非常重要,尤其是在数据恢复和备份的时候)的。

不可反复读和幻读的差别
不可反复读和幻读这两者有些相似。但不可重复读重点在于update和delete。而幻读的重点在于insert。

假设使用锁机制来实现这两种隔离级别。在可反复读中,该sql第一次读取到数据后。就将这些数据加锁,其他事务无法改动这些数据。就能够实现可反复读了。但这样的方法却无法锁住insert的数据。所以当事务A先前读取了数据,或者改动了所有数据,事务B还是能够insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据。这就是幻读。不能通过行锁来避免。

须要Serializable隔离级别 。读用读锁,写用写锁,读锁和写锁相互排斥,这么做能够有效的避免幻读、不可反复读、脏读等问题,但会极大的减少数据库的并发能力。

所以说不可重复读和幻读最大的差别,就在于怎样通过锁机制来解决他们产生的问题。。解决不可重复读的问题只需锁住满足条件的行(行级锁),解决幻读需要锁表(表级锁,最高隔离级别Serializable),使用select …where语句中加入 for update(排他锁) 或者 lock in share mode(共享锁)语句来实现。其实就是锁住了可能造成幻读的数据,阻止数据的写入操作.

上文说的,是使用悲观锁机制来处理这两种问题,MySQL出于性能考虑,使用了以乐观锁为理论基础的MVCC(多版本号并发控制)来避免这两种问题。

并发控制 与 MVCC

MVCC(multiple-version-concurrency-control)是个行级锁的变种,它在普通读情况下避免了加锁操作,因此开销更低。虽然实现不同,但通常都是实现非阻塞读,对于写操作只锁定必要的行。

一致性读 (就是读取快照,所谓读快照就是读取当前事务之前的数据
select * from table …;

当前读(就是读取实际的持久化的数据)
特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update ;
delete;

注意:select … from where… (没有额外加锁后缀)使用MVCC,保证了读快照(mysql称为consistent read),

所谓一致性读或者读快照就是读取当前事务开始之前的数据快照,在这个事务开始之后的更新不会被读到。

对于加锁读SELECT with FOR UPDATE(排他锁) or LOCK IN SHARE MODE(共享锁)、
update、delete语句,要考虑是否是唯一索引的等值查询。

INNODB的MVCC通常是通过在每行数据后边保存两个隐藏的列来实现(其实是三列,第三列是用于事务回滚,此处略去),

一个保存了行的创建版本号,另一个保存了行的更新版本号(上一次被更新数据的版本号)
这个版本号是每个事务的版本号,递增的。

这样保证了innodb对读操作不需要加锁也能保证正确读取数据。
 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值