事务的概念
事务是逻辑上的一组操作,要求这一组操作要么同时完成 要么同时不完成。
事务操作
数据库默认就有事务,mysql中默认情况下一条语句独占一个事务。
也可以通过手动控制事务:
sql控制事务:
start transaction; #开启一个事务 这条语句之后的所有的sql都将处在同一个事务中。这些sql都不会立即起作用 - 没有改变底层真正的数据。
commit; #提交事务 这个事务中所有的sql对数据库的影响立即发生 - 真正改变底层数据。
rollback; #回滚事务 撤销这个事务中所有的操作可能的对数据库的影响。
start transaction;
update account set money = money-100 where name='a';
update account set money = money+100 where name='b';
commit;
start transaction;
update account set money = money-100 where name='a';
update account set money = money+100 where name='b';
rollback;
JDBC控制事务:
conn.setAutoCommit(false);#开启了事务,接下来在这个连接执行的所有sql都将处在同一个事务当中。
....
conn.commit();#提交事务,将这个连接上执行的事务提交,对数据库产生影响。
conn.rollback();#回滚事务,取消这个连接上执行的事务。
SavePoint sp = conn.setSavePoint();
conn.rollback(sp);
要注意,回滚到回滚点时,回滚点之前的代码仍然是未提交也未回滚的状态,如果希望对数据库产生影响仍然需要进行提交的操作。
事务的四大特性 - ACID
原子性 - 事务中的一组操作是不可分割的一个整体,要么一起成功,要么一起失败。
一致性 - 事务前后 无论事务是否成功 数据库应该都保持一个完整性的状态。数据库中数据是业务完整且约束完整的。
隔离性 - 多个并发事务之间应该互相隔离 互不影响
持久性 - 一个事务成功对数据库产生的影响是永久性的,无论发生什么情况 这种影响都不会被取消。
隔离性的问题
加锁:同一时间内只能有一个人操作数据,可以完美的保证隔离性,但是这样一来数据库就相当于工作在单线程的状态下,同一时间只能有一个事务操作,并发的效率非常低下。
而现实生活中并不是所有的场景下都需要那么严格的事务隔离,在不同的业务场景下对隔离性的要求是不同的。
所以数据库的设计者在设计隔离性时并没有将隔离性写死,而是提供了不同的选项,数据库的使用者可以在使用数据时根据自身需求选择对应的选项来得到相应的隔离能力和性能。
通过这些选项,数据库使用者可以在数据库的隔离能力和性能间做一个权衡,从而在保证需要的隔离性的基础上得到尽量好的性能。
四大隔离级别
Read uncommitted
Read committed
Repeatable read
Serializable
(1)Read uncoommitted
数据库不保证任何事务特性 可能出现脏读 不可重复读 虚读(幻读) 问题
脏读:一个事务读取到了另一个事务未提交的数据
a:
set session transaction isolation level read uncoommitted;
---------------
a: 1000
b: 1000
---------------
b:
start transaction;
update account set money = money - 100 where name = 'b';
update account set money = money + 100 where name = 'a';
a:
start transaction;
select * from account;
---------------
a: 1100
b: 900
---------------
commit;
b:
rollback;
a:
start transaction;
select * from account;
---------------
a: 1000
b: 1000
---------------
commit;
(2)Read committed
保证部分隔离 可以防止脏读问题 但是具有不可重复读 和 虚读(幻读)问题
不可重复读:一个事务读取到另一个事务已经提交的数据
a:
set session transaction isolation level read committed;
-------------------------
a 1000 1000 1000
-------------------------
b:
start transaction;
select 活期 from account where name = 'a'; -- 活期存款1000
select 定期 from account where name = 'a'; -- 定期存款1000
select 固定 from account where name = 'a'; -- 固定存款1000
-------------------------
a:
start transaction;
update account set money = money - 1000 where name = 'a';
commit;
-------------------------
b:
select 活期 + 定期 + 固定 from account where name = 'a';
-------------------------
总资产2000
(3)Repeatable read
保证部分隔离 可以防止脏读 不可重复读问题 但是具有虚读(幻读)问题
虚读(幻读):一个事务读取全表数据时 读取到另一个事务向表中新增、删除操作提交的结果
**虚读(幻读)问题 有可能出现 有可能不出现 概率非常低
set session transaction isolation level Repeatable read;
-------------------------
a 1000
b 1000
-------------------------
c:
start transaction;
select count(*) from account; -- 2人
select sum(money) from account; -- 2000元
--------
d:
start transaction;
insert into account values(d 4000);
commit;
-------------------------
a 1000
b 1000
d 4000
-------------------------
select avg(money) from account; -- 2000元
commit;
(4)Serializable
保证完全隔离 可以防止脏读 不可重复读 虚读(幻读)问题
本质上是靠锁来实现的
从安全性说:
Serializable > Repeatable read > Read committed > Read uncommitted
从效率说:
Read uncommitted >Read committed > Repeatable read > Serializable
真正在开发的过程中应该根据自身的业务需求选择一个在能防止想要防止的隔离性问题的级别中性能最好的一个隔离级别
mysql的默认隔离级别时 Repeatable read
查看数据库隔离级别:
select @@tx_isolation;
设置数据库的隔离级别:
set [session/global] transaction isolation level xxxxxx;
数据库中的锁机制
数据库中是有锁的,但是锁如果控制不好,对效率影响非常大,所以数据库设计者对锁做了特别的设计:
业务情况 | 是否互斥 |
---|---|
两个查询 | 没有必要互斥 |
两个修改 | 必须互斥 |
一个查询 另一个修改 | 具体看情况 Serializable隔离级别下需要排斥 其他隔离级别不需要 |
共享锁
共享锁和共享锁可以共存,共享锁和排他锁不能共存
在非Serializable级别中查询不加任何锁 在Seralizable级别中查询加共享锁
排他锁
排他锁和任何锁都不能共存
在任意隔离级别下做增删改都加排他锁
操作和锁的关系
1. 非Serializable级别,查询不加任何锁
2. Serializable级别,查询加共享锁
3. 任意隔离级别下,增删改都加排他锁
a(Rep) | b(Rep) | 结果 | 原因 |
---|---|---|---|
a查询 | b查询 | 不互斥 | 都不加锁 |
a查询 | b增删改 | 不互斥 | a不加锁+b排他锁 |
a增删改 | b查询 | 不互斥 | a排他锁+b不加锁 |
a增删改 | b增删改 | 互斥 | a排他锁+b排他锁 |
a(Ser) | b(Rep) | 结果 | 原因 |
---|---|---|---|
a查询 | b查询 | 不互斥 | a共享锁+b不加锁 |
a查询 | b增删改 | 互斥 | a共享锁+b排他锁 |
a增删改 | b查询 | 不互斥 | a排他锁+b不加锁 |
a增删改 | b增删改 | 互斥 | a排他锁+b排他锁 |
a(Ser) | b(Ser) | 结果 | 原因 |
---|---|---|---|
a查询 | b查询 | 不互斥 | a共享锁+b共享锁 |
a查询 | b增删改 | 互斥 | a共享锁+b排他锁 |
a增删改 | b查询 | 互斥 | a排他锁+b共享锁 |
a增删改 | b增删改 | 互斥 | a排他锁+b排他锁 |
死锁:
当两边都时Serializable隔离级别时
两边都先进行查询 再尝试进行修改 则互相等待对方释放共享锁 都无法接着执行 造成了死锁
死锁的解决有两种办法:避免死锁 解决死锁
mysql没有避免死锁,尝试检测死锁,发现死锁后,退出一方,执行另一方来解决了死锁。