事务
确保事务内的SQL都可以同步执行 要么一起成功 要么一起失败。事务有四个特性原子性 一致性,隔离性,持久性
实现方式
开始事务的时候回家记录记录一个LSN日志序列 当事务执行的时候 会首先在Innodb_log_buffer 日志缓冲区插入事务日志 redo log;当事务提交的时候 再根据不同的策略将缓冲日志刷新到日志文件和磁盘中。
- 将数据写入InnoDB buffer pool 并加上独占锁
- 将UNDO信息写入undo表的回滚段 以备回滚数据的时候使用
- 更改缓存页中的数据 并将更新记录写入redo buffer
- 提交时 根据innodb_flush_log_at_trx_commit的设置 用不同的方式将redo buffer中的数据刷新到 redo log file中 然后释放独占锁
- 最后 后台IO线程根据将缓存中的数据刷新到磁盘
innodb_flush_log_at_trx_commit
默认设置为1 既在每次事务提交的时候 都会将缓冲池中的数据写入到日志文件中 并立即调用系统fsync 刷新日志文件到磁盘
设置为0 每秒钟执行数据写入日志并调用系统fsync
设置为2 只在事务提交的时候 属性buffer中的redo 数据写入日志文件中 但是将日志文件写入到磁盘 则由系统配置确认
innodb_log_buffer_size
决定了重做日志缓存的大小 如果是无语中有大量的插入或者更新数据 则需要调整默认配置 以提高性能
Redo log 重做日志缓冲
主要是解决 提交的事务没有执行完成但是数据库奔溃了,当数据库恢复之后,可以完整的恢复数据。
InnoDB存储引擎会首先将重做日志信息放到这个缓冲区 redo log buffer,然后按照不同的策略和频率将buffer中的数据刷新到重做日志中。
redo log 在磁盘中保存的名称为 ib_logfile0 and ib_logfile1
Checkpoing技术
当事务提交时 需要先写重做日志 然后再修改页。如果数据库由于未知原因崩溃 而导致数据丢失的时候 需要通过重做日志来完成
当数据库发生宕机的时候 数据的恢复 不需要重做所有的日志 这个时候就用到checkpoint。数据恢复的时候 只要恢复Checkpoint这个点,之后的数据即可。
对于InnoDB存储引擎来来说 有一个LSN的数字 来标记版本的
数据库并发
相对于串行处理 支持并发可以提高数据库的利用率 但是数据库并发可能会带来下面的问题
- 更新丢失 例如同一条数据 被不同事物更新 导致 有一个事务更新错误
- 脏读 事务更新的数据 还未提交 就被另外一个事务读取 读取的数据 就有可能是脏数据
- 不可重复读 前后两次读取数据的过程中 数据被另外的事务修改 导致数据不一致
- 幻读 解决了不可重复读 但是如果另外一个事务同时提交了新的数据
innodb隔离级别
MariaDB [(none)]> show variables like "%tx_isolation%";
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
在同一个事务内的查询 与事务开始时刻的数据 保持一致 ,但是存在幻读
幻读演示
session1 开启事务
mysql> begin;
Query OK, 0 rows affected
mysql> select * from phone; ##事务开始的时候 电话是123456的数据只有一条数据
+----+----------+
| id | phone |
+----+----------+
| 3 | 123456 |
+----+----------+
session2 如果是事务添加的话 也是一样的结果
## 这里插入一条新的数据 电话也是 123456
INSERT INTO `tmp`.`phone` (`id`, `phone`) VALUES ('2', '123456');
session1
mysql> select * from phone; ##这里再次读取数据 和事务开始的时候 数据相同 电话是123456的数据只有一条数据
+----+----------+
| id | phone |
+----+----------+
| 3 | 123456 |
+----+----------+
## 这里更新数据的时候 显示有2行数据被更新了
mysql> update phone set phone=111111 where phone=123456;
Query OK, 2 rows affected
Rows matched: 2 Changed: 2
## 这个时候 数据库有两条数据 被更新成111111了 出现了幻读
mysql> select * from phone;
+----+----------+
| id | phone |
+----+----------+
| 2 | 111111 |
| 3 | 111111 |
解决方式 加锁
Session1 开启一个事务 并通过for update的方式加锁
mysql> begin;
mysql> select * from phone for update;
Session2 开启一个事务
mysql> INSERT INTO `tmp`.`phone` (`id`, `phone`) VALUES ('2', '123456');
1205 - Lock wait timeout exceeded; try restarting transaction
## 锁等待超时 退出事务
MVCC
多版本并发控制协议 优点是 读不加锁 读写不冲突。InnoDB通过Undo Log实现了数据的多版本。而并发控制则是通过锁来实现。读操作可以分为两种 一种是快照读另外一种是当前读
快照读:读取的是记录的可见版本 也有可能是历史版本 不用加锁。
当前读:读取的是记录的最新版本
InnoDB实现的MVVC没有解决幻读 可以通过加锁的方式解决这个问题
innodb buffer pool
参考博客 :https://michael.bouvy.net/blog/en/2015/01/18/understanding-mysql-innodb-buffer-pool-size/
查看包含的数据类型及大小
SELECT
page_type AS Page_Type,
sum(data_size) / 1024 / 1024 AS Size_in_MB
FROM
information_schema.innodb_buffer_page
GROUP BY
page_type
ORDER BY
Size_in_MB DESC;
结果
+-------------------+------------+
| Page_Type | Size_in_MB |
+-------------------+------------+
| INDEX | 0.13445091 |
| UNKNOWN | 0.00000000 |
| INODE | 0.00000000 |
| IBUF_INDEX | 0.00000000 |
| TRX_SYSTEM | 0.00000000 |
| SYSTEM | 0.00000000 |
| UNDO_LOG | 0.00000000 |
| FILE_SPACE_HEADER | 0.00000000 |
| IBUF_BITMAP | 0.00000000 |
+-------------------+------------+
一些重要的且常用的数据类型解释
- INDEX:B-Tree 索引 这里应该也包含行数据页的数据 因为聚簇索引中 主键索引和数据是放在一起的
- IBUF_INDEX: 插入缓存索引(下文 innodb_change_buffering)
- UNKNOWN:未被使用或者不知道状态的
- TRX_SYSTEM:系统数据(有可能是指事务 这里不确定)
- UNDO_LOG:Undo日志(下文 Undo log)
innodb_change_buffering
https://dev.mysql.com/doc/refman/5.5/en/innodb-performance-change_buffering.html
大致意思如下
在 insert update 和 delete操作 如果涉及到索引列 特别是 secondary keys 的时候;如果所涉及到的数据不在buffer pool的时候,由于涉及到的数据 是无序的 那么 频繁的操作会导致大量的IO消耗。到数据被载入到buffer pool的时候,change buffer 会将数据合并在一起 然后更新到磁盘文件中。在空闲的时候,InnoDB的主线程 会合并buffer changes的数据。
由于change buffer 占用了部分buffer pool,降低了内存中可以缓存的数据页,如果数据已经载入到buffer pool或者有很少的secondary indexs,最好禁用掉这个特性。
可设置的值如下
- all
The default value: buffer inserts, delete-marking operations, and purges.- none
Do not buffer any operations.- inserts
Buffer insert operations.- deletes
Buffer delete-marking operations.- changes
Buffer both inserts and delete-marking operations.- purges
Buffer the physical deletion operations that happen in the background.
Undo log
Undo log是InnoDB MVCC事务特性的重要组成部分,记录的是老版本的数据。主要作用是回滚数据,也可以根据undo log回溯到某个特别的版本的数据,实现MVCC。undo数据会首先被刷新到undo buffer中 之后在合适的时间 undo buffer中的数据 会被刷新到磁盘中,所有的undo log 会存放在ibd数据文件中(表空间)。Innodb 中存在purge线程,他们会查询那些无人问津的旧版本数据或者也内容标记为删除的操作也会被清理掉,从而保证undo log 不至于无限增长。
内存管理算法
通常,数据库中的缓冲池是通过LRU即最近最少使用算法来管理,最少使用的也在LRU列表的尾端,当缓冲池中的数据满了之后,会首先释放LRU列表中的尾端的页数据。
但是InnoDBd的存储引擎在读取到新的数据页的时候,不是直接放到LRU列表的首部,而是根据midpoint位置。这样的好处是如果是扫描数据的时候,通常需要操作许多的数据页,而这些数据又仅仅是在这次查询中需要,并不属于活跃的热数据。防止将最活跃的数据被移动到LRU尾部而被释放。