MySQL--事务

事务

1.事务理解

1.1 事务的概念

事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永远更新面向数据的资源,一个逻共凑的单元要成为事务,必须满足所谓的ACID属性,即:
1.原子性(A)

  • 对于修改的数据要么全部执行,要么全部不执行

2.隔离性(C)

  • 在所有的操作没有执行之前,其他会话不能够看到中间改变的过程

3.一致性(I)

  • 事务发生前后,根据数据的规则,总额应该匹配

4.持久性

  • 事务一旦提交,其结果就是永久的,系统崩溃也不会影响
    事务例子:
     start transaction; //开启事务  
     insert into count (prefix,count,historycount) values('dy1','0','2');  
     select * from count
     commit;

1.2 事务的实现

Mysql 在进行事务处理的时候使用的是日志现行的方式来保证事务可快速和持久运行的,也就是先写日志,在写数据库。一个事务开始时,会记录该事务的一个LSN日志序列号;当执行事务时,会往InnoDB_log_buffer 日志缓冲区里插入事务日志(redo log);当事务提交时,会将日志缓冲区里的事务日志刷入磁盘。这个动作是由 innodb_flush_log_at_trx_commit 这个参数控制的。

  • 发出commit动作时,是否刷日志由变量 innodb_flush_log_at_trx_commit控制
  • 每秒刷一次。这个刷新日志的频率是由变量 innodb_flush_log_at_timeout值决定,默认是1秒,这个值与commit无关。
  • 当log buffer 中已经使用的内存超过一半时????
  • 当有 checkpoint时,checkpoint在一定程度上代表了刷新磁盘时日志所处的LS位置

可以通过命令show engine innodb status \G 查看

Log sequence number 8619676075 (表示当前的LSN日志序列号)

Log flushed up to 8619676075 (表示刷新到事物日志的LSN日志序列号)

Last checkpoint at 8619676075 (表示刷新到磁盘的LSN日志序列号)

除了记录事务日志意外,数据库还会记录一定量的撤销日志(undo log), undo与redo(事务日志)正好相反,在对数据进行修改时,由于某种原因失败了,或者人为执行了rollback回滚语句,就可以利用这些撤销日志将数据回滚到修改之前的样子。redo日志保存在ib_logfile0/1/2里,而undo日志保存在ibdata1里,在MySQL5.6里还可以把undo日志单拆分出去。

1.3 分布式事务(初步了解)

资源管理器:管理事务的提交和回滚,向事务提供资源
事务管理器:与资源管理通讯,协调完成事务的处理

用于执行分布式事务的过程的俩个阶段;

  • 第一阶段:所有分支被预备。他们被事务管理告知要准备提交,每个分支资源管理器记录分支的行动并指示认为的可行性。
  • 第二阶段:事务管理器告知资源管理器是否要提交或者回滚。如果预备分支时,所有的指示他们能够提交,那么所有的分支被告知提交。如果有一个分支出错,则全部回滚。特殊情况下,只有一个分支的时候,第二阶段则被省略。

分布式事务主要作用在于确保事务的一致性和完整性。他利用分布式的计算环境,将多个事务性的活动合并成一个事务单元,这些事务组合在一起构成原子操作,这些事务的活动要么一起执行并提交事务,要么回滚所有的操作,从而保证了多个活动之间的一致性和完整性。

2.事务与日志

`
– 查看事务日志 :
show engine innodb status\G;

– 查看日志文件设置状态

show variables like ‘innodb_%’;
`

2.1 事务特性理解

事务日志文件
innodb_log_files_in_group:DB中设置几组事务日志,默认是2;
innodb_log_group_home_dir:事务日志存放目录,不设置,ib_logfile0…存在在数据文件目录下
Innodb存储引擎可将所有数据存放于ibdata*的共享表空间,也可将每张表存放于独立的.ibd文件的独立表空间\

注意:在MySQL中对于数据来说,最为重要的是日志文件
redo log => ib_logfile0
undo log => ibdata\

2.1.1原子性:

事务的一系列操作要么全部执行,要么不执行
原子性与回滚日志:
想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行回滚,而在 MySQL 中,恢复机制是通过回滚日志(undo log)实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。

注意:系统发生崩溃、数据库进程直接被杀死后,当用户再次启动数据库进程时,还能够立刻通过查询回滚日志将之前未完成的事务进行回滚,这也就需要回滚日志必须先于数据持久化到磁盘上,是我们需要先写日志后写数据库的主要原因。

在日志文件中:在事务中使用的每一条 INSERT 都对应了一条 DELETE,每一条 UPDATE 也都对应一条相反的 UPDATE 语句。\

2.1.2 持久性:

事务被提交,数据一定会被写入到数据库中并持久存储起来,通常来说当事务已经被提交之后,就无法再次回滚了。
持久性性与重做日志:
与原子性一样,事务的持久性也是通过日志来实现的,MySQL 使用重做日志(redo log)实现事务的持久性,重做日志由两部分组成,一是内存中的重做日志缓冲区,因为重做日志缓冲区在内存中,所以它是易失的,另一个就是在磁盘上的重做日志文件,它是持久的\

2.1.3 回滚日志与重做日志:

回滚日志(undo log)和重做日志(redo log);在数据库系统中,事务的原子性和持久性是由事务日志(transaction log)保证的,在实现时也就是上面提到的两种日志,前者用于对事务的影响进行撤销,后者在错误处理时对已经提交的事务进行重做,它们能保证两点:
1. 发生错误或者需要回滚的事务能够成功回滚(原子性);
2. 在事务提交后,数据没来得及写会磁盘就宕机时,在下次重新启动后能够成功恢复数据(持久性);
在数据库中,这两种日志经常都是一起工作的,我们可以将它们整体看做一条事务日志,其中包含了事务的 ID、修改的行元素以及修改前后的值。

2.1.4 事务的流程

Mysql 的checkpoint 参考

www.cnblogs.com/lintong/p/4381578....
checkpoint,即检查点。在undolog中写入检查点,表示在checkpoint前的事务都已经完成commit或者rollback了,也就是检查点前面的事务已经不存在数据一致性的问题了。

Innodb的事务日志是指Redo log,简称Log,保存在日志文件ib_logfile里面(去mysql数据目录下看下)。Innodb还有另外一个日志Undo log,但Undo log是存放在共享表空间里面的(ibdata*文件,存储的是check point日志序列号)。

Innodb的一条事务日志共经历4个阶段
1)创建阶段:事务创建一条日志;
2)日志刷新:日志写入到磁盘的日志文件;
3)数据刷新:日志对应的脏数据写入磁盘的数据文件
4) 写入CKP:日志被当作Checkpoint写入到日志文件;\

innodb_flush_log_at_trx_commit 参数解析

//查看日志文件设置状态
show variables like 'innodb_%';

//修改
set @@global.innodb_flush_log_at_trx_commit = 0; -- 012

show variables like 'innodb_flush_log_at_trx_commit';
  • 性能对比:0>2>1
    数据安全对比:1最好,所以默认选1

3.锁机制

MySQL的不同存储引擎,支持不同的锁定机制

3.1 锁类型。

  1. MyISAM 和 Memory 存储引擎使用的是表级锁,BDB 引擎使用的是页级锁,也支持表级锁。由于 BDB 引擎基本已经成为历史,因此就不再介绍了。
  2. InnoDB 存储引擎既支持行级锁,也支持表级锁,默认情况下使用行级锁。
  3. 所谓表级锁,它直接锁住的是一个表,开销小,加锁快,不会出现死锁的情况,锁定粒度大,发生锁冲突的概率更高,并发度最低。
  4. 所谓行级锁,它直接锁住的是一条记录,开销大,加锁慢,发生锁冲突的概率较低,并发度很高。
  5. 所谓页级锁,它是锁住的一个页面,在 InnoDB 中一个页面为16KB,它的开销介于表级锁和行级锁中间,也可能会出现死锁,锁定粒度也介于表级锁和行级锁中间,并发度也介于表级锁和行级锁中间。
  6. 仅仅从锁的角度来说,表级锁更加适合于以查询为主的应用,只有少量按照索引条件更新数据的应用
  7. 行级锁更适合大量按照索引条件并发更新少量不同的数据,同时还有并发查询的应用

3.2 innoDB行级锁

InnoDB有两种类型的行级锁,两种内部使用的意向锁;

  • 共享锁(S):允许一个事务读一行数据时,阻止其他的事务读取相同数据的排他锁(update,insert,delete)。
  • 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据的共享锁和排他锁。
  • 意向共享锁(IS):事务打算给数据行加行共享锁。事务在给一个数据行加共享锁前必须先取得该表的IS锁。
  • 意向排他锁(IX):事务打算给数据行加行排他锁。事务在给一个数据行加排他锁前必须先取得该表的IX锁。
  • 悲观锁(抽象,不真实存在的锁):指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了
  • 乐观锁(抽象,不真实存在的锁):观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了

4种锁的共存逻辑关系表

锁模式 共享锁(S) 排他锁(X) 意向共享锁(IS) 意向排他锁(IX)
共享锁(S) 兼容 冲突 兼容 冲突
排他锁(X) 冲突 冲突 冲突 冲突
意向共享锁(IS) 兼容 冲突 兼容 兼容
意向排他锁(IX) 冲突 冲突 兼容 兼容

意向锁是InnoDB存储引擎自动加的,对于普通select语句,InnoDB不会加任何锁,对于insert,update,delete语句,InnoDB会自动改涉及的数据加排他锁,InnoDB以通过以下语句显示添加的共享锁和排他锁

共享锁语句

select * from table_name lock in share mode;

排他锁语句

select * from table_name for update;

关于意向锁;

意向锁是表级锁,其设计目的主要是为了在一个事务中揭示下一行将要被请求锁的类型。InnoDB 中的两个表锁:

意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的 IS 锁。如果需要对记录 A 加共享锁,那么此时 InnoDB 会先找到这张表,对该表加意向共享锁之后,再对记录 A 添加共享锁

意向排他锁(IX):类似上面,表示事务准备给数据行加入排他锁,也就是说事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。如果需要对记录 A 加排他锁,那么此时 InnoDB 会先找到这张表,对该表加意向排他锁之后,再对记录 A 添加排他锁
意向锁是 InnoDB 自动加的,不需要用户干预

共享锁和排他锁,系统在特定的条件下会自动添加共享锁或者排他锁,也可以手动添加共享锁或者排他锁。
意向共享锁和意向排他锁都是系统自动添加和自动释放的,整个过程无需人工干预

共享锁和排他锁都是锁的行记录,意向共享锁和意向排他锁锁定的是表

3.3 innodb行锁与表锁的转变与注意

InnoDB 行级锁是通过给索引项加锁来实现的,InnoDB 行级锁只有通过 索引条件检索数据,才能使用行级锁;否则,使用的是表级锁。

在不通过索引(主键)条件查询的时候,InnoDB是表锁而不是行锁。

通常begin-end用于定义一组语句块

注意:如果查询的条件没有带索引,那么行锁则会转为表锁 ,即使表中字段有主键;所以在查询的时候建议使用索引字段查询

3.4 innodb间隙锁

可以理解为是对于一定范围内的数据进行锁定,如果说这个区间没有这条数据的话也是会锁住的;主要是解决幻读的问题,如果没有添加间隙锁,如果其他事物中添加id在1到100之间的某条记录,此时会发生幻读;另一方面,视为了满足其恢复和赋值的需求。

MySQL官网间隙锁的使用 dev.mysql.com/doc/refman/5.7/en/in...

MySQL官网间隙锁属性解释 dev.mysql.com/doc/refman/5.7/en/in...

默认情况下,innodb_locks_unsafe_for_binlog是0(禁用),这意味着启用了间隙锁定:InnoDB使用下一个键锁进行搜索和索引扫描。若要启用该变量,请将其设置为1。这将导致禁用间隙锁定:InnoDB只使用索引记录锁进行搜索和索引扫描。

3.5 MySQL对于死锁的处理方式

官方定义如下:两个事务都持有对方需要的锁,并且在等待对方释放,并且双方都不会释放自己的锁

MySQL有两种死锁处理方式:

  • 等待,直到超时(innodb_lock_wait_timeout=50s)。
  • 发起死锁检测,主动回滚一条事务,让其他事务继续执行(innodb_deadlock_detect=on)

死锁检测:
死锁检测的原理是构建一个以事务为顶点、锁为边的有向图,判断有向图是否存在环,存在即有死锁。
检测到死锁之后,选择插入更新或者删除的行数最少的事务回滚,基于 INFORMATION_SCHEMA.INNODB_TRX 表中的 trx_weight 字段来判断。

3.6 对于锁与事务的建议

3.6.1 对于锁的建议

收集死锁信息:
利用命令 SHOW ENGINE INNODB STATUS查看死锁原因。
调试阶段开启 innodb_print_all_deadlocks,收集所有死锁日志。

减少死锁:
使用事务,不使用 lock tables 。
保证没有长事务。
操作完之后立即提交事务,特别是在交互式命令行中。
如果在用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE),尝试降低隔离级别。
修改多个表或者多个行的时候,将修改的顺序保持一致。
创建索引,可以使创建的锁更少。
最好不要用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE)。
如果上述都无法解决问题,那么尝试使用 lock tables t1, t2, t3 锁多张表

3.6.2 对于事务的建议

innodb存储引擎由于实现了行几所,颗粒更小,实现更复杂。但是innodb行锁在并发性能上远远要高于表锁页锁。在使用方面可以尽量做到以下几点;

  1. 控制事务大小,减少锁定的资源量和锁定时间长度。
  2. 所有的数据检索都通过索引来完成,从而避免因为无法通过索引加锁而升级为表锁。
  3. 减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的数据。
  4. 在业务条件允许下,尽量使用较低隔离级别的事务隔离。减少隔离级别带来的附加成本。
  5. 合理使用索引,让innodb在索引上面加锁的时候更加准确。
  6. 在应用中尽可能做到访问的顺序执行
  7. 如果容易死锁,就可以考虑使用表锁来减少死锁的概率
本作品采用《CC 协议》,转载必须注明作者和本文链接
Luson
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!