事务
什么是事务?
- 事务是数据库执行操作的最小逻辑单元。
- 事务可以由一个sql组成也可以由多个sql组成
1.特性
| 特征 | 说明 |
|---|---|
| 原子性(A) | 一个事务中所有的操作,要么全部完成,要么全部失败,不会在中间的某一个环节结束 |
| 一致性(C) | 在事务开始之前和事务结束之后,数据库的完整性没有被破坏 |
| 隔离性(I) | 事务的隔离性要求每个读写事务的对象与其它事务的操作对象能相互分离,即该事务提交前对其它事务都不可见 |
| 持久性(D) | 事务一旦提交了,其结果就是永久性的,就算发生了宕机等事故,数据库也能将数据恢复 |
2.并发带来的问题
- 脏读 一个事务读取了另一个事务未提交的数据
| 事务1 | 事务2 |
|---|---|
| start transaction; | |
| select name from user where id = '1'; name =zhangsan | |
| start transaction | |
| update user set name = lisi where id ='1' | |
| select name from user where id = '1'; name =lisi | |
| rollback |
- 不可重复读
一个事务前后两次读取的同一数据不一致
| 事务1 | 事务2 |
|---|---|
| start transaction; | |
| select name from user where id = '1'; name =zhangsan | |
| start transaction | |
| update user set name = lisi where id ='1';commit; | |
| select name from user where id = '1'; name =lisi | |
| commit; | === |
- 幻读 一个事务两次查询的结果集记录数不一致
| 事务1 | 事务2 |
|---|---|
| start transaction; | |
| select name from user where id = '1'; name =zhangsan | |
| start transaction | |
| update user set id = '2' where id ='1';commit; | |
| select name from user where id = '1'; 查询到的值为空 | |
| commit; | === |
3. innodb事务的隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 隔离性 | 并发性 |
|---|---|---|---|---|---|
| 顺序读(SERIALIZEABLE) | N | N | N | 最高 | 最低 |
| 可重复读(REPEATEBALE READ) 默认 | N | N | Y(可以避免) | ||
| 读以提交(READ COMMITED) | N | Y | Y | ||
| 读未提交(READ UNCOMMITED) | Y | Y | Y | 最低 | 最高 |
修改事务的隔离级别
SET [PERSIST|GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL {READ UNCOMMITED|READ COMMITED|REPEATEBALE READ|SERIALIZEABLE}
4. 事务带来的阻塞问题
阻塞 是由于不同锁之间的兼容关系,造成的一事务需要等待另一个事务释放其所占用的资源的现象 产生阻塞的主要原因是因为锁的机制,innodb中的锁:
- 查询需要对资源加共享锁
- 数据修改需要对资源加排它锁
| 排它锁 | 共享锁 | |
|---|---|---|
| 排它锁 | 不兼容 | 不兼容 |
| 共享锁 | 不兼容 | 兼容 |
如果发现阻塞? mysql8.0:
SELECT waiting_pid AS '被阻塞的线程',
waiting_query AS '被阻塞的sql',
blocking_pid AS '阻塞线程',
blocking_query AS '阻塞sql',
wait_age AS '阻塞时间',
sql_kill_blocking_query AS '建议操作'
FROM sys.innodb_lock_waits
WHERE (unix_timestamp()-unix_timestamp(wait_started))>30
mysql5.7:
#查询进程列表
show processlist;
select * from information_schema.innodb_trx\G
5. 死锁问题
并行执行的多个事务相互之间占有了对方所需要的资源。我们可以打开Mysql自动检测死锁的机制,来处理此问题
set global innodb_print_all_deadlocks = on
6.事务原理
6.1 基本概念
- redo log:重写日志 存在于ib_logfile0~1中,默认是50M,轮询使用
- redo log buffer redo log 内存区域
- idb 数据存储文件,数据行和索引
- data buffer pool 数据缓冲区,数据和索引的缓冲
- LSN: 日志序列号 存在于idb、redo文件、redo log buffer、data buffer pool,mysql每次启动时,都会比较磁盘数据页和redolog的LSN,必须两者LSN保持一致,数据库才能启动成功
- WAL : write ahead log 日志优先于数据写入到磁盘中
- 脏页: 内存脏页。内存中发生了修改,没写入到磁盘之前,我们把内存页称之为脏页
- CKPT 检查点,就是将脏页数据刷写到磁盘的动作
- TXID 事务id,
- undo 回滚日志 存在于ibdata
- 记录了数据修改之前的状态
- rollback将内存的数据修改恢复到修改之前
- 在CSR中实现未提交数据的回滚操作
- 实现一致性快照,配合锁隔离级别保证MVCC(多版本并发控制),读和写的操作不会互相阻塞
6.2基本流程
- 情况一
我们做了一个事务,begin;update;commit
- 在begin,会立即分配一个TXID=tx_01
- update时,会将需要修改的数据页(dp_p1,LSN=101),加载到data buffer中
- DBWR线程,会进行dp_01数据页修改更新,并更新LSN=102
- LOGBWR日志写线程,会将dp_01数据页的变化+LSN+TXID存储到redobugger中
- 执行commit时,LGWR日志写线程会将redobugger信息写入rdolog日志文件中,基于WAL原则,在日志完全写入磁盘后,commit命令才执行成功,会将此日志打上commit标记
- 假如此时宕机,内存脏页没有来得及写入磁盘,内存数据全部丢失
- Mysql再次重启时,必须要redolog和磁盘数据页的LSN是一致的,但是,此时dp_01,TXID=tx_01磁盘是LSN=101,dp_01,TXID=tx01,redolog中LSN=102.MySQL此时无法正常启动,MySQL触发CSR.在内存中追平LSN号,触发CKPT,将内存数据页更新到磁盘,从而保证磁盘数据页和redolog的LSN一致,这是MySQL正常启动