一、事务概念
逻辑上的一组操作,要么同时完成要么同时不完成。
例子:银行转账
create database day17;
use day17;
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values (null,'a',1000.0);
insert into account values (null,'b',1000.0);
update account set money=money-100 where name = 'a';
update account set money=money+100 where name = 'b';
二、事物控制
数据库默认就有事务,mysql中默认情况下一条语句独占一个事务。
也可以通过命令控制事务:
利用sql语句:
start transaction;#开启事务,这条语句执行之后所执行的所有的sql将处在同一个事务当中。
....
commit;#提交事务,使事务中所有的sql对数据库的影响一起发生。
rollback;#回滚事务,取消事务对数据库产生的影响。
利用JDBC来控制事务:
conn.setAutoCommit(false);#开启了事务,接下来在这个连接执行的所有sql都将处在同一个事务当中。
....
conn.commit();#提交事务,将这个连接上执行的事务提交,对数据库产生影响。
conn.rollback();#回滚事务,取消这个连接上执行的事务。
SavePoint sp = conn.setSavePoint();
conn.rollback(sp);
要注意,回滚到回滚点时,回滚点之前的代码仍然是未提交也未回滚的状态,如果希望对数据库产生影响仍然需要进行提交的操作。
三、事务的四大特性 - ACID
1.原子性:事务是一组不可分割的单位要么一起成功要么一起失败。
2.一致性:事务执行前后的数据完整性要保持一致。 数据完整性:如果在某一个时间点,数据库中的所有数据都满足数据库中的所有约束那么就称数据库为处在一个符合完整性的状态。
3.隔离性:多个并发的事务应该隔离开互不影响。
4.持久性:事务一旦被提交,对数据库产生的影响将会是永久性的,无论发生什么问题,这种影响都不可能被取消掉了。
四、隔离性
用锁来保证隔离性 -- 可以保证隔离性,但是数据库的并发性能必然大大降低
两个并发的修改 - 必须隔离
两个并发的查询 - 必须不隔离
一个修改一个查询 - ????
脏读:一个事务可以读取到另一个事务未提交的数据
-------------
a 1000
b 1000
-------------
a:
start transaction;
update account set money = money - 100 where name = 'a';
update account set money = money + 100 where name = 'b';
--------------
b:
start transaction;
select * from account;
-------------
a 900
b 1100
-------------
commit;
--------------
a:
rollback;
不可重复读:一个事务多次读取同一条记录由于并发的事务对该记录的并发修改,造成多次查询结果不一致。不可重复读的本质是一个事务读取到了另一个事务已经提交的数据。
-------------------------
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 活期 = 活期 -1000 where name = 'a';
commit;
---------------
select 活期+定期+固定 from account where name = 'a'; 总资产:2000元
commit;
~10万块钱和败家老婆 -- 不需要隔离
虚读(幻读):一个事物多次读取整表中的数据,由于另一个并发的事务对数据库中的表进行增加、删除记录的操作,造成多次读取不一致。一个事务读取到了另一个已经提交的事务的数据。
---------------
a 1000
b 2000
---------------
start transation;
select sum(money) from account; 总存款:3000元
select count(name) from account; 总账户:2个
-----------
c:
start transaction;
insert into account values ('c',3000);
commit;
---------------
a 1000
b 2000
c 3000
---------------
-----------
select avg(mone) from account;平均每账户:2000元
commit;
数据库的四大隔离级别:
read uncommitted; 数据库不做任何隔离性控制,数据库具有脏读、不可重复读和虚读(幻读)问题。
read committed; 数据库可以防止脏读问题,具有不可重复读和虚读(幻读)问题
repeatable read; 数据库可以防止脏读、不可重复读,具有虚读(幻读)问题
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 xxxxxxx; 修改隔离级别
**如果写session改变的是当前客户端和数据库进行交互时使用的隔离级别
**如果写global当前客户端和数据库进行交互时的隔离级别不变,改变的是数据库默认的隔离级别,影响新开的客户端和数据库交互时采用的默认的隔离的级别。
数据库中的锁:
共享锁:在非Serializable隔离级别下,查询不加任何锁,在Serializable隔离级别下查询加共享锁。
共享锁和共享锁可以共存,共享锁和排他锁不能共存。
排他锁:在任何隔离级别下进行增删改都会加排他锁
排他锁和任意锁都不能共存。
两个并发的修改: 无论任何隔离级别 增删改都加排他 两个排他不能共存 可以保证两个并发的修改一定隔离开
两个并发的查询: 无论什么情况都可以并发查询
非Serializable 非Serializable 两个都不加锁 可以共存,并发查询
Serializable 非Serializable 一个加共享锁 另一个不加锁 可以共存,并发查询
Serializable Serializable 两个都加共享锁 可以共存,并发查询
一个修改,一个查询:serializable的工作机制:
一个查询加了共享锁 ,其他修改试图加排他,由于共享和排他不能共存,只能等待,自然就不会造成隔离性问题。
五、更新丢失问题
更新丢失问题:两个并发的线程基于同一个查询结果进行修改,后提交的事务忽略了先提交的事务对数据库的影响,产生更新丢失问题。
例子:重复充值的案例
例子:小芳的例子
解决方案:
将数据库设置位Serializable隔离级别就可以原生的防止更新丢失问题,但是Serializable性能低下,一般不会将数据库的隔离级别设置位Serializable
在非Serializable隔离级别下:
悲观锁:悲观锁悲观认为每次查询都会造成更新丢失,所以在查询时手动加排他锁,从而防止其他并发事务的查询/修改,从而防止更新丢失问题。
select * from user for update;
乐观锁:乐观锁乐观的认为,每次更新都不会造成更新丢失,正常的查询和更新,只有检测到发生了更新丢失,才进行补救。乐观锁通常通过数据库中增加一个版本字段来工作。
如果查询比较多,更新比较少,用乐观锁。如果更新比较多,而查询比较少用悲观锁。
================================================================================================================================
六、ThreadLocal -- 本地线程变量