10、Innodb 核心特性 *****
10.1 事务
10.1.1 事务的ACID特性
Atomic (原子性),用undo来实现原子性。
所有语句作为一个单元全部成功执行或全部取消。不能出现中间状态
Consistent (一致性),通过 redo 和 undo 来保证。
如果数据库在事务开始时处于一致状态,则在执行该事务间将保留一致状态。
Isolated (隔离性),通过锁的机制,隔离级别来保证。
事务之间不相互影响
Durable (持久性),主要通过WAL(Write-Ahead Logging,日志优先写,关键点就是先写日志,再写数据),偏向于把日志保存下来,而不是把数据写入磁盘。
事务成功完成后,所做的所有更改都会准确地记录在数据库中。所做的更改不会丢失。
10.1.2、事务的生命周期(事务控制语句)
(1)如何开启事务(两条命令)
begin;
start transaction;
(2)标准的事务语句
DML:insert update delete
mysql> use world;
mysql> update city set countrycode='CHN' where id=1;
(3)事务的结束
回滚:rollback; 只能回滚未提交的语句。
提交:commit;
10.1.3、自动提交机制 (autocommit) #有大量的交易型数据的数据库,要关掉autocommit。
select @@autocommit; 默认开启
update 语句,会自动加一个begin 自动提交。
在线修改参数:
set autocommit=0; #会话级别,及时生效,影响不了其他会话。
set global autocommit=0; #全局级别,影响所有会话。断开窗口,重连,生效。
永久修改,需要重启mysql服务。
vim /etc/my.cnf
autocommit=0
10.1.4、隐式提交
在同一个会话窗口,在一个事务还没有提交前,不能再加begin
begin
a
b
begin
在一个事务还没有提交前,set 命令也会,触发隐式提交。
导致提交的非事务语句:
DDL语句:(alter create drop)
DCL语句:(grant revoke set password)
锁定语句:(lock tables unlock tables)
导致隐式提交的语句示例:
truncate table
load data infile
select for update
10.2、事务的ACID如何保证?
10.2.1、一些概念名词
redo log:重做日志,(记录内存数据页的数据变化(增删改))
/data(就是你的数据目录) 目录,ib_logfile0 ib_logfile1 ,轮询使用,默认为50M
redo log buffer:redo 的内存区域,功能:用来做redo的缓存和缓冲的。
ibd:存储数据行和索引
data buffer pool:缓冲区池,做(ibd)数据和索引的缓冲。
LSN:日志序列号,可以理解为一个版本号,专用于事务的一个版本号。
ibd,redo log, data buffer pool,redo buffer 都有LSN
++++++++++++++++++++++++++++++++++++++++++
Mysql 每次数据库启动,都会比较磁盘数据页和redolog的LSN,要求两者LSN必须一致,数据库才能正常启动。
WAL:(一种写数据的机制,持久化)
write ahead log 日志优先写的方式,实现持久化,日志是优先于数据写入磁盘的。
脏页:
内存脏页,内存中发生了修改,没写入到磁盘前,我们把内存页,称之为脏页。
CKPT:checkpoint,检查点,就是将脏页刷写到磁盘上的动作。
TXID:事务ID号,Innodb 会为每一个事务生成一个事务号,伴随着整个事务。
10.2.2 事务日志-----redo 重做日志
作用:主要功能 ,保证D(持久化),A(原子性)、C(一致性)也有一定作用。
记录了内存数据页的变化
记录了什么?
情况一:
我们做了一个事务,begin; update; commit
1、在bgein,会立即分配一个事务ID号 TXID=tx_01
2、update 时,会将需要修改的数据页(dp_01,LSN=1001),加载到data buffer 中
3、DBWR数据写线程,会进行dp_01 数据页修改更新,并更新LSN=102
4、LGWR日志写线程,会将dp_01数据页的变化+LSN+TXID存储到redo buffer
5、执行commit时,LGWR日志写线程会将 redo buffer 信息写入 redo log日志文件中,基于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的 "前滚操作"
redo 核心功能:****
事务发生变化时,内存数据页发生变化,mysql 的redo buffer就会记录这些变化,这就是redo信息。
(1) 记录了内存数据页的变化
(2) 提供快速的持久化功能(WAL)
(3) CSR(故障自动恢复)过程中实现前滚的操作(磁盘数据页和redo日志LSN一致)
redo 日志文件: iblogfile0 iblogfile1
redo buffer :数据页的变化信息+数据页当时的LSN号
redo的刷写策略:
commit;
刷新当前事务的redo buffer 到磁盘
还会顺便将一部分redo buffer 中未提交的事务日志也刷新到磁盘。
Mysql 在启动时,必须保证redo日志文件和数据文件中的LSN一致,不一致就会触发CSR,最终保证一致。
10.2.3 undo
作用:在ACID特性中,主要保证A(原子性)的特性,同时对C(一致性) 和 I (隔离性) 也有一定的功效。
(1) 记录了数据修改之前的状态
(2) rollback将内存的数据修改,恢复到修改之前
(3) 在CSR中,实现未提交数据的回滚操作。
(4) 实现一致性快照,配合隔离级别,保证MVCC(多版本并发控制),实现,读和写的操作不会互相阻塞。
10.2.4 锁
实现了事务之间的隔离功能,Innodb中实现的是行级锁,
A、B同时修改第100行数据,只有A在commit后,才能操作,B一直在等,50秒后会自动释放。
row-level lock 行级锁定,也叫记录锁,默认超时50秒,不会限制100以外的
next lock -----> innodb实现锁的过程中,不单纯只锁记录本身,还会顺带锁定一个范围,或者锁定一个间隙,从当前行往后的行。
gap间隙锁
10.2.5 隔离级别,默认RR级别。
RU:读未提交,会有脏读(读内存脏页)、幻读、不可重复读,一般不允许出现。
RC:读已提交,会有幻读、不可重复读,可以防止脏读。***** 在大部分互联网企业中是可以容忍的。
RR:可重复读,防止不可重复读,有可能会出现 幻读,可重复读由undo的快照提供以及MVCC,可以通过GAP(间隙锁)+Next Lock(下键锁) 来防止幻读。*****
SR:可串行化,可以防止死锁,但是并发事务性能较差。
补充:在RC级别下,可以减轻GAP+NextLock锁的问题,但是会出现幻读现象,一般在为了读一致性,会在正常select后添加for update 语句,但是,请记住执行完一定要commit 否则容易出现锁等待比较严重。
更改隔离级别
select @@tx_isolation; ##查看隔离模式
vim /etc/my.cnf #修改隔离模式,transaction_isolation=read-uncommitted
transaction_isolation=read-committed
幻读最可能出现在数据插入时,(一边在更新,并且未完成,一边在插入)
避免幻读:
(1) 首先隔离级别必须 是RR
(2) 更新的条件列应该要有索引,有了索引之后,会自动生成两把索引锁GAP(间隙锁)、Next Lock(下键锁)
transaction_isolation=repeatable-read
MVCC------->使用undo 快照实现,mvcc,会在每一个会话开启时,都会生成一个一致性的快照。
++++++
RU隔离模式会出现脏读,RC隔离模式会出现不可重复读,也会出现幻读,RR隔离模式有可能会出现 幻读现象,可以通过GAP(间隙锁)和Next-lock(下键锁)进行避免。
面试题:什么是不可重复读,幻读。
所谓的不可重复读,首先 不可重复读是在那个级别下,是在RC模式下,select @@tx_isolation; 查看隔离级别,准备了一张表 use test; create table rc(id int,name varchar(20)); 打开两个会话窗口
左边窗口开启一个事务,
begin
insert into rc values(1,'zs');
这时右边窗口是看不到数据的,因为左边窗口还没有提交,如果左边窗口还没有提交,右边窗口就能看到数据了,那就是脏读了。
这时,我们在左边窗口commit ,右边窗口select * from rc; 就能看到数据了。
但是,这时我们在左边窗口中,再插入一条记录时,insert into rc values(2,'ls'); 并提交,然后,在右边窗口中查询,依然可以查询到我们刚才最新插入的数据,并不是先前的数据。这就是不可重复读,在同一个会话窗口中,每次读取的数据不一样。
幻读:在RC隔离模式下也有
还是已刚才的表为例,再插入几条数据
insert into rc values(3,'s3');
insert into rc values(4,'c4');
比如,有一个需求,把id大于2的数据,name 全修改成lishi
右边发起了一个update 语句 update rc set name='lishi' where id>2;
左边窗口又插入一条id >2的数据,insert into rc values(5,'w5'); 并且已提交,发现两边互相不阻塞
这时,再在右边窗口,查看数据,就会发现,还有一条数据没有修改。这就是幻读
++++++++++++++++++++++++++
11、Innodb 核心参数的介绍
#存储引擎设置默认设置
default_storage_engine=innodb
#表空间模式:(默认为1,表示,独立表空间,0,共享表空间)
innodb_file_per_table=1
#共享表空间的文件个数和大小的设置,一般在初始化之前设置好了就可以了。
innodb_data_file_path=ibdata1:512M:ibdata2:512M:autoextend
#"双一"标准的其中一个,******* innodb_flush_log_at_trx_commit
从内存中的redo-buffter 把日志刷写到磁盘上的一个控制参数,控制的是redo往磁盘刷写的一个策略,默认是 1 。一般情况下不用改。
innodb_flush_log_at_trx_commit=1 ### 支持 0 1 2,追求性能(数据不重要),设置为0,追求安全设置为1。
1-----> 每次事务commit,立即将mysql 内存里面的数据刷到os buffer(文件系统缓存) ,并立即刷写到磁盘,这两个动作,在做commit命令的时候,会一起运行,直到保证,真正把日志写到磁盘上,这个commit命令才提交成功。
2-----> 每次事务commit,只保证刷写到 os buffer
0------> 每秒往os buffer 中刷一次,并同时从os buffer 中刷写到磁盘,按秒来计算。不管提不提交,只有redo里面有内容。
Innodb_flush_method=(o_DIRECT,fsync,o_DSYNC),(默认为fsync ,建议o_DIRECT) *****
作用:控制的是 redo buffer(redo) 和 buffer pool(数据) 刷写策略
fsync:性能好
o_DIRECT:安全
o_DSYNC:
如果企业当中,比较注重性能,最高性能模式。
innodb_flush_log_at_trx_commit=0
innodb_flush_method=fsync
如果企业当中,比较注重安全,最高安全模式。
innodb_flush_log_at_trx_commit=1
innodb_flush_method=o_DIRECT
redo日志设置有关的
innodb_log_buffer_size=16777216 # 16M #redo buffer 的大小
innodb_log_file_size=50331648 #50M ## ib_logfile0 ib_logfile1的大小,默认为50M
innodb_log_files_in_group = 3 ##redo 日志的个数,默认为两个,ib_logfile0 ib_logfile1
脏页刷写策略:默认值 75
innodb_max_dirty_pages_pct=75 ##内存里面buffer pool 数据达到,整个buffer pool 的 75%,就开始往磁盘写。
还有哪些机制会触发写磁盘?
CSR redo 满了