共计 1941 个字符,预计需要花费 5 分钟才能阅读完成。
引言
MyISAM
不支持事务。
MyISAM 与 InnoDB 关于锁方面的区别是什么
MyISAM
默认使用的是表级锁,不支持行级锁。
InnoDB
默认使用的是行级锁,也支持表级锁。
读锁 / 共享锁:其他 session
可以读,但不能写。
写锁 / 排他锁:其他 session
既不能读,也不能写。
MyISAM
,对数据进行查询时,自动为该表加上一个表级的“读锁”;对数据进行增删改时,会为该表加上一个表级的“写锁”。当“读锁”未被释放的时候,另一个 session
对这张表进行写操作会被阻塞。
InnoDB
的行锁也类似,对一行进行加锁,如果是读锁,其他事务可以读,但是不可以写;如果是写锁,其他事务既不能读,也不能写。
InnoDB
会自动为增删改查语句开启事务,并自动提交或回滚,在事务提交或回滚后会自动同时释放锁。
如果没有走事务,会进行全表遍历,提升为表锁。
MyISAM
适合的场景
- 频繁执行全表
count
语句。MyISAM
会保存整个表的行数。 - 对数据进行增删改的频率不高,查询非常频繁。
- 适合没有事务的场景。
InnoDB
适合的场景
- 数据增删改查都相当频繁。
- 可靠性要求比较高,要求支持事务。
锁的分类
按锁粒度划分
表级锁、行级锁、页级锁。
表级锁速度快,但冲突多,行级冲突少,但速度慢。页级锁介于两者之间,锁定相邻的一组记录。
按锁级别划分
共享锁、排他锁。
按加锁方式划分
自动锁、显式锁。
按操作划分
DML
锁、DDL
锁。
按使用方式划分
乐观锁、悲观锁。
乐观锁:在操作时很乐观,认为操作不会产生并发问题(不会有其他线程对数据进行修改),因此不会上锁。但是在更新时会判断其他线程在这之前有没有对数据进行修改。
悲观锁:总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加(悲观)锁。一旦加锁,不同线程同时执行时, 只能有一个线程执行,其他的线程在入口处等待,直到锁被释放。
数据库事务的四大特性
ACID
A
:原子性,事务包含的所有操作,要么全部成功,要么全部失败。C
:一致性,事务确保数据只有操作前状态和操作后状态,访问时绝不会出现中间态。I
:隔离性,多个事务并发执行时,一个事务的执行不应该影响其他事务。D
:持久性,一个事务一旦提交,对数据的修改永久保存。
事务隔离级别以及各级别下的并发访问问题
MySQL 事务的隔离级别
READ-UNCOMMITTED
:在一个事务中,可以读取到其他事务未提交的数据变化。在生产环境中不建议使用。READ-COMMITTED
:在一个事务中,可以读取到其他事务已经提交的数据变化。是Oracle
默认的事务隔离级别。REPEATABLE-READ
:在其中一个事务中,直到事务结束前,都可以反复读取到事务刚开始时看到的数据。是MySQL
默认的事务隔离级别。SERIALIZABLE
:在每个读的数据行上都加表级共享锁,在每次写数据时都加表级排他锁。这样会造成InnoDB
的并发能力下降,大量的超时和锁竞争就会发生。在生产环境中不建议使用。
事务并发访问引起的问题以及如何避免
- 更新丢失:
MySQL
所有事务隔离级别在数据库层面上均可避免。 - 脏读:一个事务读到另一个事务没有提交的数据,
READ-COMMITTED
事务隔离级别以上可避免。 - 不可重复读:其他事务有提交,导致多次读取同一数据时结果不一致,
REPEATABLE-READ
事务隔离级别以上可避免。 - 幻读:更新
M
条数据,其他事务对数据进行了增删,却成功了M+N/M-N
条,产生了幻觉,SERIALIZABLE
事务隔离级别可避免。但是MySQL
的InnoDB
引擎在REPEATABLE-READ
就避免了幻读现象。
InnoDB REPEATABLE-READ 隔离级别下如何避免幻读
表象:快照读(伪MVCC
)
内在:next-key 锁
(行锁 +gap
锁)
当前读和快照读
当前读:即加了锁的增删改查语句,读取的是记录的最新版本,并且读取之后还需要保证其他并发事务不能修改当前记录,所以要加锁。
快照读:不加锁的非阻塞读,SELECT
。为了提升并发性能,读写不冲突,基于MVCC
,避免了加锁操作,开销更低。
MVCC
:多版本并发控制,避免使用锁,保存某个时间点上的数据快照。
next-key 锁
行锁:对单个行上锁。
Gap
锁:对间隔上锁。
对主键索引或者唯一索引会用 Gap 锁吗?
如果 WHERE
条件全部命中,则不会用 Gap
锁,只会加记录锁。
如果 WHERE
条件部分命中或全不命中,则会加 Gap
锁。
Gap 锁会用在非唯一索引或者不走索引的当前读中
非唯一索引
只会锁住操作相邻的区间。
不走索引
所有的区间都会被锁住。
RC、RR 级别下的 InnoDB 的非阻塞读如何实现
- 数据行里的
DB_TRX_ID
、DB_ROLL_PTR
、DB_ROW_ID
字段。 undo
日志。read view
,决定当前事务能看到的是哪个版本的数据。