MySQL事物与存储引擎

137 阅读20分钟

事物的概念

事务是一种机制、一个操作序列,包含了一组数据库操作命令,并且把所有的命令作为一个 整体一起向系统提交或撤销操作请求,即这一组数据库命令要么都执行,要么都不执行。

事务是一个不可分割的工作逻辑单元,在数据库系统上执行并发操作时,事务是最小的控制单元。

事务适用于多用户同时操作的数据库系统的场景,如银行、保险公司及证券交易系统等等。

事务通过事务的整体性以保证数据的一致性。

事务能够提高在向表中更新和插入信息期间的可靠性。

总结:所谓事物,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位

数据库是一个公司的核心资产,关系型数据库是可靠的,就是因为有事务的存在

事务的ACID特点

ACID,是指在可靠数据库管理系统(DBMS) 中,事务(transaction)应该具有的四个特性:原子性(Atomicity) 、一致性(Consistency )、隔离性(Isolation) 、持久性(Durability) 。这是可靠数据库所应具备的几个特性

原子性(保证事务的整体性)

原子性:事务是一个不可再分割的工作单位,事务中的操作要么都发生,要么都不发生

  • 事务是一个完整的操作,事务的各元素是不可分的。
  • 事务中的所有元素必须作为一个整体提交或回滚。
  • 如果事务中的任何元素失败,则整个事务将失败。

案例说明:

A给B转帐100元钱的时候只执行了扣款语句,就提交了,此时如果突然断电,A账号已经发生了扣款,B账号却没收到加款,在生活中就会引起纠纷。这种情况就需要事务的原子性来保证事务要么都执行,要么就都不执行。

一致性(保证数据的完整性)

一致性:指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏

  • 当事务完成时,数据必须处于一致状态 。
  • 在事务开始前,数据库中存储的数据处于一致状态。
  • 在正在进行的事务中,数据可能处于不一致的状态。
  • 当事务成功完成时,数据必须再次回到E知的一致状态。

案例说明:

对银行转帐事务,不管事务成功还是失败,应该保证事务结束后表中A和B的存款总额跟事务执行前一致。例如,A的账户有1000元,B的账户有1000元,现在A转账200元给B,不管事务成功还是失败,转账前后A和B的存款总额都应该是2000元。

隔离性

隔离性:指在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间

  • 对数据进行修改的所有并发事务是彼此隔离的,表明事务必须是独立的,它不应以任何方式依赖于或影响其他事务。
  • 修改数据的事务可在另一个使用相同数据的事务开始之前访问这些数据,或者在另一一个使用相同数据的事务结束之后访问这些数据。
  • 也就是说并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的

事务之间的相互影响

当多个客户端并发地访问同一表时,可能出现下面的一致性问题:

(1)脏读:当一个事务正在访问数据,并对数据进行修改,而这中修改还没有提交到数据库中,这时,另外一个事务也访问这个数据然后使用了这个数据

Snipaste_2022-10-21_13-47-01.png

(2)不可重复读: 指在一事务内,多次读同一数据。 在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一一个事务内两次读到的数据是不一样的,因此称为是不可重复读。( 即不能读到相同的数据内容)

Snipaste_2022-10-21_13-47-17.png

(3)幻读: 一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,另一个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。 那么,操作前一个 事务的用户会发现表中还有没有修改的数据行,就好象发生了幻觉一样

Snipaste_2022-10-21_13-47-27.png

(4)丢失更新: 两个事务同时读取同一条记录,A先修改记录,B也修改记录(B不知道A修改过),B提交数据后B的修改结果覆盖了A的修改结果

Snipaste_2022-10-21_13-47-37.png

可以通过添加事务的隔离级别解决上述的一致性问题

事务隔离级别

事务的隔离级别决定了事务之间可见的级别。

MySQL事务支持如下四种隔离,用以控制事务所做的修改,并将修改通告至其它并发的事务:

(1)未提交读(Read Uncommitted(RU)):

允许脏读,即允许一个事务可以看到其他事务未提交的修改。

(2)提交读(Read Committed (RC)):

允许一个事务只能看到其他事务已经提交的修改,未提交的修改是不可见的。防止脏读。

(3)可重复读(Repeatable Read(RR)):——mysql默认的隔离级别

确保如果在一个事务中执行两次相同的SELECT语句,都能得到相同的结果,不管其他事务是否提交这些修改。可以防止脏读和不可重复读。

(4)串行读(serializable):——相当于锁表

完全串行化的读,将一个事务与其他事务完全地隔离。每次读都需要获得表级共享锁,读写相互都会阻塞。可以防止脏读,不可重复读取和幻读,(事务串行化)会降低数据库的执行效率

隔离级别脏读取不可重复读幻想读
读未提交可能可能可能
读已提交不可能可能可能
可重复读不可能不可能对InnoDB不可能
串行化不可能不可能不可能

事务隔离级别的作用范围分为两种:

全局级:对所有的会话有效

会话级:只对当前会话有效

查看事务的隔离级别

查询全局事务隔离级别
variables 指变量, global variables 指全局变量
​
show global variables like '%isolation%';方法一
select @@global.tx_isolation;方法二
​
注释:
like:表示模糊查询
'%isolation%':表示包含isolation字符串
'%isolation':以isolation字符串开头
'isolation%':以isolation字符串结尾
复制代码

Snipaste_2022-10-25_09-34-50.png

查询会话事务隔离级别:
session variables 指会话变量
​
show session variables like '%isolation%'; #方法一
select @@session.tx_isolation;  #方法二
select @@tx_isolation;  ##方法三
复制代码

Snipaste_2022-10-25_09-37-48.png

设置事务的隔离级别

设置全局事务隔离级别
全局级别设置之后,当前会话需要退出重新进入才会生效
set global transaction isolation level 隔离级别;   ##永久生效
​
示例:
set global transaction isolation level read committed; ##将全局事务隔离级别设置为提交读(RC)
复制代码

Snipaste_2022-10-25_09-44-30.png

设置全局事务隔离级别
会话事务隔离级别只对当前连接有效,退出连接后生效。在其他终端连接无效
再次连接后会恢复为全局事务的隔离级别
​
set session transaction isolation level 隔离级别;
​
示例:
set session transaction isolation level read committed;  ##将会话事务隔离级别设置为提交读(RC)
复制代码

Snipaste_2022-10-25_09-48-13.png Snipaste_2022-10-25_09-56-29.png ### 持久性(Durability)

持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚

  • 指不管系统是否发生故障,事务处理的结果都是永久的
  • 一旦事务被提交,事务的效果会被永久地保留在数据库中

总结:在事务管理中,原子性是基础,隔离性是手段,一致性是目的,持久性是结果

事务控制语句

begin或START TRANSACTION:显示的开启一个服务

commit 或 COMMITWORK: 提交事务,并使已对数据库进行的所有修改变为永久性的。

rollback 或 ROLLBACK WORK: 回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。

savepoint S1: 使用SAVEPOINT 允许在事务中创建一个回滚点,一个事务中可以有多个SAVEPOINT;“S1”代表回滚点名称。

rollback to [SAVEPOINT] S1: 把事务回滚到标记点

测试bengin和commit(开始事务和提交事务)

先准备一个数据表:

use bbc;
create table account (id int,name char(10),money double);
​
insert into account values(1,'lucy',1000);
insert into account values(2,'lisi',1000);
复制代码

Snipaste_2022-10-25_10-02-07.png Snipaste_2022-10-25_10-03-37.png

示例1:

begin开启事务,修改数据未提交,退出后重新连接查看,数据未改变

Snipaste_2022-10-25_10-11-58.png

示例2:

begin开启事务,修改数据commit提交,重新连接后查看数据发生改变

Snipaste_2022-10-25_10-13-19.png

示例3:

当前全局事务隔离级别和会话事务隔离级别都为RU(未提交读) ,允许脏读,事务A在提交前,事务B可以看到事务A未提交前的修改

Snipaste_2022-10-25_10-17-39.png Snipaste_2022-10-25_10-24-41.png Snipaste_2022-10-25_10-27-30.png

示例4:

全局事务隔离级别和会话事务隔离级别都为RC(提交读) ,防止脏读,但不可重复读

事务A在提交前的修改,事务B是看不到

事务A在提交后,事务B可以看到修改的数据

Snipaste_2022-10-25_10-44-08.png Snipaste_2022-10-25_10-47-24.png Snipaste_2022-10-25_10-52-39.png

示例5:

全局事务隔离级别和会话事务隔离级别都为RR(重复读) ,可以防止脏读和不可重复读

Snipaste_2022-10-25_10-55-24.png Snipaste_2022-10-25_10-58-50.png Snipaste_2022-10-25_11-01-24.png

测试事务回滚rollback

rollback:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改

Snipaste_2022-10-25_11-05-23.png

创建回滚点并进行回滚

Snipaste_2022-10-25_11-08-31.png Snipaste_2022-10-25_11-09-31.png

使用set设置控制事务

在mysql中执行单独的命令会立即生效,是因为Mysql默认开启自动提交

使用set设置控制事务:

set autocommit=0;   ##禁止自动提交(仅针对当前会话)
set sutocommit=1;   ##开启自动提交(仅针对当前会话),默认值为1set global autocommit=0;  ##禁止自动提交(针对全局事务)
set global sutocommit=1;  ##开启自动提交(针对全局事务)
​
show variables like 'autocommit'; ##查看当前会话的autocommit值
show global variables like 'autocommit'; ##查看全局事务的autocommit值
复制代码

如果没有开启自动提交,当前会话连接的mysql的所有操作都会当成一个事务直到你输入rollback;或commit; 当前事务才算结束,当前事务结束前新的myql连接是无法读取到任何当前会话的操作结果

如果开启了自动提交,mysql会将每个sql语句当成一个事务,然后自动的commit

当然无论开启与否,begin; commit | rollback; 都是独立的事务

开启自动提交

Snipaste_2022-10-25_11-16-26.png Snipaste_2022-10-25_11-16-56.png

总结:

事务一种机制,包含了一组操作命令,会作为一个不可分割的整体,要么都执行,要么都不执行

ACID特点

原子性(基础) 保证事务事个不可分割的工作单位,事务中的操作要么都执行,要么都不执行

一致性(目的) 保证事务开始前与事务结束后数据库数据的完整性

隔离性(手段) 保证在多个并发事务操作同一个数据时,结果事务都有各自的数据空间,相互互不干扰 4个隔离级别(未提交读RU 提交读RC 重复读RR 串行读)

持久性(结果) 保证事务被提交后修改的结果是永久保存的,不会回滚的

事务的控制语句

begin; 显示的开启一个事务

commit; 提交并结束事务

rollback; 回滚并结束事务

savepoint 回滚点; 在事务中创建回滚点

rollback to 回滚点; 回滚到某个回滚点的状态,但事务没有结束

set命令 设置自动提交

set autocommit=0|1; 0代表关闭自动提交事务,1代表开启 show variables like 'autocommit';

开启自动提交事务,mysql会把每个sql语句当成一个事务然后自动commit提交 关闭自动提交事务,把在当前会话中的所有操作都当成一个事务直到执行commit|rollback

MySQL数据库之存储引擎

存储引擎的概念

什么是存储疫情

MySQL中的数据用各种不下同的技术存储在文件中,每一种技术都使用不同的存储机制、索引技巧、锁定水平并最终提供不同的功能和能力,这些不同的技术以及配套的功能在MySQL中称为存储引擎。

存储引擎是MySQL将数据存储在文件系统中的存储方式或者存储格式。

存储引擎是MySQL数据库中的组件,负责执行实际的数据I/O操作。

MySQL系统中,存储引擎处于文件系统之上,在数据保存到数据文件之前会传输到存储引擎,之后按照各个存储引擎的存储格式进行存储。

MySQL常用的存储引擎

  • MyISAM
  • InnoDB

注意:一个表只能使用一个存储引擎,一个库中不同的表可以使用不同的存储引擎

MyISAM存储引擎

MyISAM介绍

  1. MyISAM不支持事务,也不支持外键约束,中支持全文索引,数据文件和索引文件时分开保存的
  2. 访问速度快,对事物完整性没有要求
  3. MyISAM适合查询、插入为主的应用
  4. MyISAM在磁盘上存储成三个文件,文件名和表名都相同,但是扩展名分别为:
  • .frm文件存储表结构的定义
  • 数据文件的扩展名为.MYD(MYData)
  • 索引文件的扩展名为.MYI(MYIndex)

MyISAM的特点

表级锁定形式,数据在更新时锁定整个表

数据库在读写过程中相互阻塞:

  • 会在数据写入的过程阻塞用户数据的读取
  • 也会在数据读取的过程中阻塞用户的数据写入

数据单独写入或者读取,速度过程较快且占用资源相对较少

MyISAM表支持3种不同的存储格式

静态表(固定长度)

静态表是默认的存储格式。静态表中的字段都是非可变字段,这样每个记录都是固定长度的,这种存储方式的优点是存储非常迅速,容易缓存,出现故障容易恢复;缺点是占用的空间通常比动态表多

动态表

动态表包含可变字段,记录不是固定长度的,这样存储的优点是占用空间较少,但是频繁的更新、删除记录会产生碎片,需要定期执行OPTIMIZE TABLE语句或myisamchk-r命令来改善性能,并且出现故障的时候恢复相对比较困难(因为会产生磁盘碎片,而且存储空间不是连续的)

压缩表

压缩表由 myisamchk 工具创建,占据非常小的空间,因为每条记录都是被单独压缩的,所以只有非常小的访问开支。(压缩的过程中会占用CPU性能)

MyISAM使用的生产场景举例

公司业务不需要事务的支持

单方面读取或写入数据比较多的业务

MylSAM存储引擎数据读写都比较频繁场景不适合(因为读写是互相阻塞的)

使用读写并发访问相对较低的业务

数据修改相对较少的业务

对数据业务一致性要求不是非常高的业务

服务器硬件资源相对比较差(MyISAM占用资源相对少)

InnoDB存储引擎

InnoDB介绍

支持事务,支持4个事务隔离级别

MySQL从5.5.5版本开始,默认的存储引擎为InnoDB

读写阻塞与事务隔离级别相关

能非常高效的缓存索引和数据

表与主键以簇的方式存储 BTREE

支持分区、表空间,类似oracle数据库

支持外键约束,5.5前不支持全文索引,5.5后支持全文索引

对硬件资源要求还是比较高的场合

行级锁定,但是全表扫描仍然会是表级锁定,如

  • update table set a=1 where user like '%zhang%';

InnoDB 中不保存表的行数,如 select count(*) from table; 时,InnoDB 需要扫描一遍整个表来计算有多少行,但是 MyISAM 只要简单的读出保存好的行数即可。需要注意的是,当 count(*)语句包含 where 条件时 MyISAM 也需要扫描整个表

对于自增长的字段,InnoDB 中必须包含只有该字段的索引,但是在 MyISAM 表中可以和其他字段一起建立组合索引

InnoDB使用表级锁定的场景:

全表扫描的时候,比如 where 语句中使用 like 做模糊查询的时候

select count(*) 统计全表的记录行数的时候

使用没有索引的字段操作的时候(InnoDB行锁是通过给索引项实现的,如果没有索引那就会全表扫描)

InnoDB的特点

业务需要事务的支持。

行级锁定对高并发有很好的适应能力,但需确保查询是通过索引来完成。

业务数据更新较为频繁的场景。

  • 如:论坛,微博等。

业务数据一致性要求较高。

  • 如:银行业务。

硬件设备内存较大,利用InnoDB较好的缓存能力来提高内存利用率,减少磁盘IO的压力

清空整个表时,InnoDB 是一行一行的删除,效率非常慢。MyISAM 则会重建表

MyISAM和InnoDB之间的区别

MyISAM:不支持事务和外键约束,占用资源较小,访问速度快,表级锁定,支持全文索引,适用于不需要事务处理,单独写入或查询的应用场景。
存储格式: 表名.frm(表结构文件)  表名.MYD(数据文件)   表名.MYI(索引文件)
​
InnoDB:支持事务处理、外键约束,缓存能力较好,支持行级锁定,读写并发能力较好,5.5版本后支持全文索引,适用于一致性要求高、数据更新频繁的应用场景。
表名.frm(表结构文件)  表名.idb(表数据文件/索引文件)  db.opt(表属性文件)
复制代码

企业选择存储引擎依据

1、需要考虑每个存储引擎提供了哪些不同的核心功能及应用场景

2、支持的字段和数据类型

  • 所有引擎都支持通用的数据类型
  • 但不是所有的引擎都支持其他的字段类型,如二进制对象

3、锁定类型:不同的存储引擎支持不同级别的锁定

  • 表锁定:MyISAM支持
  • 行锁定:InnoDB支持

查看和修改存储引擎

查看存储引擎

查看系统支持的存储引擎

show engines;
​
输出包含以下列:
#Engine:存储引擎的名称。
#Support:YES表示引擎受支持且处于活动状态,NO表示不支持,DEFAULT表示默认存储引擎。DISABLED表示支持引擎但已将其禁用。#Comment:存储引擎的简要说明。
#Transactions:存储引擎是否支持事务。
#XA:存储引擎是否支持XA事务。
#Savepoints:存储引擎是否支持回滚点(标记点)。
复制代码

Snipaste_2022-10-25_11-20-29.png Snipaste_2022-10-25_11-24-29.png

查看数据表使用的存储引擎

show table status from 库名 where name='表名'\G
复制代码

Snipaste_2022-10-25_11-26-55.png

use 库名;
show create table 表名;
复制代码

Snipaste_2022-10-25_11-28-24.png

修改存储引擎

alter table修改

use 库名;
alter table 表名 engine=存储引擎名称;
​
示例:
use kgc;
alter table class engine=MyISAM; #将class表的存储引擎修改为MyISAM#注意:因为MyISAM不支持外键约束,如果数据表设置了外键,则无法修改为MyISAM。
复制代码

Snipaste_2022-10-25_11-29-49.png

修改配置文件,指定默认存储引擎

注意:此方法只对修改配置文件并重启mysql服务之后新创建的表有效,已经存在的表不会有变更。

创建数据表时如果没有指定存储引擎,则会使用默认存储引擎

vim /etc/my.cnf
​
[mysqld]
default-storage-engine=InnoDB   #修改这一行,指定默认存储引擎为InnoDB
​
systemctl restart mysqld   #重启服务
复制代码

Snipaste_2022-10-25_11-32-00.png Snipaste_2022-10-25_11-32-32.png

create table创建表时指定存储引擎

use 库名;
create table 表名(字段1 数据类型,...)  engine=存储引擎名称;
​
示例:
use bbc;
create table test(id int,name char(10)) engine=MyISAM;  ##指定存储引擎为MyISAM
复制代码

Snipaste_2022-10-25_11-43-40.png

行级锁定与标记锁定

先创建一个表

create table t1 (id int,name char(4),age int);
​
insert into t1 values (1,'aaa',22);
insert into t1 values (2,'bbb',23);
insert into t1 values (3,'ccc',24);
insert into t1 values (4,'aaa',25);
insert into t1 values (5,'bbb',26);
复制代码

Snipaste_2022-10-25_11-45-39.png Snipaste_2022-10-25_11-50-35.png

delete from t1 where id=1;
因为id字段是主键,Innodb对于主键使用了索引,删除过程中会直接锁住整行记录。行级锁定。
复制代码

Snipaste_2022-10-25_11-57-31.png

delete from t1 where name='aaa';
因为name字段是普通索引,会先锁住索引的两行(因为aaa有两行),接着会锁住相应主键对应的记录。行级锁定。
复制代码

Snipaste_2022-10-25_12-28-48.png

delete from t1 where age=23;
​
因为age字段没有索引,会使用全表扫描过滤,这时表上的各个记录都将加上锁。表级锁定
复制代码

Snipaste_2022-10-25_12-29-42.png

死锁

表锁会导致表中数据的读写相互阻塞

行锁如果使用不当会导致死锁(死锁一般是事务相互等待对方释放资源,最后形成环路造成的)

session1session2
begin;begin;
delete from t1 where id=5;#事务结束前,id=5的行会被锁定
select * from t1 where id=1 for update; #加排他锁,模拟并发情况,锁定id=1的行
delete from t1 where id=1; #死锁产生
update t1 set name='abc' where id=5; #死锁产生。因为会话1中id=5的行还在删除过程中,该行已被锁定
rollback; #回滚,结束事务。id=5的行被解锁
update t1 set name='abc' where id=5; #成功更新数据

for update: 可以为数据库中的行上一个排它锁。当一个事务的操作未完成时,其他事务可以读取该行数据,但是不能写入、更新或删除

操作

会话1:

Snipaste_2022-10-25_12-34-37.png

会话2

Snipaste_2022-10-25_12-36-08.png

会话1

Snipaste_2022-10-25_12-38-13.png

会话2

Snipaste_2022-10-25_12-41-24.png

会话1

Snipaste_2022-10-25_12-42-20.png

会话2

Snipaste_2022-10-25_12-43-12.png

如何避免死锁?

使用更合理的业务逻辑,以固定的顺序访问表和行数据 大事务拆小,大事务更容易出现死锁,如果业务允许,将大事务拆成多个小事务执行 在同一个事务中,尽可能做一次锁定所需的所有资源,减少死锁概率 降低隔离级别。如果业务允许,可以降低隔离级别,比如把RR调整成RC,这样可以避免很多造成死锁的因素 为表字段添加合理的索引。因为不使用会进行表级锁定,死锁的概率就会提高