外链提高网站权重,重庆景点分布图,wordpress语言包下载,淘货铺1.关于事务 理解和学习事务#xff0c;不能只站在程序猿的角度来理解事务#xff0c;而是要站在使用者#xff08;用户#xff09;的角度来理解事务。
比如支付宝转账#xff0c;A转了B100块前#xff0c;在程序猿的角度来看#xff0c;是两条update操作#xff0c;A …1.关于事务 理解和学习事务不能只站在程序猿的角度来理解事务而是要站在使用者用户的角度来理解事务。
比如支付宝转账A转了B100块前在程序猿的角度来看是两条update操作A -100块B 100块。但是站在使用者的角度这就是一条转账逻辑。 事务就是一组 DML 语句组成这些语句在逻辑上存在相关性这一组 DML 语句要么全部成功要么全部失败是一个整体。MySQL 提供一种机制保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。 事务就是要做的或所做的事情主要用于处理操作量大复杂度高的数据。 事务具有四个属性 原子性 一个事务 transaction 中的所有操作要么全部完成要么全部不完成不会结束在中 间某个环节。事务在执行过程中发生错误会被回滚 Rollback 到事务开始前的状态就像这个 事务从来没有执行过一样。 一致性 在事务开始之前和事务结束以后数据库的完整性没有被破坏。 这表示写入的资料必须完全符合所有的预设规则这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。 也就是说一个事务在开始之前和和事务结束之后它的结果是可预期的。比如A转给B100块如果成功了那么A -100块B 100块如果失败了那么AB都不变。不会出现A -100但是B没变的这种情况。 虽然说一致性是事务的一大属性但是MYSQL并没有从技术层面上来实现它一致性是通过原子性隔离性持久性来保证的。 隔离性 数据库允许多个并发事务同时对其数据进行读写和修改的能力隔离性可以防止多个事务 并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别包括读未提交 Read uncommitted 、读提交 read committed 、可重复读 repeatable read 和串行化 Serializable 持久性 事务处理结束后对数据的修改就是永久的即便系统故障也不会丢失。 上面四个属性可以简称为 ACID 。 原子性 A tomicity 或称不可分割性 一致性 C onsistency 隔离性 I solation 又称独立性 持久性 D urability 。 MYSQL中存在的大量的事务它们也是需要先被描述然后再组织的这样管理起来的。其实就是将一批SQL语句打包成一个事务对象将这个对象放在事务执行的事务列表里。 2.为什么要存在事务 事务被 MySQL 编写者设计出来 , 本质是为了当应用程序访问数据库的时候 , 事务能够简化我们的编程模型 ,不需要我们去考虑各种各样的潜在错误和并发问题. 可以想一下当我们使用事务时 , 要么提交 , 要么回滚 , 我们不会去考虑网络异常了, 服务器宕机了 , 同时更改一个数据怎么办对吧 ? 因此事务本质上是为了应用层服 务的 . 而不是伴随着数据库系统天生就有的 . 后面我们将MYSQL的一行信息称为一条记录。
3.了解事务的提交方式
在MYSQL的两大存储引擎Innodb和MYISAM只有Innodb支持事务。
事务的提交方式有两种
1.自动提交。
2.手动提交。
我们可以查看我们的MYSQL的提交方式
mysql show variables like autocommit; ON表示了它是自动提交的。
我们也可以设置它的提交方式
set autocommit0;show variables like autocommit;0是关闭1是打开。
4.准备工作
我们知道mysqld是一个网络服务那么我们可以查看
netstat -nltp可以看到端口号为3306就是我们的mysql网络服务。
另外可以查看我们mysql和mysqld文件所在的位置 首先我们先将隔离级别设置成为读未提交
set global transaction isolation level read uncommitted;但是设置好后需要登录mysql会话才能生效
此时我们输入
select tx_isolation; 此时的隔离级别就是读未提交
然后创建一张测试表
create table if not exists account(
id int primary key,
name varchar(50) not null default ,
blance decimal(10,2) not null default 0.0
)ENGINEInnoDB DEFAULT CHARSETUTF8; 5.事务的基本操作 这里我们看到事务是自动提交的。 有两种方式可以开启事务
start transaction;begin;
推荐 使用begin
设置保存点比如p1
savepoint p1;这是可以方便我们定向回滚的。比如
在插入一条数据后
insert into account values(1,张三,123.2);
我们看到另一个开启事务的会话可以直接看到更新的结果 然后我们回到插入数据前的保存点p1。
rollback to p1; 数据就消失了
也可以直接
rollback;直接回到最开始的地方。
如果想结束事务
commit;事务回滚只能在事务运行期间如果事务已经提交了就不能回滚了。
6.事务异常验证与产出的结论
当mysql遇到异常情况比如强制关闭会话事务会自动回滚。
但是如果事务已经commit了那么mysql的数据就不会受到影响已经持久化了。
另外关于事务的自动提交如果我们手动开启事务比如使用begin那么即便mysql的设置是自动提交手动开启的事务必须要手动提交。那么此外所有的SQL语句都是自动开启的事务因为又是手动提交所以每一条SQL都是一个事务并且执行完就提交了。
7.事务隔离性理论 数据库中为了保证事务执行过程中尽量不受干扰就有了一个重要特征隔离性 数据库中允许事务受不同程度的干扰就有了一种重要特征隔离级别 隔离级别由低到高 读未提交【 Read Uncommitted 】 在该隔离级别所有的事务都可以看到其他事务没有提交的 执行结果。实际生产中不可能使用这种隔离级别的但是相当于没有任何隔离性也会有很多 并发问题如脏读幻读不可重复读等我们上面为了做实验方便用的就是这个隔离性。 就比如说两个事务同时运行一方对表插入了数据还没有提交另一个事务直接就能查看到更新的结果。 读提交【 Read Committed 】 该隔离级别是大多数数据库的默认的隔离级别不是 MySQL 默 认的。它满足了隔离的简单定义 : 一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读即一个事务执行时如果多次 select 可能得到不同的结果。 比如两个事务同时运行其中一个事务对表插入了数据只有提交了另一个事务才能查看到更新的结果。 可重复读【 Repeatable Read 】 这是 MySQL 默认的隔离级别它确保同一个事务在执行 中多次读取操作数据时会看到同样的数据行。但是会有幻读问题。 还是这两个事务其中一个事务对表插入了数据但是就算它提交了另一个事务也看不到更新的结果只有当它也结束了才能看得到。 串行化【 Serializable 】 : 这是事务的最高隔离级别它通过强制事务排序使之不可能相互冲突 从而解决了幻读的问题。它在每个读的数据行上面加上共享锁。但是可能会导致超时和锁竞争 这种隔离级别太极端实际生产基本不使用 8.事务隔离级别的设置和查看。
1.查看隔离级别
有三种方式
-- 查看
mysql SELECT global.tx_isolation; --查看全局隔级别
-----------------------
| global.tx_isolation |
-----------------------
| REPEATABLE-READ |
-----------------------
1 row in set, 1 warning (0.00 sec)mysql SELECT session.tx_isolation; --查看会话(当前)全局隔级别
------------------------
| session.tx_isolation |
------------------------
| REPEATABLE-READ |
------------------------
1 row in set, 1 warning (0.00 sec)mysql SELECT tx_isolation; --是上一种的简写方式也是查看当前会话的
-----------------
| tx_isolation |
-----------------
| REPEATABLE-READ |
-----------------
1 row in set, 1 warning (0.00 sec) 2.设置隔离级别
中括号表示可以省略但是这里不建议
-- 设置当前会话 or 全局隔离级别语法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ
COMMITTED | REPEATABLE READ | SERIALIZABLE}
比如将当前会话的隔离级别改为 读-提交
set session transaction isolation level read committed; 9.事务隔离级别--读未提交
这种隔离级别几乎没有加锁虽然效率高但是问题也很多严重不推荐使用。
读-未提交会产生脏读现象
即一个事务在执行时可以看到另一个事务的更新但是没有提交的数据就是脏读。 10.事务隔离级别--读提交
在这个隔离级别下一个事务在执行时可以看到其他事务更新并且提交后的数据。
这样会带来一个后果
就是在同一个事务内部每次查询数据时根据时间段的不同可能会查询到不一样的结果我们把这个现象称为不可重复读。
事务的运行要保证其原子性不可重复读会破坏原子性。 11.事务隔离级别--可重复读
可重复读解决了两个并发运行的事务一方提交不会影响另一个未提交的事务。 多次查看发现终端A在对应事务中insert的数据在终端B的事务周期中也没有什么影响也符合可重复的特点。但是一般的数据库在可重复读情况的时候无法屏蔽其他事务insert的数据(为什么因为隔离性实现是对数据加锁完成的而insert待插入的数据因为并不存在那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的但是insert的数据在可重复读情况被读取出来导致多次查找时会多查找出来新的记录就如同产生了幻觉。这种现象叫做幻读 (phantom read)。很明显MySQL在RR级别的时候是解决了幻读问题的(解决的方式是用Next-Key锁(GAP行锁)解决的。 也就是说幻读问题是insert数据操作时伴随出来的问题属于不可重复度的情况的一种。
12.事务隔离级别--串行化
对所有的操作进行加锁也就是将所有的事务进行串行化这样就不会存在安全问题但是效率很低。 其中隔离级别越严格安全性越高但数据库的并发性能也就越低往往需要在两者之间找一个平 衡点。 MYSQL默认的隔离级别是可重复读并且解决了幻读的问题。
13.关于一致性的理解
一致性其实不是技术层面的概念而是使用层面的概念。
比如有一个事务在运行时操作到一半时出现了异常导致事务没有完成那么那些完成了一半的操作使数据库处于一种不正确的状态也就是不一致的状态。
所以 事务执行的结果必须使数据库从一个一致性状态变到另一个一致性状态。当数据库只包含事务 成功提交的结果时数据库处于一致性状态。如果系统运行发生中断某个事务尚未完成而被迫中 断而改未完成的事务对数据库所做的修改已被写入数据库此时数据库就处于一种不正确不一 致的状态。因此一致性是通过原子性来保证的。 其实一致性和用户的业务逻辑强相关一般 MySQL 提供技术支持但是一致性还是要用户业务逻辑做支撑比如要保证SQL是对的也就是一致性是由用户决定的。 而技术上通过AID保证C 。
14.MVCC机制
数据库的并发有三种场景 读 - 读 不存在任何问题也不需要并发控制 读 - 写 有线程安全问题可能会造成事务隔离性问题可能遇到脏读幻读不可重复读 写 - 写 有线程安全问题可能会存在更新丢失问题比如第一类更新丢失第二类更新丢失 这里重点介绍读写。
多版本并发控制 MVCC 是一种用来解决 读-写冲突 的无锁并发控制 。 MYSQL为事务分配单向增长的事务 ID 为每个修改保存一个版本版本与事务 ID 关联读操作只读该事务开始前的数据库的快照。 所以 MVCC 可以为数据库解决以下问题。 事务ID越小说明这个事务来得越早。可以根据事务ID的大小来判断事务到来的顺序。 mysqld可能会同时面临处理多个事务的情况事务也有自己的生命周期mysqld要将这些事务管理起来就需要先描述再组织所以在mysqld中一定是有一套对应的结构体/类对象事务也有自己的结构体。 理解MVCC需要的三个前提知识 3 个记录隐藏字段 undo 日志 Read View 3个记录隐藏字段
其实我们在建表的时候mysql会给我们加上3个隐藏字段。 DB_TRX_ID 6 byte 最近修改 ( 修改 / 插入 ) 事务 ID 记录创建这条记录 / 最后一次修改该记录的事务ID DB_ROLL_PTR : 7 byte 回滚指针指向这条记录的上一个版本简单理解成指向历史版本就 行这些数据一般在 undo log 中 DB_ROW_ID : 6 byte 隐含的自增 ID 隐藏主键如果数据表没有主键 InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引 。如果我们指明了主键那么就用我们指明的这个主键来建立索引。 补充实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除而是删除flag变了
所以我们删除一条记录其实并不是真的把它删除了而是改变了它的flag。这也是典型的用空间换时间的一种做法并且如果出现了误删操作还可以进行还原。 undo日志log
MYSQL将来是以服务进程的方式在内存中运行。我们之前所讲的所有机制索引事务隔离性日志等都是在内存中完成的即在 MySQL 内部的相关缓冲区中保存相关数据完成各种判断操作。然后在合适的时候将相关数据刷新到磁盘当中的。
所以可以简单理解为undolog就是mysql中的一段内存缓冲区。
模拟MVCC的过程 现在有一个事务 10( 仅仅为了好区分 ) 对 student 表中记录进行修改 (update) 将 name( 张三 ) 改成 name( 李四 ) 。 事务 10, 因为要修改所以要先给该记录加行锁。 修改前现将改行记录拷贝到 undo log 中所以 undo log 中就有了一行副本数据。 ( 原理就是写 时拷贝 ) 所以现在 MySQL 中有两行同样的记录。现在修改原始记录中的 name 改成 李四 。并且修改原始 记录的隐藏字段 DB_TRX_ID 为当前 事务 10 的 ID, 我们默认从 10 开始之后递增。而原始记录的回滚指针 DB_ROLL_PTR 列里面写入 undo log 中副本数据的地址从而指向副本记录既表示我的上一个版本就是它。 事务 10 提交释放锁。 图中李四是最新的记录。 现在又有一个事务 11 对 student 表中记录进行修改 (update) 将 age(28) 改成 age(38) 。 事务 11, 因为也要修改所以要先给该记录加行锁。 修改前现将改行记录拷贝到 undo log 中所以 undo log 中就又有了一行副本数据。此时新的 副本我们采用头插方式插入 undo log 。 现在修改原始记录中的 age 改成 38 。并且修改原始记录的隐藏字段 DB_TRX_ID 为当前 事务 11 的ID。而原始记录的回滚指针 DB_ROLL_PTR 列里面写入 undo log 中副本数据的地址从而指向副本记录既表示我的上一个版本就是它。 事务 11 提交释放锁。 这样我们就有了一个基于链表记录的历史版本链。所谓的回滚无非就是用历史数据覆盖当前数据。 上面的一个一个版本我们可以称之为一个一个的快照。 如果一个事务提交undo log就清理掉了自然也就不能回滚了。
一些思考 上面是以更新 upadte 主讲的 , 如果是 delete 呢一样的别忘了删数据不是清空而是设置 flag为删除即可。也可以形成版本。 如果是 insert 呢因为 insert 是插入也就是之前没有数据那么 insert 也就没有历史版本。但是 一般为了回滚操作insert 的数据也是要被放入 undo log 中此时对应的回滚操作就是delete如果当前事务 commit 了那么这个 undo log 的历史 insert 记录就可以被清空了。 总结一下也就是我们可以理解成 update 和 delete 可以形成版本链 insert 暂时不考虑。 那么 select 呢 首先 select 不会对数据做任何修改所以为 select 维护多版本没有意义。不过此时有个问题就是 select 读取是读取最新的版本呢还是读取历史版本 当前读读取最新的记录就是当前读。增删改都叫做当前读 select 也有可能当前读比如 select lock in share mode(共享锁 ), select for update 这个好理解我们后面不讨论 快照读读取历史版本 ( 一般而言 ) 就叫做快照读。 我们可以看到在多个事务同时删改查的时候都是当前读是要加锁的。那同时有 select 过来如果也要读取最新版( 当前读 ) 那么也就需要加锁这就是串行化。 但如果是快照读读取历史版本的话是不受加锁限制的。也就是可以并行执行换言之提高了效率即MVCC的意义所在。 所以是隔离级别决定了select是当前读还是快照读。 那为什么要有隔离级别呢 事务都是原子的。所以无论如何事务总有先有后。 但是经过上面的操作我们发现事务从 begin-CURD-commit 是有一个阶段的。也就是事务有执行前执行中执行后的阶段。但不管怎么启动多个事务总是有先有后的。 那么多个事务在执行中 CURD 操作是会交织在一起的。那么为了保证事务的 “ 有先有后 ” 是不是应该让不同的事务看到它该看到的内容这就是所谓的隔离性与隔离级别要解决的问题。 15.read view理论 Read View 就是事务进行 快照读 操作的时候生产的 读视图 (Read View) 在该事务执行的快照读的那一刻会生成数据库系统当前的一个快照记录并维护系统当前活跃事务的ID( 当每个事务开启时都会被分配一个ID, 这个 ID 是递增的所以最新的事务 ID 值越大 ) Read View 在 MySQL 源码中 , 就是一个类本质是用来进行可见性判断的。 即当我们某个事务执行快照读也就是select的操作的时候对该记录创建一个 Read View 读视图把它比作条件 , 用来判断当前事务能够看到哪个版本的数据既可能是当前最新的数据也有可能是该行记录的 undo log 里面的某个版本的数据。 read view结构体中重要的字段
class ReadView {
// 省略...
private:
/** 高水位大于等于这个ID的事务均不可见*/
trx_id_t m_low_limit_id;/** 低水位小于这个ID的事务均可见 */
trx_id_t m_up_limit_id;/** 创建该 Read View 的事务ID*/
trx_id_t m_creator_trx_id;/** 创建视图时的活跃事务id列表*/
ids_t m_ids;/** 配合purge标识该视图不需要小于m_low_limit_no的UNDO LOG
* 如果其他视图也不需要则可以删除小于m_low_limit_no的UNDO LOG*/
trx_id_t m_low_limit_no;/** 标记视图是否被关闭*/
bool m_closed;// 省略...
};// 重点是这四个字段
m_ids; //一张列表用来维护Read View生成时刻系统正活跃的事务ID
up_limit_id; //记录m_ids列表中事务ID最小的ID(没有写错)
low_limit_id; //ReadView生成时刻系统尚未分配的下一个事务ID也就是目前已出现过的事务ID的
最大值1(也没有写错)
creator_trx_id //创建该ReadView的事务ID 我们在实际读取数据版本链的时候是能读取到每一个版本对应的事务 ID 的即当前记录的 DB_TRX_ID 。 那么我们现在手里面有的东西就有当前快照读的 ReadView 和 版本链中的某一个记录的 DB_TRX_ID 。 所以现在的问题就是当前快照读应不应该读到当前版本记录。 read view它生成对象后只初始化一次。此时里面的字段m_ids表示的是当这个read view起来的时候此时活跃的事务ID。那么有两种情况是可以select到的
1.当前创建该read view时的事务ID等于遍历到的记录的事务IDDB_TRX_ID说明是select自己那么当然可以查看。
2.当初跟我们同时运行的事务它们的事务ID可能比我们大但是它们可能比我们先提交了既然已经提交了我们也可以查看。
那么不能查看的也有两种情况
1.正在运行的事务我们不能查看在m_ids中的。
2.当前的事务IDlow_limit_id。说明是快照之后才提交的事务也不应该看到。
read view是从版本链中遍历的也就是undo log中遍历。
我们可以看一下在mysql的源码中对每次遍历的判断 可见与我们刚刚分析的差不多如果id小于最低水位说明这些事务是在这个read view之前就提交了如果id 当前事务id那么也返回true。
并且要注意这个id是指我们从版本链中遍历到的事务的ID DB_TRX_ID。
如果id大于最高水位说明这个事务是在read view之后创建的肯定不能查看所以返回false。
另外如果m_ids是空的说明此时就只有一个事务当然能查看所以也返回true。
总结
read view是事务可见性的一个类但是注意它不是事务一创建出来就跟着有的read view对象而是当这个事务已经存在首次进行快照读的时候mysql才会形成read view对象。
16.read view实验 现在有这样一条记录 然后有四个事务 根据事务id可以判断它们的创建的先后顺序纵向代表它们的执行状态并且可以看到事务4是先提交了的。
事务4的修改操作是
修改name张三为name李四
注意low_limit_id代表的是系统分配的最大的事务id 再 1。
那么此时事务2在进行快照读的时候形成的read view对象的字段为
//事务2的 Read View
m_ids; // 1,3
up_limit_id; // 1
low_limit_id; // 4 1 5原因ReadView生成时刻系统尚未分配的下一个事务ID
creator_trx_id // 2
那么此时的版本链 因为只有事务4进行了修改并且提交那么当事务2的read view创建时读取该记录时就会拿这个事务的DB_TRX_ID开始进行比较
//事务2的 Read View
m_ids; // 1,3
up_limit_id; // 1
low_limit_id; // 4 1 5原因ReadView生成时刻系统尚未分配的下一个事务ID
creator_trx_id // 2
//事务4提交的记录对应的事务ID
DB_TRX_ID4
//比较步骤
DB_TRX_ID4 up_limit_id1 ? 不小于下一步
DB_TRX_ID4 low_limit_id(5) ? 不大于下一步
m_ids.contains(DB_TRX_ID) ? 不包含说明事务4不在当前的活跃事务中。//结论
故事务4的更改应该看到。
所以事务2能读到的最新数据记录是事务4所提交的版本而事务4提交的版本也是全局角度上最新的版本
到这里我们也发现这不就是之前说的RC读提交隔离级别吗
17.RC和RR的本质区别
我们在select语句后面加上 lock in share mode表示加共享锁的方式读取是当前读不加就是快照读。
在RR级别下可重复读有两个事务同时运行 图中我们发现事务B在事务A提交前就进行了快照读那么也就形成了read view在事务A提交后事务B再使用快照读依旧不能读到更新后的结果。
还有另一种情况
两种情况的唯一不同就是后者事务B是在事务A提交后才进行的快照读此时就能读到更新的结果。这就是RR隔离级别。
结论 事务中快照读的结果是非常依赖该事务首次出现快照读的地方即某个事务中首次出现快照读决 定该事务后续快照读结果的能力 delete 同样如此 所以read view的形成的时机不同会影响事务的可见性
所以RC与RR的本质区别 正是 Read View 生成时机的不同从而造成 RC,RR 级别下快照读的结果的不同 在 RR 级别下的某个事务的对某条记录的第一次快照读会创建一个快照及 Read View, 将当前系统活 跃的其他事务记录起来此后就不会变了 此后在调用快照读的时候还是使用的是同一个 Read View 所以只要当前事务在其他事务提交更 新之前使用过快照读那么之后的快照读使用的都是同一个 Read View 所以对之后的修改不可 见 即 RR 级别下快照读生成 Read View 时 Read View 会记录此时所有其他活动事务的快照这些事 务的修改对于当前事务都是不可见的。而早于 Read View 创建的事务所做的修改均是可见 而在 RC 级别下的事务中每次快照读都会新生成一个快照和 Read View, 这就是我们在 RC 级别下的事务中可以看到别的事务提交的更新的原因 总之在 RC 隔离级别下是每个快照读都会生成并获取最新的 Read View 而在 RR 隔离级别下则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个 Read View 。 正是 RC 每次快照读都会形成 Read View 所以 RC 才会有不可重复读问题。 顺便说下读未提交它直接都不形成read view所以什么都能看到。