Innodb核心特性——事务
一、什么是事务?
主要针对DML语句(update,delete,insert)
一组数据操作执行步骤,这些步骤被视为一个工作单元:
1)用于对多个语句进行分组
2)可以在多个客户机并发访问同一个表中的数据时使用
所有步骤都成功或都失败
1)如果所有步骤正常,则执行
2)如果步骤出现错误或不完整,则取消
二、事务通俗的理解
伴随着“交易”出现的数据库概念。
我们理解的“交易”是什么?
1)物与物的交换(古代)
2)货币现金与实物的交换(现代1)
3)虚拟货币与实物的交换(现代2)
4)虚拟货币与虚拟实物交换(现代3)
数据库中的“交易”是什么?
1)事务又是如何保证“交易”的“和谐”?
2)ACID
# 10.0.0.51的第一个窗口 # 1.创建一个转账表 mysql> use test mysql> create table jiaoyi; mysql> insert into jiaoyi values('msy',1000), mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 1000 | | hjm | 1000 | +------+-------+ # 2.开始事务 mysql> begin; # 3.更改数据(测试手动更改 其实都是自动的) mysql> update jiaoyi set money=1500 where name='msy'; mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 1500 | | hjm | 1000 | +------+-------+ mysql> update jiaoyi set money=500 where name='hjm'; mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 1500 | | hjm | 500 | +------+-------+ # 4.在10.0.0.51的另一个窗口查看 mysql> select * from test.jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 1000 | | hjm | 1000 | +------+-------+ mysql> select * from test.jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 1000 | | hjm | 1000 | +------+-------+ 并没有发生变化 # 5.回刚才begin的第一个窗口 mysql> commit; # 6.在10.0.0.51的另一个窗口查看 mysql> select * from test.jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 1500 | | hjm | 500 | +------+-------+ 数据变化
mysql> update jiaoyi set money=2500 where name='msy'; mysql> update jiaoyi set money=-500 where name='hjm'; mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 2500 | | hjm | -500 | +------+-------+ #错误行为 正常情况下是不允许操作的 money会加上无符号 现在只能手动演示 哈哈 #如果不成功就会回滚 mysql> rollback; mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 1500 | | hjm | 500 | +------+-------+ `注意:当你执行完commit之后 是不能回滚到的
三、事务ACID特性
A:原子性,将一个事务视为一个单元,要么全部成功(commit),要么全部失败(rollback)
C:一致性,在事务执行前后,数据保持一致
I:隔离性(隔离级别),事务和事务之间没有任何影响,完全隔离
D:持久性,事务执行结束后,commit的那一刻,写入磁盘,永久保存。
只要commit之后 就没办法回滚了 相当于执行commit就是把数据写入磁盘了 不执行commit相当于在内存中写
四、事务流程举例

五、事务控制语句
START TRANSACTION(或 BEGIN) #显式开始一个新事务 SAVEPOINT #分配事务过程中的一个位置,以供将来引用 像打游戏存档 COMMIT #永久记录当前事务所做的更改 ROLLBACK #取消当前事务所做的更改 ROLLBACK TO SAVEPOINT #取消在 savepoint 之后执行的更改 RELEASE SAVEPOINT #删除 savepoint 标识符 SET AUTOCOMMIT #为当前连接禁用或启用默认 autocommit 模式
mysql> update jiaoyi set money=3000 where name='msy'; mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 3000 | | hjm | 1500 | +------+-------+ #存档 mysql> savepoint diyici; # 不小心执行错了 如果roollback 上一笔hjm的操作也没了 mysql> update jiaoyi set money=2000 where name='hjm'; mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 3000 | | hjm | 2000 | +------+-------+ #这个时候就可以用回滚到存档 mysql> rollback to savepoint diyici; mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 3000 | | hjm | 1500 | +------+-------+
注意:要关闭自动提交 不然每次触发就会自动提交
mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ #临时修改 退出会话就没了 mysql> set autocommit=0; #永久关闭 [root@db01 ~]# vim /etc/my.cnf [mysqld] autocommit=0

六、事务的生命周期
一个成功事务的生命周期
begin;
sql1
sql2
sql3
...
commit;
一个失败事务的生命周期
begin;
sql1
sql2
sql3
...
rollback;
七、事务的隐式提交
在MySQL5.6,不需要手动执行begin;只要执行DML语句,会自动开启一个事务
update、insert、delete
神奇的事-1
mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 2000 | | hjm | 1500 | +------+-------+ mysql> insert into jiaoyi values('jjj',0); mysql> rollback; mysql> select * from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 2000 | | hjm | 1500 | | jjj | 0 | +------+-------+ #按理说在没有执行commit之前 是不会提交的 但是做一次回滚回到提交之后的数据发现有刚插入的 #这就是触发DML语句自动提交
mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+ [root@db01 data]# vim /etc/my.cnf autocommit=0 [root@db01 data]# /etc/init.d/mysqld restart Shutting down MySQL.... SUCCESS! Starting MySQL.. SUCCESS'
神奇的事-2
# 10.0.0.51窗口1执行但不提交 mysql> select *from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 2000 | | hjm | 1500 | | jjj | 500 | +------+-------+ mysql> update jiaoyi set money=600 where name='jjj'; mysql> select *from jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 2000 | | hjm | 1500 | | jjj | 600 | +------+-------+ # 10.0.0.51窗口2查看数据 无变化 mysql> select * from test.jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 2000 | | hjm | 1500 | | jjj | 500 | +------+-------+ # 10.0.0.51窗口1提交 mysql> commit; #10.0.0.51窗口2查看数据 mysql> select * from test.jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 2000 | | hjm | 1500 | | jjj | 500 | +------+-------+ 为什么还是无变化??? 这不应该的啊!!!!! 退出当前会话重新进之后就变化了 mysql> \q [root@db01 ~]# mysql -S /tmp/mysql.sock mysql> select * from test.jiaoyi; +------+-------+ | name | money | +------+-------+ | msy | 2000 | | hjm | 1500 | | jjj | 600 | +------+-------+
面试题
开启了几个事务 完成了几个事务?
开启三个 完成三个
#这是一个 begin; update create database #这是第二个 update select insert #这是第三个 begin; update commit;
1.在事务执行期间,如果执行begin或者 start transaction会自动提交上一个事务,并且开启新的事务
2.在事务执行期间,如果运行了DDL和DCL都会自动提交事务 就是非DML和DQL
3.在事务执行期间,执行lock table 或者 unlock table 也会自动提交事务
4.导出数据 load data infile ,会自动提交事务
5.在事务执行期间,执行select for update,会自动提交事务
6.在autocommit=1的时候,会自动提交事务
八、事务日志
1.redu
1.Redo是什么?
redo,顾名思义“重做日志”,是事务日志的一种。
2.作用是什么?
在事务ACID过程中,实现的是“D”持久化的作用。
3.redo buffer
redo的buffer:数据页的变化信息+数据页当时的LSN号 LSN:日志序列号 磁盘数据页、内存数据页、redo buffer、redolog
4.redo的刷新策略
commit; 刷新当前事务的redo buffer到磁盘 还会顺便将一部分redo buffer中没有提交的事务日志也刷新到磁盘
5.REDO工作过程
#执行步骤 update t1 set num=2 where num=1;
1)首先将t1表中num=1的行所在数据页加载到内存中buffer page
2)MySQL实例在内存中将num=1的数据页改成num=2
3)num=1变成num=2的变化过程会记录到,redo内存区域,也就是redo buffer page中
#提交事务执行步骤 commit;
1)当敲下commit命令的瞬间,MySQL会将redo buffer page写入磁盘区域redo log
2)当写入成功之后,commit返回ok
特性:WAL(Write Ahead Log)日志优先写
REDO:记录的是,内存数据页的变化过程



2.undo
1.undo是什么?
undo,顾名思义“回滚日志”,是事务日志的一种。
2.作用是什么?
在事务ACID过程中,实现的是“A”原子性的作用。
当然CI的特性也和undo有关
在rolback时,将数据恢复到修改之前的状态
在CSR实现的是,将redo当中记录的未提交的时候进行回滚.
undo提供快照技术,保存事务修改之前的数据状态.保证了MVCC,隔离性,mysqldump的热备

3.MySQL CSR——前滚
MySQL : 在启动时,必须保证redo日志文件和数据文件LSN必须一致, 如果不一致就会触发CSR,最终保证一致 情况一: 我们做了一个事务,begin;update;commit. 1.在begin ,会立即分配一个TXID=tx_01. 2.update时,会将需要修改的数据页(dp_01,LSN=101),加载到data buffer中 3.DBWR线程,会进行dp_01数据页修改更新,并更新LSN=102 4.LOGBWR日志写线程,会将dp_01数据页的变化+LSN+TXID存储到redobuffer 5. 执行commit时,LGWR日志写线程会将redobuffer信息写入redolog日志文件中,基于WAL原则, 在日志完全写入磁盘后,commit命令才执行成功,(会将此日志打上commit标记) 6.假如此时宕机,内存脏页没有来得及写入磁盘,内存数据全部丢失 7.MySQL再次重启时,必须要redolog和磁盘数据页的LSN是一致的.但是,此时dp_01,TXID=tx_01磁盘是LSN=101,dp_01,TXID=tx_01,redolog中LSN=102 MySQL此时无法正常启动,MySQL触发CSR.在内存追平LSN号,触发ckpt,将内存数据页更新到磁盘,从而保证磁盘数据页和redolog LSN一值.这时MySQL正长启动 以上的工作过程,我们把它称之为基于REDO的"前滚操作"
3.redo和undo的存储位置
在MySQL5.6版本中undo是在ibdata文件中,在MySQL5.7版本会独立出来。
5.6也可以 只不过要重新编译
#redo位置 [root@db01 data]# ll /application/mysql/data/ -rw-rw---- 1 mysql mysql 50331648 Aug 15 06:34 ib_logfile0 -rw-rw---- 1 mysql mysql 50331648 Mar 6 2017 ib_logfile1 #undo位置 (共享表空间) [root@db01 data]# ll /application/mysql/data/ -rw-rw---- 1 mysql mysql 79691776 Aug 15 06:34 ibdata1 -rw-rw---- 1 mysql mysql 79691776 Aug 15 06:34 ibdata2
4.一些概念
redo log ---> 重做日志 ib_logfile0~1 50M , 轮询使用 redo log buffer ---> redo内存区域 ibd ---> 存储 数据行和索引 buffer pool --->缓冲区池,数据和索引的缓冲 LSN : 日志序列号 磁盘数据页,redo文件,buffer pool,redo buffer MySQL 每次数据库启动,都会比较磁盘数据页和redolog的LSN,必须要求两者LSN一致数据库才能正常启动 WAL : write ahead log 日志优先写的方式实现持久化 脏页: 内存脏页,内存中发生了修改,没写入到磁盘之前,我们把内存页称之为脏页. CKPT:Checkpoint,检查点,就是将脏页刷写到磁盘的动作 TXID: 事务号,InnoDB会为每一个事务生成一个事务号,伴随着整个事务.
九、事务中的锁
锁一般配合隔离级别使用
1.什么是“锁”?
“锁”顾名思义就是锁定的意思。
2.“锁”的作用是什么?
在事务ACID特性过程中,“锁”和“隔离级别”一起来实现“I”隔离性的作用。
3.MySQL中的锁
排他锁 :保证在多事务操作时,数据的一致性。(修改操作会被阻塞)
共享锁 :保证在多事务工作期间,数据查询 时不会被阻塞。
乐观锁 :谁先提交,以谁为准
悲观锁 :谁先执行事务,并阻塞其他事务查询
4.锁的粒度
innodb:行级锁
myisam:表级锁
5.锁的实践
1) 同一列的情况下:如果两个事务修改同一行 一定是谁先提交以谁为准
# 1.创建库与表 对数据进行修改 但不提交 mysql> create table suo(id int); mysql> insert into suo values(1),(2),(3); mysql> select *from suo; +------+ | id | +------+ | 1 | | 2 | | 3 | +------+ mysql> update test.suo set id=2 where id=1; Query OK, 1 row affected (0.00 sec) # 2.另个窗口也对数据做修改 但是失败 因为这个数据正在被修改 被锁住了 mysql> select * from suo; +------+ | id | +------+ | 1 | | 2 | | 3 | +------+ mysql> update test.suo set id=3 where id=1; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction # 3.第一个窗口提交 mysql > commit; # 4.第二个窗口修改成功 但是数据没有变化 因为第一个修改好了就没有id=1了 数据变了 所以结果是第一个提交后的 mysql> update test.suo set id=3 where id=1; Query OK, 1 row affected (0.00 sec) mysql> select * from suo; +------+ | id | +------+ | 2 | | 2 | | 3 | +------+
2) 想行级锁 必须要有主键
# 1.修改数据 先不提交 mysql> update suo set id=10 where id=3; Query OK, 1 row affected (0.00 sec) # 2.另一个窗口不同行做修改 #因为行级锁同行不能同时修改 但是不同行就可以 mysql> update test.suo set id=20 where id=2; 卡住 # 3.第一个窗口提交 mysql> commit; mysql> select * from suo; +------+ | id | +------+ | 2 | | 2 | | 10 | +------+ #第二个窗口执行 还是不行
# 1.创建一个新表 带主键 mysql> create table suo_new(id int not null primary key auto_increment); mysql> insert into suo_new values(1),(2),(3); mysql> commit; mysql> select * from suo_new; +----+ | id | +----+ | 1 | | 2 | | 3 | +----+ # 2.第一个窗口修改一行数据 先不提交 mysql> update suo_new set id=10 where id=1; # 3.另一个窗口修改另一行数据 先不提交 但是不卡了 执行成功了 mysql> update test.suo_new set id=20 where id=2; # 4.提交以后 mysql> select * from suo_new; +----+ | id | +----+ | 3 | | 10 | | 20 | +----+
3) 不同列的情况下:就不是谁先提交看谁
mysql> alter table test.suo_new add name varchar(10); mysql> update test.suo_new set name='qls' where id=3; mysql> select * from suo_new; +----+------+ | id | name | +----+------+ | 3 | qls | | 10 | NULL | | 20 | NULL | +----+------+ # 另一个窗口 mysql> update test.suo_new set name='zhangsan' where id=10; mysql> select * from test.suo_new; +----+----------+ | id | name | +----+----------+ | 3 | NULL | | 10 | zhangsan | | 20 | NULL | +----+----------+ 两个事务互不影响
# 1.第一个窗口修改 mysql> update test.suo_new set id=100 where name='qls'; # 2.另一个窗口修改 不成功 mysql> update test.suo_new set id=200 where name='qls'; 卡住 # 3.第一个窗口提交 mysql> commit; # 4.第二个窗口就执行成功了 然后commit 最后结果是 mysql> select * from test.suo_new; +-----+----------+ | id | name | +-----+----------+ | 10 | zhangsan | | 20 | NULL | | 200 | qls | +-----+----------+
十、MVCC多版本并发控制
1)只阻塞修改类操作(排他锁),不阻塞查询类操作(共享锁)
2)乐观锁的机制(谁先提交谁为准)
十一、隔离级别
1.查看当前隔离级别
mysql> show variables like '%iso%'; +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | tx_isolation | REPEATABLE-READ | +---------------+-----------------+
2.read-commit(RC)
提交读
[root@db01 ~]# vim /etc/my.cnf [mysqld] transaction_isolation=read-commit # 1.第一个窗口修改数据 mysql> select * from test.suo_new; +-----+--------+ | id | name | +-----+--------+ | 20 | zhang3 | | 30 | li4 | | 200 | qls | +-----+--------+ mysql> begin; mysql> update test.suo_new set name='wang5' where id=200; mysql> commit; # 2.提交时另一个窗口查看 mysql> select * from test.suo_new; +-----+--------+ | id | name | +-----+--------+ | 20 | zhang3 | | 30 | li4 | | 200 | wang5 | +-----+--------+
3.read-uncommit(RU)
未提交读
安全性低 容易出现幻读 脏读
[root@db01 ~]# vim /etc/my.cnf [mysqld] transaction_isolation=read-uncommit # 1.第一个窗口修改但不提交 mysql> begin; mysql> select * from test.suo_new; +-----+--------+ | id | name | +-----+--------+ | 20 | zhang3 | | 30 | li4 | | 200 | qls | +-----+--------+ mysql> update test.suo_new set name='wang5' where id=200; # 2.未提交时另一个窗口查看 mysql> select * from test.suo_new; +-----+--------+ | id | name | +-----+--------+ | 20 | zhang3 | | 30 | li4 | | 200 | wang5 | +-----+--------+
4.REPEATABLE-READ(RR)
重复读
[root@db01 ~]# vim /etc/my.cnf [mysqld] transaction_isolation=REPEATABLE-READ # 1.第一个窗口修改并提交 mysql> select * from test.suo_new; +-----+--------+ | id | name | +-----+--------+ | 20 | zhang3 | | 30 | li4 | | 200 | wang5 | +-----+--------+ mysql> begin; mysql> update test.suo_new set name='zhao4' where id=30; mysql> commit; # 2.第二个窗口必须先退出一下会话 再重新进入 才可查看
5.串行化
[root@db01 ~]# vim /etc/my.cnf [mysqld] transaction_isolation=SERIALIZABLE #阻塞所有操作,查询的时候,不能修改,修改的时候,不能查询 除非提交 其他都阻塞
十二、架构改造项目
项目背景: 2台 IBM X3650 32G ,原来主从关系,2年多没有主从了,"小问题"不断(锁,宕机后的安全) MySQL 5.1.77 默认存储引擎 MyISAM 数据量: 60G左右 ,每周全备,没有开二进制日志 架构方案: 1. 升级数据库版本到5.7.20 2. 更新所有业务表的存储引擎为InnoDB 3. 重新设计备份策略为热备份,每天全备,并备份日志 4. 重新构建主从 结果: 1.性能 2.安全方面 3.快速故障处理
来源:https://www.cnblogs.com/MENGSHIYU/p/12013086.html