Mysql的事务
1.基本概念
事务本质是一组SQL操作,事务中的语句要么全部执行成功,或者全部执行失败。
2.如何保证一个事务:四个特性(ACID)
原子性 (Automic)
表示事务是操作的最小单元。整个事务的操作,要么全部成功,要么全部失败。
一致性 (consistency)
数据库总是从一个状态转换成另一个状态。
当事务中间执行错误时,事务会回滚到执行事务之前的状态。如果整个事务执行成功,才能成为成功的状态。
隔离性 (isolation)
一般来说,一个事务在提交操作之前对其他事务是不可见的。
在事务中的操作完成提交之后,其他的事务才能看到当前对数据库的修改。当然这只是一般状态,因为隔离级别的不同,可能会存在差异。
持久性 (durablity)
一个事务执行成功,其所对数据库所进行的修改会永久的保存到数据库中。
3.Mysql的事务操作
开始事务
start transaction;
set autocommit=0;
提交事务 commit;
回滚事务 rollback;
- mysql 默认不开始事务,需要手动启动事务
- 每一步数据修改都直接生效
- 查看事务是否开启
show variables like 'autocommit';
4.事务的可见性控制:隔离级别
不同数据库及数据引擎可能会有不同,以innodb数据库引擎为准
四种隔离级别:
- READ UNCOMMITED(读未提交) 事务中的修改,即使没有提交,其他事务也能看到;
- READ COMMITED (读已提交) 一个事务只能看到提交的事务做出的修改;
- REPEATED READ (可重复读) 保证多次读取同样的记录是一致的。MYSQL的默认隔离级别
- SERIALIZABLE (序列化) 强制让事务串行执行,会给读取的每一行数据都加上锁,所以会造成锁竞争,出现超时情况。
隔离级别的设置:
set tx_isolation='READ-UNCOMMITTED';
set tx_isolation='read-committed';
set tx_isolation='repeatable-read';
set tx_isolation='serializable';
查看隔离级别
select @@transaction_isolation
访问冲突情况
隔离级别 | 访问冲突现象 |
---|---|
read-uncommitted | 脏读,幻读,不可重复读 |
read-committed | 幻读,不可重复读 |
repeatable-read | 幻读 |
serializable | 序列化,完全隔离(同一个表,只能一个事务处理) |
脏读:
可以读取其他事务未提交的数据
客户端1 | 客户端2 |
---|---|
set tx_isolation='read-uncommitted'; | set tx_isolation='read-uncommitted'; |
rollback;begin; | rollback;begin; |
Insert into tb1(name)values('bb'); | Select * from tb1; |
Update tb1set name='a'where id=1; | Select * from tb1; |
rollback; | Select * from tb1; |
不可重复读:
其他事务对当前事务查询范围数据进行了的修改,在其他事务提交后,查询到的是修改后的数据。因为在其他事务提交之前和其他事务提交之后查询的结果不一致,因此也就是不可重复读。
客户端1 | 客户端2 |
---|---|
select @@transaction_isolation set transaction_isolation='read-committed' |
select @@transaction_isolation set transaction_isolation='read-committed' |
begin | begin |
select * from temp (图一) | |
update temp set name='zhangsan' where name='zs' select * from temp |
|
commit | |
select * from temp(图二) | |
commit |
图一:
图二:
幻读:
幻读,又叫做幻行。指在当前事务查询的范围内,其他事务进行了增删操作,当再次查询的时候就会出现幻行的情况。
客户端1 | 客户端2 | 客户端3 |
---|---|---|
select @@transaction_isolation set transaction_isolation='repeatable-read' |
select @@transaction_isolation set transaction_isolation='repeatable-read' |
select @@transaction_isolation set transaction_isolation='repeatable-read' |
begin | begin | begin |
select * from temp(图一) | ||
delete from temp where name ='lisi' | ||
commit | ||
select * from temp(图二) | ||
insert into temp (id,name)values(uuid(),'xiaohong'); | ||
commit | ||
select * from temp(图三) | ||
commit |
图一:
图二:
图三:
5.事务的资源竞争:锁
5.1 Mysql的锁和引擎有关系
- innodb默认使用的是行级锁
一个事务修改一行数据未提交时, 该行数据会被锁定,不允许其他事务修改
- myisam 默认使用的是表级锁
5.2 死锁
出现场景:当两个事务或多个事务在统一资源上占用,并请求对象占用的资源,造成了恶行循环;
事务一
start transaction
update temp set username='zhangsan' where username='zs'
update temp set username='ls' where username='lisi'
事务二
start transaction
update temp set username='lsj' where username='lisi'
update temp set username='zhangsanfeng' where username='zs'
5.2 几种常见的数据库锁
乐观锁
乐观锁,通常不是数据库自带,通过自己实现;
实现的过程一般为:
例子:
在修改信息当前,判断versions,和之前存储的versions编号进行比对,看是否一致
1.查询要修改数据的标志(sign)
select name,sign from temp where id='666'
2.修改要调整的数据,判断sign是否和之前的查询数据一直,如果一直则更新;
update temp set name='wangwu',sign=sign+1 where id='666'and sign='之前的sign'
悲观锁
在整个数据处理过程中,将数据处于锁定状态
悲观锁的实现:
1. 自动提交模式要关闭
set autocommit = 0;
当执行一个sql时,数据库立即执行;
2. 开启事务
begin;/begin work;/start transaction;
3. 查询要操作数据的信息
SELECT * FROM TB WHERE CD FOR UPDATE
FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据会等其他数据执行完毕后再执行
4.数据进行插入操作等
insert into t_orders (id,goods_id) values (null,1);
5.更新插入数据列状态
update t_goods set status=2;
6.提交SQL执行
commit;/commit work;
手动对需要执行的SQL进行提交;
不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。
明确指定了主键,并且有该数据,row lock
console1:
BEGIN;
SELECT * FROM city WHERE id='1' FOR UPDATE
|id|name|state|
|:--|---:|:--:|
|1|美国|0|
console2:
BEGIN;
SELECT * FROM city WHERE id='1' FOR UPDATE
查询被阻塞;
报错:error:1205
Lock wait timeout exceeded; try restarting transaction
明确指定主键,若查无此数据,无lock
console1:查询结果为空;
console2:查询结果为空,查询无阻塞,说明console1没有对数据执行锁定
主键不明确,table lock
console1:查询正常
BEGIN ;
SELECT * FROM city WHERE id !='3' FOR UPDAT;
id | name | state |
---|---|---|
1 | 美国 | 0 |
2 | 新加坡 | 0 |
console2:查询被阻塞,说明console1把表锁住了
BEGIN;
SELECT * FROM city WHERE id ='1' FOR UPDATE
共享锁
console1: 查询没有问题
BEGIN;
select * from city WHERE ID='1' LOCK IN SHARE MODE
console2:查询阻塞报错;
select * from city WHERE ID='1'
console2:查询没有问题
select * from city WHERE ID='1' LCOCK IN SHARE MODE
用的共享锁,可以查询同一行数据,但是不能进行insert,update等操作,因为这些操作会单独在次进行锁的判断
排它锁
console1:查询无误
BEGIN;
SELECT * FROM CITY WHERE ID='1' FOR UPDATE
console2:阻塞状态
SELECT * FROM CITY WHERE ID='1'
console2:阻塞状态
SELECT * FROM CITY WHERE ID='1' FOR UPODATE
共享锁和排它锁更像是悲观锁的一个分类;
参考博客:https://blog.csdn.net/puhaiyang/article/details/72284702