事务和锁
事务
transaction,一组原子性的SQL查询,或者说是一个独立的工作单元。如果能够成功执行这组查询的全部语句,就会执行这组查询;如果其中任何一条语句无法成功执行,那么这组查询的所有语句都不会执行。
也就是说,事务内部的语句,要么全部执行成功,要么全部执行失败。
使用事务
- 开始标志:任何一条DML语句的执行
- 结束标志:
- 提交:成功的结束,将所有的DML语句操作记录和底层硬盘文件中数据进行同步
- 回滚:失败的结束:将所有DML语句操作记录全部清除
MySQL默认是自动提交
#开启事务
start transaction;
#提交事务
commit;
#保存回滚点
savepoint 回滚点名;
#回滚事务
rollback[to 回滚点名];#查看事务的提交方式
show variables like 'autocommit';
#启用和关闭自动提交模式
set autocommit = 1;
set autocommit = 0;
举例:转账业务
- 创建表并插入数据
drop table if exists account;
create table account(id int primary key auto_increment,name varchar(20),money int check(money >= 0)
);
insert into account(name,money
)
values
('张三',20000),
('李四',500);
- 自动提交和手动提交,再开一个MySQL—Front窗口查看数据变化
事务的四个特性(ACID)
ACID:
- A(原子性 atomicity):事物是最小工作单元,不可再分,一个事务的所有操作要么全部执行成功,要么执行失败全部回滚,不会出现成功一部分的情况
- C(一致性 consisyency):数据库总会从一个一致的状态转变成另一个一致的状态,不会因为一条语句的失败而出现另外的状态,打个比方事物执行成功前的状态是 0 ,执行成功后的状态是 1,不会因为某一条语句的失败而出现 0.5的状态。
- I(隔离性 isolation):通常来说,一个事物所做的修改在最终提交前,对于其他事物是不可见的,即在最终提交前该事务的修改不会影响到其他事务。
- D(持久性 durability):事务一旦提交,其做的修改会持久的保存到数据库中,即使系统崩溃,修改的数据也不会丢失。
一个兼容ACID的数据库系统很多复杂但可能用户并没有察觉到的工作.相比没有实现ACID的数据库,通常回需要更强的CPU处理能力,更大的内存和更多的磁盘空间。
持久性原理:
一般事务的持久性都是借助redo log来实现的。
Redo log(重做日志)是一种日志文件,记录了对于数据库的修改操作(包括插入,修改删除等等操作),它的主要作用就是确保系统或数据库崩溃之后,系统能够通过重做日志上记录的操作,从而是数据库回到事务提交后的状态,确保事务的持久性。
并行事务带来的问题
数据一致性问题:
- 脏读(dirty read):一个事务读到另一个事务未提交的数据就是脏读,因为一个事务对于一条记录做修改,在该事务未提交前所做的修改随时有可能回滚,因此被另一个事务读到并回滚的数据就是脏数据。
- 不可重复读(non-repeatable read):在一个事务内多次读取同一条记录,前后结果不一致就是不可重复读,比如事务A多次查询同一行数据,在某次查询间隔中,事务B对该行数据进行了修改并提交,导致事务A对该行数据的下次读取出现了不一致,这种情况就叫不可重复读。
- 幻读(phantom read):在一个事务内按照相同的条件多次查询,前后结果集的数量不同就是幻读,比如事务A按照id>5的条件进行多次查询,出现幻读前的查询结果一直是count(*)=3,在某次查询间隔中,事务B插入了一条id=9的数据并提交,那么在修改后事务A查询到的结果就会变成count(*)=4,这就是幻读问题。
注意:不可重复读主要针对数据本身,幻读则针对的是查询读取的结果集数量。
影响:
- 脏读:读到其他事务未提交的数据
- 不可重复读:前后读取的数据不一致
- 幻读:前后读取的结果集数量不同
隔离级别
在SQL标准中定义了四个隔离级别,每一种级别都规定了一个事务中所做的修改,哪些事物内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。
- 未提交读(READ UNCOMMITED):事务中未提交的修改也可以被其他事务读取到,这个隔离等级会出现包括脏读,不可重复读,幻读问题,一般不用。
- 提交读(READ COMMITED):事务中的修改在提交之后才会被其它事务读取到,这样就避免了脏读问题,但是还是会出现不可重复读和幻读问题。
- 可重复读(REPEATABLE READ):同一事物中多次读取同样记录的结果是一致的,就解决的脏读和不可重复读的问题,但是幻读问题还是未得到完全的解决,值得注意的一点是隔离级别是MySQL的默认隔离级别,又因为MySQL的特质该隔离级别可以解决大部分幻读问题。
- 可串行化(SERIALIZABLE):强制事务串行执行,不会出现并行事务,所以就解决了脏读,不可重复读,幻读的问题,该隔离等级会读取的行上添加行锁以及隔离锁以避免幻读问题,同时对于读取操作加入共享锁,写入操作加入排他锁,值得一提的串行化虽然能够避免所有有关一致性的问题,但是因为它并发性能低,容易死锁,效率低,在一般项目中很少用,更多的还是使用可重复读或提交读。
查看隔离级别:
#查看会话级的当前隔离级别:(会话级只对当前窗口有效)
select @@tx_isolation
select @@session.tx_isolation
#查看全局级的当前隔离级别:
select @@global.tx_isolation
设置隔离级别:
- 方法1:my.ini文件中的[mysqld]下面添加transaction-isolation =隔离级别
- 方法2:通过指令
#设置全局或当前会话范围
set global transaction isolation level 隔离级别;
set session transaction isolation level 隔离级别;
实现隔离级别的两种方法:
- 加锁:读数据前对其,阻止其他事务对数据修改
- 快照:读取数据的快照(之前的版本)实现,例如,可重复读级别在整个事务期间都读取事务开始时的快照去解决不可重复读问题。
InnoDB中的锁
共享锁和排他锁
- 共享锁(S Lock):读锁,允许事务读取数据
- 排他锁(X Lock):也叫独占锁,写锁,允许事务删除或更新数据
如果事务获取了某个数据的共享锁,其他事务可以立即获取该数据的共享锁,这种情况叫锁兼容。如果事务获取了某个数据的共享锁或排他锁,其他事务想要获取该数据的排他锁,必须要等到该行的锁被释放掉。
排他锁和共享锁的兼容性
意向锁
InnoDB允许事务在行锁和表锁同时存在。为支持在不同粒度上进行加锁,InnoDB支持意向锁。
意向锁,将锁定的对象分为多个层级,意向锁意味着事务有意向在更细粒度上加锁。如果需要加锁,需要先对表意向锁再对加行锁。
意向锁在InnoDB中就是表级别的锁,支持两种意向锁:
- 意向共享锁(IS Lock),事务有意向对表中某些行加共享锁
- 意向排他锁(IX Lock),事务有意向对表中某些行加排他锁
表级锁的兼容性
注意:意向锁不会和行级锁冲突,意向锁之间也不会冲突,意向锁只会和共享表锁和排他表锁冲突
意向锁的作用:如果没有意向锁,想要给一个表加表锁必须要检查该表是否有表锁和每一行是否有锁。而如果在加行锁前给这个表加上了意向锁,这时只需要检查表锁和意向锁就可以了,不需要检查每一行锁。
一致性非锁定读
一致性非锁定读,读取正在执行delete和update操作的行时,不会等待该行上锁的释放,而是读取该行的一个快照数据(该行之前的版本)。非锁定读极大的提高了数据库的并发性,InnoDB默认时这种读取方式,也就是说默认普通的select语句不会加锁而是通过读取快照实现数据一致性。
上面提到的快照数据就是该行数据的历史版本,由此带来的并发控制称为多版本并发控制(MVCC)。
READ COMMITTED(提交读)和REPEATABLE READ(可重复读)在InnoDB中使用的是一致性非锁定读,但是读取的快照不同。提交读级别读取的是最新版本的快照,可重复读级别读取的是事务开始时数据的快照。
可重复读级别通过读快照,可以解决前面提到的幻读问题,但是有些情况需要锁定读。
一致性锁定读
InnoDB默认使用一致性非锁定读。某些情况用户需要显示加锁保证数据的一致性,支持两种一致性锁定读的操作。
- select ... for update:对读取的行加一个排他锁
- select ... lock in share mode:对读取的行加一个共享锁
如果只对读取的行加锁会有幻读问题:
锁定读时使用键值间隙锁(Next-Key Lock),就是行锁加间歇锁,来解决幻读问题。
- Record Lock:单行记录的锁,比如只锁3这行记录
- Gap Lock:间歇锁,锁定一个范围,但不包括记录本身,就像这样只锁(1-2),(4-5)这个范围的记录
- Next-Key Lock:Record Lock+Gap Lock,锁定一个范围,并锁定记录本身,两个锁一相加就成了锁定(1-5)中所有的记录
非锁定读使用快照和锁定读使用间歇锁可以基本解决幻读问题,但是极特殊情况还是有可能发生幻读。