MySQL——存储引擎、表空间、事务的理解及介绍

文章目录

  • 一、存储引擎简介
  • 二、存储引擎的种类
  • 三、存储引擎的特性
    • InnoDB存储引擎
      • Innodb数据页结构
      • innodb存储引擎在物理存储下的结构
      • Innodb存储引擎核心特性——参数(双一标准之一)
    • MyISAM存储引擎
    • MEMORY存储引擎
    • MERGE存储引擎
    • TOKUDB存储引擎
  • 四、表空间的理解及说明
    • 表空间理解
    • 共享表空间
    • 独立表空间
  • 五、表迁移
  • 六、事务
    • 事务的四个特性ACID
    • 事务的控制语句
    • InnoDB事务ACID的实现
      • 对于事务ACID流程的理解 *****(redo)
      • 对于事务ACID流程的理解 *****(undo)
    • 锁的种类及作用
    • 隔离级别(针对读操作)
    • MVCC多版本并发机制详解

一、存储引擎简介

1、存储引擎的相当于Linux文件系统这么一种结构,主要负责数据的读写,以及数据的安全,数据的一致性,性能的提升,热备份,高可用等一些功能。
2、InnoDB是一种兼顾了高可靠性和高性能的通用存储引擎。在MySQL 8.0中,InnoDB是默认的MySQL存储引擎。除非您配置了其他默认存储引擎,否则 'CREATE TABLE不带ENGINE= ’ 子句的语句将创建一个InnoDB表,MySQL5.5之前的默认存储引擎是MyISAM,5.5版本之后改成了InnoDB。

3、它的DML操作遵循ACID模型,并具有提交,回滚和崩溃恢复功能的事务,以保护用户数据。

4、行级锁定和Oracle风格的一致读取可提高多用户并发性和性能。

5、InnoDB表格将您的数据排列在磁盘上以基于主键优化查询 。每个InnoDB表都有一个称为聚集索引的主键索引,该索引组织数据以最小化主键查找的I / O。

6、维护数据完整性, InnoDB支持 FOREIGN KEY约束。使用外键检查插入,更新和删除,以确保它们不会导致不同表之间的不一致。

7、存储引擎是作用在表上的,也就意味着,不同的表可以有不同的存储引擎类型。

8、MySQL默认支持多种存储引擎,以适用于不同领域的数据库应用的需要,用户可以选择不同的存储引擎提高应用以及业务的效率,实现最大程度的可定制性。

二、存储引擎的种类

1、Oracle MySQL
有以下几种:InnoDB、MyISAM、MEMORY、ARCHIVE、FEDERATED、EXAMPLE、BLACKHOLE、MERGE、NDBCLUSTER、CSV

mysql[t1]>show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)

2、PerconaDB:默认是XtraDB

3、MariaDB:默认是InnoDB

4、其他的存储引擎支持:

TokuDB

RocksDB

MyRocks

以上三种存储引擎的共同点:压缩比较高,数据插入性能极高
现在很多的NewSQL,使用比较多的功能特性,可以去相关的网站进行了解。

三、存储引擎的特性

各种存储引擎的特性:
MySQL——存储引擎、表空间、事务的理解及介绍_第1张图片

InnoDB存储引擎

查看存储引擎有关的内容:

select table_schema,table_name,engine from information_schema.tables where engine='innodb';

InnoDB存储引擎功能:
MySQL——存储引擎、表空间、事务的理解及介绍_第2张图片
InnoDB存储引擎的优点:

1、事务(Transaction)

2、MVCC(Multi-Version Concurrency Control多版本并发控制)

3、行级锁(Row-level Lock)

4、ACSR(Auto Crash Safety Recovery)自动的故障安全恢复 (XBK增量备份)

5、支持热备份(Hot Backup)

6、Replication: Group Commit , GTID (Global Transaction ID) ,多线程(Multi-Threads-SQL )

7、支持外键,保证多表的数据一致性

8、Replication:group commit,GTID

默认存储引擎查看及修改:

mysql[(none)]>select @@default_storage_engine;
+--------------------------+
| @@default_storage_engine |
+--------------------------+
| InnoDB                   |
+--------------------------+
1 row in set (0.00 sec)

mysql[(none)]>set default_storage_engine=myisam;     		当前修改
Query OK, 0 rows affected (0.00 sec)

mysql[(none)]>set global default_storage_engine=myisam;		全局修改
Query OK, 0 rows affected (0.00 sec)

查看及修改库下面表的存储引擎:

使用show命令查询:
mysql[world]>show  table status from world;
+-----------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+-------------------+----------+----------------+---------+
| Name            | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time         | Update_time         | Check_time | Collation         | Checksum | Create_options | Comment |
+-----------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+-------------------+----------+----------------+---------+
| city            | InnoDB |      10 | Dynamic    | 4188 |             97 |      409600 |               0 |       131072 |         0 |           4080 | 2020-02-10 00:01:59 | 2020-02-10 00:02:00 | NULL       | latin1_swedish_ci |     NULL |                |         |
| country         | InnoDB |      10 | Dynamic    |  239 |            411 |       98304 |               0 |            0 |         0 |           NULL | 2020-02-10 00:02:00 | 2020-02-10 00:02:00 | NULL       | latin1_swedish_ci |     NULL |                |         |
| countrylanguage | InnoDB |      10 | Dynamic    |  984 |             99 |       98304 |               0 |        65536 |         0 |           NULL | 2020-02-10 00:02:00 | 2020-02-10 00:02:00 | NULL       | latin1_swedish_ci |     NULL |                |         |
+-----------------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+---------------------+------------+-------------------+----------+----------------+---------+
3 rows in set (0.00 sec)

使用select命令查询除系统库以外的库内表的相关信息:
mysql[world]>select table_schema,table_name,engine from information_schema.tables where table_schema not in ('sys','mysql','information_schema','performance_schema');
+--------------+-----------------+--------+
| table_schema | table_name      | engine |
+--------------+-----------------+--------+
| t1           | stu             | InnoDB |
| t1           | student         | InnoDB |
| t1           | t_50w           | InnoDB |
| world        | city            | InnoDB |
| world        | country         | InnoDB |
| world        | countrylanguage | InnoDB |
+--------------+-----------------+--------+
6 rows in set (0.01 sec)
或者单库查询:
select table_schema,table_name,engine from information_schema.tables 
where table_schema='world';
修改单表存储引擎(还能进行对innodb表的碎片整理):
mysql[world]>alter table city engine=innodb;
Query OK, 0 rows affected (0.14 sec)
Records: 0  Duplicates: 0  Warnings: 0delete的删除是不会释放磁盘的
多表修改使用拼接语句:
mysql[world]>select concat("alter table",table_schema,".",table_name," engine=innodb") from information_schema.tables where table_schema='world';
+--------------------------------------------------------------------+
| concat("alter table",table_schema,".",table_name," engine=innodb") |
+--------------------------------------------------------------------+
| alter tableworld.city engine=innodb                                |
| alter tableworld.country engine=innodb                             |
| alter tableworld.countrylanguage engine=innodb                     |
+--------------------------------------------------------------------+
3 rows in set (0.00 sec)
多表修改一般在业务不繁忙时进行修改,因为DDL修改表引擎会导致锁表。

Innodb数据页结构

File Header (文件头)上下编号 组成双链表
Page Header ( 页头) deleted_flag	next 16 bits指向下一条数据的指针
Infimun + Supremum Records 上下限数据记录 边界
User Records (用户记录 ,即行记录)
Free Space (空闲空间)
Page Directory (页目录)理解为索引 solts,每6条数据插入一个solts
File Trailer (文件结尾信息)检验页的完整性,是否写入磁盘

常见的页类型有:

数据页 ( B-tree Node )
Undo页 ( Undo Log Page )
系统页 ( System Page )
事务数据页 ( Transaction system Page )
插入缓冲位图页 (Insert Buffer Bitmap )
插入缓冲空闲列表页 (Insert Buffer Free List )
未压缩的二进制大对象页 (Uncompressed BLOB Page )
压缩的二进制大对象页 (Compressed BLOB Page )

innodb存储引擎在物理存储下的结构

5.7版本:
MySQL——存储引擎、表空间、事务的理解及介绍_第3张图片
8.0版本:
MySQL——存储引擎、表空间、事务的理解及介绍_第4张图片

5.7版本
ibdata1:系统数据字典信息(统计信息)也叫元数据,变更缓冲池,双写缓冲池,UNDO表空间等数据,  
ib_logfile0 ~ ib_logfile1: REDO日志文件,事务日志文件。
ibtmp1: 临时表空间磁盘位置,存储临时表
frm:存储表的列信息
ibd:表的数据行和索引
8.0版本:
ibdata1(system tablespace):变更缓冲池、双写缓冲池
ibd:表的数据行和索引
frm:存储表的列信息
ibtmp1(global):临时表空间磁盘位置,存储临时表
temp_1.ibt(session):也是临时表空间位置,存储单个会话的临时表
ib_logfile0 ~ ib_logfile1:REDO日志,事务日志文件
undo_001.ibu(undo tablespaces):undo表空间和数据

外键的创建及使用:

show create tableshow table status 	查看外键信息
set foreign_key_checks=0;				关闭外键约束
set foreign_key_checks=1;				开启外键约束

Innodb存储引擎核心特性——参数(双一标准之一)

指定/修改存储引擎:

通过参数设置默认引擎

建表时:
create table tablename(列内容)engine=innodb;

建完表后的修改:
alter table t1 engine=innodb;

表空间:

共享表空间:
innodb_data_file_path
初始化之前已设置好
例:innodb_data_file_path=ibdata1:512M:ibdata2:512M:autoextend
独立表空间:
select @@innodb_file_per_table;

BUFFER POOL:

select @@innodb_buffer_pool_size;
set global innodb_buffer_pool_size=268435456;
show engine innodb status\G;
可以看到buffer pool size的大小是多少,空闲了多少,大小单位为页,需要乘16再除1024就是M的单位
也可以在参数进行设置

Innodb双一标准(之一):

控制日志写入磁盘的标准
select @@innodb_flush_log_at_trx_commit;
mysql[(none)]>select @@innodb_flush_log_at_trx_commit;
+----------------------------------+
| @@innodb_flush_log_at_trx_commit |
+----------------------------------+
|                                1 |
+----------------------------------+
1 row in set (0.00 sec)
取值012,默认值为1
0表示事务提交不立即刷写到磁盘,每秒将redo buffer刷写到os buffer,然后文件系统再每秒sync磁盘一次。
1表示事务提交立即刷写到磁盘,从redo buffer立即刷写到os buffer,osbuffer也立即刷写到ib_logfile0~1
2表示事务提交立即刷写到os buffer,os buffer每秒完成一次sync磁盘操作。

在这里插入图片描述
Innodb刷新方法:

innodb_flush_method=O_DIRECT,fsync,O_DSYNC
这个参数控制的是log buffer和data buffer是否经过文件系统缓存
参数说明:
O_DIRECT  :数据缓冲区写磁盘,不走OS buffer
fsync :日志和数据缓冲区写磁盘,都走OS buffer
O_DSYNC  :日志缓冲区写磁盘,不走 OS buffer
查看方式:
select @@innodb_flush_method;
建议使用:
高安全:
innodb_flush_log_at_trx_commit=1
innodb_flush_method=O_DIRECT
高性能:
innodb_flush_log_at_trx_commit=0
innodb_flush_method=fsync

刷写方式直接看图中所示:MySQL——存储引擎、表空间、事务的理解及介绍_第5张图片
MySQL——存储引擎、表空间、事务的理解及介绍_第6张图片

MyISAM存储引擎

MyISAM是MySQL5.5版本之前默认使用的存储引擎,即不支持事务,也不支持外键,所以相对于InnoDB的访问速度会快很多
在磁盘上存储成3个文件,扩展名:

.frm(存储表定义)
.MYD(MYData,存储数据)
.MYI(MYIndex,存储索引)
数据文件和索引文件放在不同的文件,平均的分布了IO,可以获得更快的速度。
表损坏可修复表:
check table		检查表
repair table	修复表

支持3种不同的存储格式,分别是:
静态表(默认格式)
动态表
压缩表

MyISAM查询语句:

select table_schema,table_name,engine from information_schema.tables where engine='myisam';

MyISAM支持的功能及特点:

支持温备份

表级锁

B树索引、全文索引

索引缓存

数据可压缩性

批量插入性能高

MEMORY存储引擎

MEMORY存储引擎使用存在内存中的内容来创建表。一般用在那些内容变化不大的代码表,或者做为统计操作的中间结果表,便于高效的对中间结果分析并统计。对于MEMORY表的话更新操作需要小心,因为表是存在内存中的,并没有写到磁盘上,所以如果一旦服务重启或者断电操作都会导致数据的丢失。

select table_schema,table_name,engine from information_schema.tables where engine='memory';

可以看到MEMROY的表都是在information_schema表下的。

MERGE存储引擎

用于将一系列等同的MyISAM表以逻辑方式组合在一起,并作为一个对象 引用它们。MERGE 表的优点在于可以突破对单个 MyISAM 表大小的限制,并且通过将不同 的表分布在多个磁盘上,可以有效地改善 MERGE 表的访问效率。这对于诸如数据仓储等 VLDB 环境十分适合。
MyISAM表的组合,MyISAM表必须结构完全相同,merge表没有数据,对这个MERGE类型的表进行增删改查的操作是对内部MyISAM表进行的。
MERGE表进行DROP操作,只删除表定义,对内部表没有影响。

select table_schema,table_name,engine from information_schema.tables where engine='merge';

TOKUDB存储引擎

这个存储引擎是最近几年新扩展,额外的改进了存储引擎的性能,可以提供索引查询加速,优化复制的性能,还能提高备份压缩,架构在线修改,适用于在写入密集的工作条件下使用。
HybridDB目前使用的就是ToKuDB,使用方法和innodb相同。
HybridDB 基于开源 Greenplum Database (内核是 PostgreSQL)项目的 MPP 分布式数据仓库,与 PostgreSQL 不同,HybridDB 可以实现横向扩展,提供用户需要的百 GB 到百 TB 的高性能分析能力。

select table_schema,table_name,engine from information_schema.tables where engine='tokudb';

四、表空间的理解及说明

表空间理解

一般的话用户操作完,所有数据都是存储在内存区域的,然后内存区域再写入到磁盘里面,表空间在这里相当于起到中间的作用,内存写到磁盘前都是需要经过这个逻辑层表空间(tablespace)的。
为什么要这样做呢?因为如果内存直接把数据写到磁盘里面的话,如果这个磁盘满了,就没办法扩容了,只能通过加硬盘,然后把原来的数据导到入新的磁盘里面, 还需要停业务,所以效率不高。
所以提出了这么个逻辑层的表空间的方法,把ibd、数据这一类的文件都放到表空间下,然后如果表空间不够了,直接能在线给表空间加存储容量,这样就提高了数据库和磁盘的局限性,也不需要停系统业务,类似于LVM的一种理念,这个表空间就类似于操作系统下的磁盘上的逻辑卷。

共享表空间

把所有数据存储到同一个表空间中,管理比较混乱。

5.5版本:出现了共享表空间管理模式,默认即为共享表空间的管理模式。

5.6版本:共享表空间只用来存储数据字典信息、undo、临时表,其它都被独立出来。

5.7版本:临时表也被独立出来了,共享表空间只有undo、数据字典信息。

8.0版本:undo也被独立出来了,共享表空间只剩数据字典信息。

共享表空间设置:

mysql[(none)]>select @@innodb_data_file_path;	配置表空间大小
+-------------------------+
| @@innodb_data_file_path |
+-------------------------+
| ibdata1:12M:autoextend  |
+-------------------------+
1 row in set (0.00 sec)
mysql[(none)]>show variables like '%extend%';	增长空间大小
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| innodb_autoextend_increment | 64    |
+-----------------------------+-------+
1 row in set (0.03 sec)
vim /etc/my.cnf直接从配置文件修改
innodb_data_file_path=ibdata1:512M:ibdata2:512M:autoextend
innodb_autoextend_increment=64

独立表空间

5.6版本以后默认表空间不再使用共享表空间,替换成了独立表空间。

独立表空间主要存储用户数据

独立表空间存储特点:每个表都有一个ibd文件,存储用户数据行和索引信息

frm:表结构和元数据存储(例:xxx.frm)

结论:
mysql表组成 = 元数据+数据行+索引
mysql表数据 = ibdata1 + frm + ibd(段、区、页)
DDL DML+DQL
MySQL的innodb存储引擎日志位置:

Redo Log:ib_logfile0 ib_logfile1 重做日志.
Undo Log:ibdata1 ibdata2(存储在共享表空间中)回滚日志
临时表:ibtmp1  一般的话在使用joinunion操作的时候会产生临时数据,用完就自动回收了

独立表空间设置:

mysql[(none)]>select @@innodb_file_per_table;		1表示开启独立表空间0表示共享表空间
+-------------------------+
| @@innodb_file_per_table |
+-------------------------+
|                       1 |
+-------------------------+
1 row in set (0.00 sec)
set global innodb_file_per_table=0;
set global innodb_file_per_table=0;
删除和填加表空间:
use world;
alter table tablename dicard tablespace;    只删除这张表的ibd文件
alter table tablename inport tablespace;	如果其它地方有这张表的ibd文件,拷到原来的目录下能直接用这条命令恢复

五、表迁移

create table xxx;
alter table database.tablename discard tablespace;
alter table database.tablename import tablespace;

例(参考案例):
生产环境下有一个jira(bug追踪),confulence(内部知识库)——LNMT架构

软件及硬件环境:

联想服务器IBM
磁盘500G 没有RAID
系统:CentOS6.8
MySQL 5.6.33 innodb引擎  独立表空间
备份没开,日志也没开
使用编译制作的RPM包
软件包:/usr/bin/mysql
数据包:/var/lib/mysql
所有数据都在’/‘目录下

故障:

断电了,然后启动完成后的“/”目录是只读状态
所以进行了fsck 的重启,保证操作系统进行正常启动(一般的话不要这样操作,虽然能保证操作系统能正常启动,
但是里面的数据不一定能保证数据存在,所以我想到的方法是进救援模式先备份一份数据出来,挂一块新磁盘需要比原来的磁盘大,
放到新磁盘上面,然后再进行操作,就算数据丢了,至少还有备份,镜像磁盘命令:dd if=/dev/sda of=/dev/sdb)
执行完fsck以后,mysql启动不了了,结果就是confulence库还在,然后jira库丢失了,但是直接起confulence起不来了,因为表空间和frm都没了
只有ibd文件了,先把这个ibd文件考到其它地方,留着备用。

解决:

jira只能去硬盘恢复了,因为数据没有备份,没有二进制日志,也没有主从
在confulence库下的话目录下的表空间和frm都没了,只有元数据和ibd文件,接下去就是需要把表都创建出来,必需要和原来的表结构创建的一样,
前提是还有以前留下的表的结构,不然表创建不出来。
创建出来的表相当于就是这个表的frm,然后把这个表给discard掉,这样这个表的ibd文件就直接丢失掉了,然后通过把备份的那个ibd文件给拷过来授权,
这样原来的数据就被恢复出来了。

过程:

1、现在虽然是有ibd文件,但是打不开,还好有历史库,把历史库的表结构去拿到一个新的测试库:
mysqldump -u root -p -B confulence --no-data >test.sql
表结构导出来了,现在有了
2、然后把需要恢复库的表空间给删除
3306[world]>select concat('alter table ',table_schema,'.',table_name,' discard tablespace;') from information_schema.tables where table_schema='world' into outfile '/tmp/discard.sql';
Query OK, 3 rows affected (0.00 sec)
3306[(none)]>source /tmp/discard.sql;
Query OK, 0 rows affected, 1 warning (0.01 sec)

Query OK, 0 rows affected (0.01 sec)

Query OK, 0 rows affected, 1 warning (0.00 sec)
如果有外键跳过外键检查:set foreign_key_checks=0
3、然后把刚原来库里面打不开的ibd文件拷到这个新的测试库,再进行恢复
[root@localhost ~]# ls /opt/mysql-data/mysql/world/
city.frm  country.frm  countrylanguage.frm  db.opt
[root@localhost ~]# mv /opt/world/country.ibd /opt/world/city.ibd /opt/world/countrylanguage.ibd /opt/mysql-data/mysql/world/
[root@localhost ~]# ls /opt/mysql-data/mysql/world/
city.frm  city.ibd  country.frm  country.ibd  countrylanguage.frm  countrylanguage.ibd  db.opt
[root@localhost mysql]# chown -R mysql.mysql world/


3306[(none)]>select concat('alter table ',table_schema,'.',table_name,' import tablespace;') from information_schema.tables where table_schema='world' into outfile '/tmp/import.sql';
Query OK, 3 rows affected (0.00 sec)

3306[(none)]>source /tmp/import.sql;
Query OK, 0 rows affected, 2 warnings (0.06 sec)

Query OK, 0 rows affected, 2 warnings (0.05 sec)

Query OK, 0 rows affected, 2 warnings (0.04 sec)

3306[world]>select * from city limit 10;
+----+----------------+-------------+---------------+------------+
| ID | Name           | CountryCode | District      | Population |
+----+----------------+-------------+---------------+------------+
|  1 | Kabul          | AFG         | Kabol         |    1780000 |
|  2 | Qandahar       | AFG         | Qandahar      |     237500 |
|  3 | Herat          | AFG         | Herat         |     186800 |
|  4 | Mazar-e-Sharif | AFG         | Balkh         |     127800 |
|  5 | Amsterdam      | NLD         | Noord-Holland |     731200 |
|  6 | Rotterdam      | NLD         | Zuid-Holland  |     593321 |
|  7 | Haag           | NLD         | Zuid-Holland  |     440900 |
|  8 | Utrecht        | NLD         | Utrecht       |     234323 |
|  9 | Eindhoven      | NLD         | Noord-Brabant |     201843 |
| 10 | Tilburg        | NLD         | Noord-Brabant |     193238 |
+----+----------------+-------------+---------------+------------+
10 rows in set (0.00 sec)
恢复成功!

六、事务

事务的理解:

事务简单的说就是做一件事情,这个事情有很多步骤,事务里面执行的SQL语句相当于就是这些步骤,然后做这个事情只有成功或者失败两个结果,如果步骤都做完了就做成功了,有步骤没做完,那就要回头重新做,之前做的步骤就会全部失效。

事务的四个特性ACID

一、原子性(Atomicity)
所有语句要么全部执行成功,要么全部执行失败,没有中间状态,事务中原子是最小单元,不能再进行分割,事务中的操作也都是原子单位。
二、一致性(Consistency)
事务执行后,数据库之间的数据必须保持一致。比如银行的转帐业务,不管在事务执行后成功还是失败,银行帐户里的金额总和都是不变的。
三、隔离性(Isolation)
指在数据库操作中,每个事务都是单独隔离开来的,每个事务的操作不会互相干扰。
例:a在取钱,b是银行人员,a取钱的过程就相当于一个事务在发生,所以b看到的要么就是a取钱之前的金额数量,
要么就是b取完钱之后的金额数量。这也是隔离级别中的一种,这个隔离级别等会会介绍。
四、持久性(Durability)
事务一旦提交成功,提交的事务必须被持久化到数据库中去,就算提交完成后出来宕机了或者断电了,在重启数据库的时候,也可以通过CSR的机制进行数据的恢复。

事务的控制语句

事务的相关命令:

start transaction / begin  :可以开始一项新的事务。
commit/rollback : 提交或者回滚事务。
示例:
begin
insert into tablename values(xxx,xxx);
rollback;

begin
insert into tablename values(xxx,xxx);
commit;

chain:用在提交或回滚事务之后,会开启一个新的事务,并且拥有和前一个事务相同的隔离级别。
release:用在提交或回滚事务之后,断开和客户端的连接。
示例:
begin
insert into tablename values(xxx,xxx);
commit and chain;
begin 
insert into tablename values(xxx,xxx);
commit and release;

set autocommit:修改事务提交方式,为1时自动提交,为0时则需要通过commit命令进行手动提交。
示例:
3306[world]>set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
3306[world]>set global autocommit=0;
Query OK, 0 rows affected (0.00 sec)
3306[world]>select @@autocommit;		自动提交查询
+--------------+
| @@autocommit |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)

lock tables:锁定用于当前线程的表。
示例:
3306[world]>lock table city read;		读锁定
Query OK, 0 rows affected (0.00 sec)  
3306[world]>lock table city write;		写锁定
Query OK, 0 rows affected (0.01 sec)
当读锁定时,其它线程还能进行读表操作,如果是写锁定,其它线程会进行等待,直到锁释放为止才能进行读取。

unlock tables:释放当前线程获得的任务锁定。
示例:
3306[world]>unlock tables;				释放锁
Query OK, 0 rows affected (0.00 sec)

事务的隐式提交示例:

示例1lock table tablename write;
start transaction;
insert into tablename values(1,'hdfeng');
rollback;
start transaction;
示例2begin
insert into city values(xxx,xxx);
update t1 set sname='hdfeng1' where id=1;
begin

示例1就是一个典型的start transaction导致unlock tables,如果其它非事务类型的表再去查看,还是能查看到这条记录,因为非事务类型的表是没有事务这个功能的,所以这个回滚操作会被记录到二进制中,确保这个数据的更新可以恢复到数据库中。
示例2则演示了一个在SET AUTOCOMMIT=1情况下自动提交事务的隐式提交的SQL语句。

事务的隐式提交语句:

DDL语句:alter,create,drop
DCL语句:grant,revoke,set password
示例:
truncate table
load data infile
select for update

模拟回滚事务示例:

用户1的操作:
3306[youngboy]>start transaction;					启动事务
Query OK, 0 rows affected (0.00 sec)
3306[youngboy]>insert into t1 values(1,'hdfeng1');	插入一条记录
Query OK, 1 row affected (0.00 sec)
3306[youngboy]>select * from t1;					查看该记录
+------+---------+
| id   | sname   |
+------+---------+
|    1 | hdfeng1 |
+------+---------+
1 row in set (0.00 sec)

用户2的操作:
3306[youngboy]>select * from t1;					事务没提交,无法查询到记录
Empty set (0.00 sec)

用户1的操作:
savepoint t1;										设置保存点,保存点名为t1
3306[youngboy]>insert into t1 values(2,'hdfeng2');	插入记录2
Query OK, 1 row affected (0.00 sec)
3306[youngboy]>select * from t1;					看到2条记录
+------+---------+
| id   | sname   |
+------+---------+
|    1 | hdfeng1 |
|    2 | hdfeng2 |
+------+---------+
2 rows in set (0.00 sec)
3306[youngboy]>rollback to savepoint t1;			回滚保存点
Query OK, 0 rows affected (0.00 sec)
3306[youngboy]>select * from t1;					只能看到1条记录
+------+---------+
| id   | sname   |
+------+---------+
|    1 | hdfeng1 |
+------+---------+
1 row in set (0.00 sec)

用户2的操作:
3306[youngboy]>select * from t1;					用户1未提交,看不到记录
Empty set (0.00 sec)

用户1的操作:
3306[youngboy]>commit;								提交
Query OK, 0 rows affected (0.01 sec)
3306[youngboy]>select * from t1;					回滚到保存点1,所以只有1条记录
+------+---------+
| id   | sname   |
+------+---------+
|    1 | hdfeng1 |
+------+---------+
1 row in set (0.00 sec)

用户2的操作:
3306[youngboy]>select * from t1;					事务提交,能查到记录
+------+---------+
| id   | sname   |
+------+---------+
|    1 | hdfeng1 |
+------+---------+
1 row in set (0.00 sec)

InnoDB事务ACID的实现

MySQL——存储引擎、表空间、事务的理解及介绍_第7张图片

对于事务ACID流程的理解 *****(redo)

图中蓝色矩形为内存区域
图中红色矩形为磁盘区域 
ibdata1:元数据,undo的一些东西
redo:事务里面的重做日志,事务日志的一种
ib_logfile0~N:redo的日志文件(redolog)
redo buffer:数据页变化的信息+当前LSN号(redo buffer)
LSN:日志序列号
TXID:事务号
page buffer cache:缓冲池,数据和索引的缓冲(buffer pool)
脏页:内存脏页,内存中发生了修改,未写到磁盘之前,把这个内存页称为脏页
ckpt(检查点):将脏页刷写到磁盘的动作
begin
原sname=X
update  t1 set sname='Y' where id=1;
commit

对于这个事务执行的理解:
1、begin开始时,会立即分配给该事务一个TXID=1

2、进行update操作的时候,会把需要修改的page1(LSN=10)调用到buffer cache里面

3、DBWR线程会将page1数据页进行更新,更新到LSN=11

4、LGWR日志写线程,会将page1数据页的变化+TXID+LSN写入到redobuffer中

5、当用户执行commit操作时,LGWR日志写线程会将redobuffer信息写入到redolog日志,基于WAL原则(Write ahead Log),日志完全写入到磁盘中,commit才算成功,并且打上commit的标记

6、如果现在发生突发情况,服务器断电了,就出现了脏页的情况,没有来的急写入到磁盘,内存数据全部丢失。

7、数据库进行重启时,redolog和磁盘data page的LSN必须一致,此时page1,TXID=1磁盘为LSN=10,page1,TXID=1,而redolog中LSN=11,这样的话MySQL数据库是无法启动的,MySQL会触发CSR去追平这个LSN号,ckpt(校验点check point),将内存page更新到磁盘,从而保证data page和redolog的LSN号一致,这个时候数据库再进行启动则可以启动成功,这个操作即为“REDO的前滚操作”。

8、对执行语句的理解:在ibd磁盘上的X这条数据的页被加载到page buffer cache区域了,做完解析、优化、执行,然后把X改成Y,redo buffer会记录这个X改成Y的变化,然后commit以后redo buffer把记录写入REDO磁盘文件,如果现在服务器断电,磁盘上的记录还是A,但是REDO文件里面的记录是X变成Y的记录,这样的话ibd和REDO文件里的记录都会被调到buffer里面进行检验,在redo buffer里面检查到数据X变成Y的操作了,在page buffer cache里面就会去执行这个操作,执行完以后记录到磁盘ibd里面,这个操作就叫前滚,来完成持久化的操作。(buffer cache里面X变成Y是十六进制的变化)

redo的日志参数:

innodb_log_buffer_size=16777216
innodb_log_file_size=50331648
innodb_log_files_in_group=3
show status like '%Com_%';查看commitupdaterollback等命令的提交次数,一般用这个来计算服务器的TPS

对于事务ACID流程的理解 *****(undo)

undo的作用:

实现了ACID里的原子性,要么全部成功,要么全部失败,在ACID里面其实C和I也需要undo支持
在rollback的时候,undo能将数据恢复到begin开始之前
在CSR的时候,把redolog里面没有commit的事务进行回滚
undo还有快照技术:提供了事务修改之前的状态,保证了MVCC多版本并发,事务的隔离性,mysqldump的热备

锁的种类及作用

在MySQL里面,锁主要用在事务ACID中,主要用来实现“C一致性“和”I隔离性“的功能。锁一共分为3种,具体如下:

表级锁:开销比较小,加锁比较快,不容易出来死锁,但是并发度低,锁定粒度大,发生锁冲突的概率非常高。
行级锁:开销比较大,加锁慢,但是容易出现死锁,并发度高,锁定粒度小,发生锁冲突的概率较小。
页面锁:开销和加锁在行级和表级锁之间,会出来死锁,并发度一般,锁定粒度在行级和表级锁之间。

MyISAM和MEMORY存储引擎采用的是表级锁;BDB存储引擎采用的是页表锁,也支持表级锁;InnoDB支持的是行级锁,但也支持表级锁,默认使用行级锁。

表级锁的锁模式

表共享读锁:开启读锁后,其它线程也可以进行读操作,但是无法进行写操作。
表独占写锁:开启写锁后,其它线程无法进行读/写操作。
表级锁在读操作的时候会自动加读锁,写操作的时候自动加写锁,针对于MyISAM,这里不过多讲解。
为了行锁和表锁的共存,表锁里面有意向共享锁和意向排他锁来配合行锁里面的共享锁和排他锁。

InnoDB锁的模式
因为InnoDB锁会有事务、隔离级别等功能,所以在并发状态下会引出一系列的问题,以下介绍的是在并发状态下事务处理带来的问题:

更新丢失(Lost Update):因为事务之间是不知道有对方的存在的,所以双方事务如果同时对一行进行修改,最后commit的事务会把前面所有事务做的更新给覆盖掉,造成了更新丢失的问题。
如果一个事务在编辑的时候,其它事务不能进行访问,就防止了此问题的发生。

脏读(Dirty Read):指的是一个事务在进行修改操作的时候修改了之前的数据,并且在未commit的情况下,
第二个事务对此行数据进行了访问,对于第二个事务来说,这条数据就是和原来数据不一致的状态;
如果第二个事务读取了这行数据,并且进一步做处理,就产生了未提交数据的依赖关系,这种现象就叫’脏读‘;

不可重复读(Non-Repeatable Read):一个事务在读取某行数据后的一个时间点,再次读取这一行的数据的时候,发现这行数据已经发生了修改或者已经被删除,这个就叫做不可重复读。

幻读(Phantom Read):指的是第一个事务读取某一行数据的时候,发现明明这一行没有数据或者是需要修改的数据,
第二个事务对这行数据进行了数据的修改,随后第一个事务却读取到了这行新的数据,这种现象就叫做“幻读”。

乐观锁和悲观锁的区别

乐观锁:指的是很乐观状态,认为自己去拿数据的时候别人不会修改,所以数据不上锁,更新的时候还会去判断一下在此期间有没有其它人更新这个数据,乐观锁其实就是没有锁。
悲观锁:指的是很悲观状态,认为自己去拿数据的时候别人会修改,然后每次拿数据都上锁,别人拿数据的时候必须等到它拿完解锁才能进行访问操作。
所以我们的表锁、行锁、排它锁等等,都属于悲观锁。一般乐观锁用在写比较少的情况下,这样冲突很少发生,就能省去了锁的开销,加大了系统的吞吐量。
如果说经常产生冲突,引起锁等待,死锁等情况,反而降低了数据库的性能,这种情况下的话就比较适合悲观锁。

InnoDB行锁的实现方式
有以下三种通过索引项来加锁:

Record lock:对索引项加锁。
Gap lock:对索引项之间的”间隙“、第一条记录前的”间隙“或最后一条记录后的”间隙“加锁。
Next-key lock:前两种的组合,对记录及其前面的间隙加锁。
注:以上锁实现的特点是如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。

隔离级别(针对读操作)

MySQL里面的隔离级别可以分为4种:

未提交读(read uncommitted):指一个事务在操作的过程中修改的数据,在其它终端中进行查询能直接查询到修改的内容,
不需要事务进行提交就能读,也就是能脏读,读的一致性低,也可以重复读,幻读。

已提交读(read committed):事务1在表中做了更新操作,事务2开始读是原来的数据,事务1进行commit,事务2立即能查看到事务1更新的那一行数据。
这样的话就会引起幻读,如果事务1不断的更新数据,不断的commit,事务2每次查到的数据都是不一样的,所以可以使用for update解决,防止幻读。可能出现幻读,可以防止脏读。

可重复读(repeatable read):事务1做了更新操作并且commit,其它事务不能不能看到他所commit的值,其它事务必须commit或者退出当前线程,重启登录才能查看到事务1所更新的操作。
能防止”幻读“的现象,利用的是undo的快照技术+GAP+NextLock

可序列化(serializable):可串行化,可以防止死锁,最高事务级别,但是并发性能较差

注:在RC级别下,可以减轻GAP+NextLock锁的问题,但是会出现幻读的现象,所以在select后面用for update语句去解决幻读的问题。
在查询或者修改完该行后及时的进行commit,不然会阻塞其它事务对该行的操作,容易出现锁等待比较严重的现象。

例:

3306[youngboy]>select * from t1 where id=1 for update;
+------+-------+
| id   | sname |
+------+-------+
|    1 | ccc   |
+------+-------+
1 row in set (0.00 sec)

查看隔离级别命令:

3306[youngboy]>select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

MVCC多版本并发机制详解

MVCC并发实现的原理的组成:

undo日志
readview
隐式字段

MVCC中的当前读和快照读
当前读:
当前读指的是它读取的记录都是最新的版本,读取的时候需要保证其它事务不能对自己正在操作的这条记录进行修改,会对当前读取的记录进行加锁操作,像这些操作都是当前读的一种状态例:

select .... for update;
update;
insert;
delete;
select .... lock in share mode;

快照读:
直接进行的select这样的操作就是快照读,因为不知道自己读到的是不是最新的记录,快照读也不能是序列化级别的,如果是序列化级别的话,快照读也会变成当前读,也可以这么理解,快照读读到的并不一定是最新版本,也有可能是以前的版本。

数据库并发的三种状态
read-read:读对读的操作互不影响,没有并发问题,不需要并发控制。
read-write:对读有影响,对写的影响不大,主要问题在隔离性上,可能会造成脏读,不可重复读,脏读的现象。
write-write:存在更新丢失的问题,事务对同一行数据进行操作,前面记录的事务被最后提交修改记录的事务进行覆盖,导致前面修改记录的事务数据丢失。

MVCC主要是为了提高并发的读写性能,不用加锁就能让多个事务并发操作
对于删除操作,MySQL底层会记录好被删除的数据行的删除事务TXID
对于更新操作,MySQL底层会新增一行相同数据并记录好对应的创建事务TXID

readview(读视图):在事务快照读操作生产的读视图
在事务执行快照读的那一刻,数据库中会生成一个快照,维护并记录当前session下活跃的事务中所进行的操作
当事务开始的时候,开始第一个属于事务的操作时,就会被分配一个ID,这个ID就是TXID,每个ID都是递增的,
所以越是新的事务,ID越大。
隐式字段:
TRX_ID:每条记录对应的事务ID号
ROLL_POINTER:回滚指针,会指向上一个版本
DELETE_FLAG:显示删除状态,如果标记为true,则表示记录被删除,在查询的时候不显示数据。
能力有限,只能按我的理解画出个大概(图中是一行一行的是记录版本链)

MySQL——存储引擎、表空间、事务的理解及介绍_第8张图片
记录版本链比对规则:
trxid trxid>max_id 是由将来启动的事务生成的,一定不可见
min_id<=trxid<=max_id 有如下两种情况

如果row数据在数组中,表示事务还没提交,不可见,当前事务可见
如果row数据不在数组中,表示这个版本的事务已经进行了提交,可见

select1中
max_id=12
min_id=10
readview[10,11],12

select2中
max_id=12
min_id=11
readview[11],10,12
思路:

1、在上图中,transaction1进行操作并且commit,记录版本链中会记录这条记录为最新记录,然后把老的数据
放到undo日志中去,并且最新的这条记录中的roll_pointer会指向老的那条记录。
在上图版本链中可以看到,每一个DML操作都会被记录到版本链中

2、实际上readview中是由未提交事务ID的数组(min_id)和已创建的最大事务ID(mix_id)组成,所以下图中能看到,当select1执行selct操作时,
生成readview,并且生成id,id为[10,11],12

3、然后trx10又开始执行update语句,这样的话trx12中的update语句就被放到undo日志中去,因为已经不是事务版本链中最新的数据
之后select1再去查询这行数据的时候,还是原来的结果,但是trx10中已经进行了操作,为什么呢?
因为select1在执行第二个select语句查询的时候,第一个select语句已经生成了readview,所以还是会延用第一个select语句的readview,
前提是在repeatable-read的机制下
所以从下图中就可以知道,因为trx10在select1做第二次select语句的时候并未提交,是不可见的,所以看到的还是trx12提交的结果

4、select1去执行第三次select语句的时候为什么还是同样的结果呢?
现在已经是执行到trx11,sname已经为hdfeng5
原因跟上述一样,还是会延用第一次生成的readview[10,11],12,再去进行比对,发现11还是在数组内,所以还是只会显示hdfeng1的结果

5、再看select2的这种情况,为什么select2显示的是hdfeng3呢?
因为在select2执行第一条语句的时候trx10,trx12已经提交了事务,并且记录到了当前版本链中去了,所以按照这个规则生成readview的id为[11],10,12
去记录版本链一对比发现trx10<min_id是当前快照最新的row,所以输出的结果为hdfeng3

对于删除的情况可以认为是update的特殊情况,会去把记录版本链的数据复制一份出来,然后再将trxid修改成删除操作的trxid,同时在该条记录的record header里面的deleted flag标记上写上true,表示当前数据已经被删除,在查询的时候如果这条记录被查询到,表示这条记录已经被删除,不返回数据。
MySQL——存储引擎、表空间、事务的理解及介绍_第9张图片

你可能感兴趣的:(数据库层面)