MySQL 的ASID
MySQL的结构
MySQL数据引擎 InnoDB 和 MyISam 的区别
事务方面
InnoDB
支持事务,而 MyISam
不支持事务。这个是 mysql 将默认引擎改为 InnoDB
的主要原因。
外键方面
InnoDB
支持外键,而 MyISaM
不支持外键
索引方面
InnoDB
是聚簇索引,而 MyISam
是非聚簇索引
锁的粒度
InnoDB
是行锁, MyISam
是表锁,这也是把 myISAM 改成 InnoDB
的一个重要原因
保存的文件格式
MyISAM
是有三个文件
-
.frm
存储表的定义 -
.myd
存的是数据 -
.myi
存的是索引
InnoDB
有两个文件(没有专门保存数据的文件)
-
.frm
表的定义 -
.idb
数据和索引存储文件
非聚簇索引。索引和数据是分开存的
聚簇索引,索引和数据是一起存的
所以也就明白了为什么尽量使用覆盖索引?
正如下图,如果使用覆盖索引,直接就把数据拿出来了,而不需要再去主键索引里去查找了
ACID
-
原子性(atomicity)
-
一致性(consistency)
-
持久性(isolation)
-
隔离性(durability)
并发控制的时候,出现的三种问题
脏读
指当一个事务正在访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。依据脏数据所做的操作肯能是不正确的。
不可重复读
不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事物内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
幻读
幻象读:一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
其实仔细想想,就脏读和不可重复读两种情况,幻读只不过是读多行的不可重复读
在标准的 SQL 规范中,事务的隔离级别有四种
读未提交
事务 A
和事务 B
同时执行,事务 A
在整个执行过程中,把数据从 1 加到 10 ,而事务 B
可以看到事务 A
操作的中间值。所有他会脏读
读已提交
示例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…
注意:
也就说,事务对数据进行 读
操作时,读操作事务要等待这个 写
操作事务提交后才能读取数据
可重复读
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。
注意:
重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
串行化
是最严格的事务隔离级别,它要求所有事务被串行执行,即事务只能一个接一个的进行处理,不能并发执行。
总结一下这四种隔离级别
-
为什么会出现“脏读”?因为没有
select
操作没有规矩。 -
为什么会出现“不可重复读”?因为
update
操作没有规矩。 -
为什么会出现“幻读”?因为
insert
和delete
操作没有规矩。 -
“读未提(Read Uncommitted)”能预防啥?啥都预防不了。
-
“读提交(Read Committed)”能预防啥?使用“快照读(Snapshot Read)”,避免“脏读”,但是可能出现“不可重复读”和“幻读”。
-
“可重复读(Repeated Red)”能预防啥?使用“快照读(Snapshot Read)”,锁住被读取记录,避免出现“脏读”、“不可重复读”,但是可能出现“幻读”。
-
“串行化(Serializable)”能预防啥?排排坐,吃果果,有效避免“脏读”、“不可重复读”、“幻读”,不过效果谁用谁知道。
在mysql数据引擎中,使用 MVCC 实际上就可以解决了已提交和不可重复读的问题,这个和
readview
的生成时间有关。而且我们可以通过 innodb 引擎中,通过添加间隙锁,防止添加数据,从而解决幻读的问题。
mysql 的默认隔离级别
mysql的默认隔离界别是可重复读,但是一般项目我们会把隔离级别更改成 读已提交
。
mysql5.0
之前默认的隔离级别是读已提交
, 5.0 以后的版本改成了 可重复读
,原因是使用读已提交,在进行 主从复制
的时候,会存在大量的不一致的情况。
这个主要是和主从复制使用的 binglog
日志文件的格式有关
-
statement:记录的是修改SQL语句
-
row:记录的是每行实际数据的变更
-
mixed:statement和row模式的混合
为什么很多人会把读已提交,当作默认的隔离级别呢?
因为 可重复读
会让死锁的概率增大,在 可重复读
的条件下,条件未命中,将会引发表锁,而在 读已提交
的隔离级别下,只会锁行。
select * from test where id <3 for update;
-
在RR隔离级别下,存在间隙锁,可以锁住(2,5)这个间隙,防止其他事务插入数据!
-
而在RC隔离级别下,不存在间隙锁,其他事务是可以插入数据!
缘由一:在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多!
缘由二:在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
参考文献https://www.cnblogs.com/shoshana-kong/p/10516404.html
MySQL是如何保证 ACID的?
使用 Undolog 来实现原子性
原子的保证,其实就是依赖于 rollback
回滚机制,在sql语句执行的时候,就会记录 undo log
日志,当执行发生回滚的时候,mysql就会执行与 undo log
相反的操作,比如你之前执行了一个 create 操作,那么他就会执行一个 delete 操作,如果你执行了一个 updata 操作,那么mysql就执行一个相反的 updata 操作。
使用 redolog 来实现持久性
背景: 因为数据的写是写在磁盘中的,所以每次的 IO
操作都很费时间,所以 mysql 建立了一个缓存 buffer
,每次把要提交的数据写到缓存的 buffer
中,然后把buffer中的数据,一次性的写入到数据库中,问题就在于这个了。如果数据我写入到 buffer 中,但是 buffer 中的数据还没有写入磁片。此时数据库宕机了。
解决这个问题,就是在将数据写入 buffer
的时候,同时将操作写入 redo log
中,因为redog是使用文件的追加模式的,所以他比将buffer写入磁盘要快很多很多。
隔离性
写
-写
:操作,通过锁来实现的读
-写
:操作,通过MVCC
机制实现的
写写操作如何用锁实现
如果是 写-写
操作的话,可以使用锁
,来实现,锁呢分为表锁,行锁,还有间隙锁。其具体的区别在另一篇博客中可见https://www.cnblogs.com/rush-peng/p/14992892.html
一致性
其实一致性是最终要追求的目标,就是通过上面的三种性质,来保证最终的数据库的一致性,比如主键唯一,数据要合法,在合理的范围内,等等。
什么是 MVCC
mvcc
全称 Multi-Version Concurrency Control
,即多版本并发控制。
同一行的数据,在执行读写操作的时候,会上 阻塞锁
,但是 MVCC
在读写的时候,不用加锁,这里所说的读是 快照读
。而不是 当前读
。 当前读
是一种悲观锁。
什么当前读和快照读
当前读
是读到的数据的 最新版本
,会对当前读到的数据进行加锁,防止其他事务修改数据,是悲观锁
的一种操作。
如何使用mysql
执行快照读。
SELECT * FROM `student` LOCK in share mode;
快照读
是基于多版本并发控制,读到的不一定是当前最新的数据,有可能是 历史记录数据
。
简单点来理解:
当前读读的是数据的最新版本,读到的总是最新的数据。快照读读取的是历史版本的记录。
MVCC 最根本的操作,就是控制着操作的可见性。
数据库并发场景
-
读-读
: 不存在任何问题,也不需要并发控制 -
读-写
: 存在线程安全问题 -
写-写
:存在线程安全问题
MVCC 解决哪些并发问题
读操作,只读事务开始前的 数据库快照
.
解决的问题如下:
-
读写
并发的时候:读操作不阻塞写操作,写操作不阻塞读操作。 -
解决了
脏读
,幻读
,不可重复读
的问题。但是没有解决写-写
更新丢失的问题。
因此有了提高并发性能的组合拳:
-
mvcc
+悲观锁
: MVCC解决读写冲突,悲观锁解决写写冲突
-
mvcc
+乐观锁
: MVCC解决读写冲突,乐观锁解决写写冲突
什么是版本链,什么是事务ID。
版本链如下图,就是可以根据回滚指针,回滚到他的上一个版本,同时事务 ID
是自增的。那么他之前的版本信息呢,就存在 undolog
里面。
注意: 事务ID都是可自增的。
rediview保存的信息
trx
是事务的简写。
m_ids | 生成这个 rediview时候,处于活跃状态的id列表 |
---|---|
min_trx_id | 活跃状态中,最小的 id |
max_tex_id | 生成事务的时候,应该分配给下一个事务的id |
create_trx_id | 表示生成当前的 readview 的事务ID |
活跃状态,即未提交的 ID 列表
如何通过readview 判断哪个版本是可用的?
一定要注意:这些 readview 都是用来控制
select
读的
事务的ID关系 | 是否可访问 |
---|---|
事务ID = create_trx_id | 他读他自己事务的id,当然可以访问 这个版本 |
事务ID < min_trx_id | 可以访问 |
事务ID > max_tex_id | 不可访问 |
min_trx_id=< 事务ID =<max_tex_id | 如果事务ID在 m_ids 里面,就可以访问,反之则不行 |
如何通过 readview 做到 读已提交
和 可重复性读
其实两者的区别就在于 readview
的生成的时间
-
如果同一事物中,每次 select 都生成一个 readview,就实现了
读已提交
-
如果在同一事务中,只生成一个
readview
,那么就是可重复读
-
如何防止幻读呢?其实也可以,就是加上间隙锁就可以了
参考文献
https://www.cnblogs.com/myseries/p/10748912.html
https://www.jianshu.com/p/fc8a654f2205
乐观所和悲观锁