什么是数据库事务?
可操作数据项的一组SQL语句操作,这些操作要么全部成功,要么全部失败(可能一部分成功,但有一步失败就要回滚所有操作),并且要满足ACID属性。
数据库的ACID属性
- 原子性(Atomicity):一组操作指令,要么都成功,要么都失败
- 一致性(Consistent):在事务开始和完成时,没有预期外的数据状态产生
- 隔离性(Isolation):多个事务操作,相互之间互不影响
- 持久性(Durable):事务完成后对于数据的修改是永久性的,即使系统崩溃也能保持
主要了解一下事务隔离~
事务隔离要解决的问题
- 脏读:读取最终一定存在的数据,如果读到其他事务未提交的数据,可能会回滚
- 不可重复读:同一事务内,不同的时刻读到的同一批数据可能不一样
- 幻读:更改同一条记录行,事务B在事务A提交前进行更改操作,导致事务A提交时,数据更改好像未起作用
事务隔离级别
- 读未提交:不加锁,性能最好。事务A可以读到事务B修改过但未提交的数据
- 读已提交:事务B只能在事务A修改过并且已经提交后才能读到事务B修改的数据
- 可重复读:事务B只能在事务A修改过数据并提交后,自己也提交后才能读到事务B修改的数据
- 串行化:效率最低
> 隔离越严格,并发副作用越小,但效率越低
| 事务隔离 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不可能 | 可能 | 可能 |
| 可重复读 | 不可能 | 不可能 | 可能 |
| 串行化 | 不可能 | 不可能 | 不可能 |
MySQL默认的事务隔离级别是可重复读
事务隔离如何实现?(以MySQL为例)
事务隔离是通过锁实现的。
锁:锁是计算机协调多个进程或线程并发访问某一资源的机制。
锁的分类
锁的两种策略:
1. 乐观锁:不上锁,仅在提交数据时判断是否其他事务操作过此数据,如果有就回滚。适合并发量不大或者数据冲突的后果不严重的。
2. 悲观锁:每次读都会上锁,直到取出数据,开销大。适合并发量大的。
从读写角度分为:(针对InnoDB存储引擎)
InnoDB的行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件进行数据检索,InnoDB才使用行级锁;否则,将使用表锁。
1. 分享锁:也叫读锁。有读锁的事务只读不可写,其他事务可以加读锁不能加写锁
select * from table where id = 1 lock in share mode;
- 排他锁:也叫写锁。有写锁的事务可读可写,其他事务不允许加读锁或写锁
delete update insert操作默认加排他锁
select * from table where id = 1 for update;
从颗粒度的角度分:
1. 表级锁:整个表加锁,用户可进行读操作。如用户对表进行写操作,获得写锁且禁止其他用户读写操作,开销小,并发度最低。
2. 行级锁:对表中的一行记录加锁,表中的其他记录可进行读写操作。开销大,能支持高并发但会出现死锁。
死锁:事务A和B互相等对方完成,形成死循环
读未提交:不加锁,不隔离
读提交:读取数据时加行级读锁,读完该行后释放读锁;更新数据时加写锁,事务结束后释放。
可重复读:读取数据时加行级读锁,事务结束后释放读锁;更新数据时加写锁,事务结束后释放。
串行化:读的时候加读锁,其他事务可以并发读,但不能写。写的时候加写锁,其他事务不能并发写,也不能并发读,效率很低。
所以在只使用锁来实现隔离级别的控制的时候,需要频繁的加锁解锁,而且很容易发生读写的冲突。为了不加锁解决读写冲突的问题,引入了MVCC机制。