1)事务管理
1.1事务概述
事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
事务作用:保证在一个事务中多次操作数据库表中数据时,要么全都成功,要么全都失败.
1.2 mysql事务操作
sql语句 | 描述 |
---|---|
start transaction; | 开启事务 |
commit; | 提交事务 |
rollback; | 回滚事务 |
操作:
– MYSQL中可以有两种方式进行事务的管理:
– 手动提交:先开启,再提交
– 自动提交:MySql默认自动提交.即执行一条sql语句提交一次事务.
手动提交:
例句一: 需求:演示提交事务,a给b转账100元.
-- 开启事务
start transaction;
-- 执行sql语句
update account set money= money-100 where name ='a';
update account set money= money+100 where name ='b';
-- 提交事务
commit; --(事务提交之后,sql语句对数据库产生的操作才会被永久的保存)
例句二: 需求:演示回滚事务,a给b转账100元.
-- 开启事务
start transaction;
-- 执行sql语句
update account set money= money-100 where name ='a';
update account set money= money+100 where name ='b';
-- 回滚事务(撤销)
rollback;--(撤销已经成功执行的sql语句,回到开启事务之前的状态)
注意:
只要提交事务,那么数据就会长久保存了,就不能回滚事务了.
即提交或者回滚事务都是代表结束当前事务的操作.
自动提交:
通过修改mysql全局变量“autocommit”进行控制.
查看命令:
通过以下命令可以查看当前autocommit模式:
show variables like ‘%commit%’;
Variable_name Value
autocommit ON
设置命令:
设置自动提交的参数为 OFF:
set autocommit = 0; – 0:OFF 1:ON
说明:
1)MySql默认自动提交.即执行一条sql语句提交一次事务.
2)设置autocommit为off状态,只是临时性的,
下次重新启动mysql,autocommit依然变为on状态.
3)如果设置autocommit为off状态,那么当我们执行一条sql语句,
就不会自动提交事务,重新启动可视化工具,数据并没有改变.
4)如果设置autocommit为on状态,如果我们先执行 start transaction;
然后在执行修改数据库的语句:
那么此时就表示上述修改数据库的sql语句都在同一个事务中,
此时必须手动提交事务,即commit;
换句话说,如果我们手动开启事务 start transaction;
那么此时mysql就不会自动提交事务,必须手动提交事务.
5)如果设置autocommit为on状态,如果我们不执行 start transaction;
直接执行修改数据库的语句:
那么此时mysql就会自动提交事务.即上述每条sql语句就是一个事务.
注意:
Oracle数据库事务不自动提交.
1.3 JDBC事务操作
Connection对象的方法名 | 描述 |
---|---|
conn.setAutoCommit(false) | 开启事务 |
conn.commit() | 提交事务 |
conn.rollback() | 回滚事务 |
注意:
在jdbc事务操作中,事务的控制都是通过Connection对象完成的,
当一个完整的业务操作前,我们首先使用conn.setAutoCommit(false)来开启事务.
默认情况下是true的,表示关闭事务,那么一条sql语句就是一个事务,默认提交事务.
如果设置为false,那么表示开启事务,所有的sql语句就会都在一个事务中.
当业务操作完成之后,如果整个操作没有问题,我们需要使用conn.commit()来提交事务.
当然了,如果出现了异常,我们需要使用conn.rollback()撤销所有的操作,
所以出现异常,需要进行事务的回滚.
如下是使用jdbc操作事务的转账案例代码.
需求: a转给b 100元
分析:
a用户 money=money-100
b用户 money=money+100
代码演示:
@Test
public void show() throws SQLException {
Connection conn = null;
PreparedStatement pst = null;
try {
// 1.获取连接
conn = JDBCUtils_Demo02.getConnection();
// 2.开启事务
conn.setAutoCommit(false);
// 3.执行sql语句
// 3.1 完成a-100
String sql = "update account set money=money-? where name=?";
pst = conn.prepareStatement(sql);
//这里的1是 sql语句中第一个?(占位符) 100为第一个占位符的值
pst.setDouble(1, 100);
//这里的2是 sql语句中第二个?(占位符) a为第二个占位符的值
pst.setString(2, "a");
//上面代码相当于以下代码:
//String sql = "update account set money=money-100 where name='a'";
pst.executeUpdate();
// 6 模拟发生异常
// int x = 1 / 0;
// 3.2 完成b+100
String sq2 = "update account set money=money+? where name=?";
pst = conn.prepareStatement(sq2);
pst.setDouble(1, 100);
pst.setString(2, "b");
pst.executeUpdate();
// 4.1 一切正常 提交事务
System.out.println("一切正常 事务已提交");
conn.commit();
} catch (Exception e) {
// 4.2 如果出现异常 回滚事务
System.out.println("发现异常 事务已回滚");
if (conn != null) {
conn.rollback();
}
} finally {
// 5 释放资源
if (pst != null) {
pst.close();
}
if (conn != null) {
conn.close();
}
}
}
2) 事务的特性 ACID
数据库的事务必须具备ACID特性,ACID是指 Atomic(原子性)、Consistensy(一致性)、
Isolation(隔离型)和Durability(持久性)的英文缩写。
2.1、隔离性(Isolation)
多个用户并发的访问数据库时,一个用户的事务不能被其他用户的事务干扰,
多个并发的事务之间要相互隔离。一个事务的成功或者失败对于其他的事务是没有影响。
2个事务应该相互独立。
2.2、持久性(Durability)
指一个事务一旦被提交,它对数据库的改变将是永久性的,
哪怕数据库发生异常,重启之后数据亦然存在。
2.3、原子性(Atomicity)
原子性是指事务包装的一组sql(一组业务逻辑)是一个不可分割的工作单位,
事务中的操作要么都发生,要么都不发生。
2.4、一致性(Consistency)
一个事务在执行之前和执行之后 数据库都必须处于一致性状态。
如果事务成功的完成,那么数据库的所有变化将生效。
如果事务执行出现错误,那么数据库的所有变化将会被回滚(撤销),返回到原始状态。
重点: 事务的成功与失败,最终数据库的数据都是符合实际生活的业务逻辑。一致性绝大多数依赖业务逻 辑和原子性。
3) 事务问题
3.1 事务的隔离级别
如果不考虑事务的隔离型,由于事务的并发,将会出现以下问题:
1、脏读 – 最严重,杜绝发生。
脏读:指一个事务读取了另外一个事务未提交的数据。(非常危险)
脏读的前提是没有事务的隔离性。
说明:事务a首先执行转账操作,然后事务a还没有提交数据的情况下,事务b读取了数据库的数据。
紧接着事务a执行回滚操作,导致事务b读取的结果和数据库的实际数据是不一样的。
2、不可重复读 (强调的数据内容不一致)
不可重复读:在一个事务内多次读取表中的数据,多次读取的结果不同。
说明:事务b首先读取数据库的数据,然后事务a对数据修改并提交。之后事务b对数据库再次进行读取。
这时发现在事务b中2次读取的结果不一致。
– 不可重复读和脏读的区别:
脏读:强调的是读取了未提交的数据。
不可重复读:一个事务内2次读取,其中一次读取了另一个事务提交了的数据。
3、幻读(虚读)(强调的数据数量不一致)
幻读(虚读):同一个事务内,2次读取的数据的数量不一致,叫做幻读或者虚读。
说明:事务b首先读取数据的数量,然后事务a添加了一条数据,并且提交了。
接着事务b再次读取了数据的数量。2次读取不一致。
– 虚读(幻读)和不可重复读的区别:
不可重复读:强调的是数据内容的不一致。另一个事务是update操作。
虚读(幻读):强调的数据的数量(记录数)的不一致。另一个事务是insert或者delete操作。
3.2 数据库的隔离级别:解决问题
数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
1) read uncommitted 读未提交。
一个事务读到另一个事务没有提交的数据。
a) 存在:3个问题(脏读、不可重复读、虚读)。
b) 解决:0个问题
2) read committed (oracle数据库)读已提交。
一个事务读到另一个事务已经提交的数据。
a) 存在:2个问题(不可重复读、虚读)。
b) 解决:1个问题(脏读)
3) repeatable read(mysql数据库):可重复读。
在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
a) 存在:1个问题(虚读)。
b) 解决:2个问题(脏读、不可重复读)
4) serializable 串行化。
多个事务排队执行,同时只能执行一个事务,相当于事务中的单线程。
a) 存在:0个问题。
b) 解决:3个问题(脏读、不可重复读、虚读)
安全和性能对比:
安全性:serializable > repeatable read > read committed > read uncommitted
性能 : serializable < repeatable read < read committed < read uncommitted
常见数据库的默认隔离级别:
MySql:repeatable read。
Oracle:read committed。
注意:其实三个问题,开发中最严重的问题就是脏读,这个问题一定要避免,
而关于不可重复读和虚读其实只是感官上的错误,并不是逻辑上的错误。
就是数据的时效性,所以这种问题并不属于很严重的错误。
如果对于数据的时效性要求不是很高的情况下,我们是可以接受不可重复读和虚读的情况发生的。
4)数据库语言分类:
DDL :create / alter / drop 操作数据库和整个表
DML :insert / update/ delete 操作数据库表中的数据
DQL :select 查询数据库表中的数据
DCL :grant / revoke 给用户授权和撤销权限的
一个公司里面的数据库服务器上面可能同时运行着很多个项目的数据库。
所以,我们应该可以根据不同的项目建立不同的用户,分配不同的权限来管理和维护数据库。
4.1 创建用户
语法:
create user ‘用户名’@‘主机名’ identified by ‘密码’;
具体操作:
需求:创建 zhangsan 用户,只能在localhost这个服务器登录mysql服务器,密码为123
mysql> create user 'lisi'@'%' identified by '123';
mysql> create user 'zhangsan'@'localhost' identified by '123';
需求:创建lisi用户,能在所有服务器登录mysql服务器,密码为123
mysql> create user 'lisi'@'%' identified by '123';
需求:创建lisi用户,能在所有服务器登录mysql服务器,密码为123
4.2 用户授权
语法:
grant 权限1, 权限2… on 数据库名.表名 TO ‘用户名’@‘主机名’;
具体操作:
需求:
1.给 zhangsan 用户分配对 demo01 这个数据库操作的权限:
创建表,修改表,插入记录,更新记录,查询
A:使用root管理员创建一个数据库.
create database demo01;
B:使用root管理员给普通用户 zhangsan 进行上述需求的授权。
grant create,alter,insert,update,select on demo01.* to 'zhangsan'@'localhost';
2.使用zhangsan用户创建表person,并插入一条数据
-- 登录zhangsan用户
mysql -u zhangsan -p 123
-- 切换到数据库demo01
use demo01;
-- 创建表person
create table person(age int);
-- 向person表中插入一条记录:
insert into person values(18);
-- 删除person表
delete from person;
注意:由于上述没有给普通用户授权删除表中的数据,所以当我们删除的时候,会出现如下错误:
[Err] 1142 - DELETE command denied to user 'zhangsan'@'localhost' for table 'person'
注意:由于上述没有给普通用户授权删除表中的数据,所以当我们删除的时候,会出现如下错误:
[Err] 1142 - DELETE command denied to user 'zhangsan'@'localhost' for table 'person'
2.使用zhangsan用户创建表person,并插入一条数据
-- 登录zhangsan用户
mysql -u zhangsan -p 123
-- 切换到数据库demo01
use demo01;
-- 创建表person
create table person(age int);
-- 向person表中插入一条记录:
insert into person values(18);
-- 删除person表
delete from person;
注意:由于上述没有给普通用户授权删除表中的数据,所以当我们删除的时候,会出现如下错误:
注意:由于上述没有给普通用户授权删除表中的数据,所以当我们删除的时候,会出现如下错误:
[Err] 1142 - DELETE command denied to user 'zhangsan'@'localhost' for table 'person'
4.3 撤销授权
注意:撤销用户权限必须使用root管理员。
语法:
revoke 权限1, 权限2… on 数据库.表名 from ‘用户名’@‘主机名’;
具体操作:
需求: 撤销 zhangsan 用户对 demo01 数据库所有表的操作的权限.
-- 首先使用root权限撤销用户u1的所有权限。
revoke all on demo01.* from 'zhangsan'@'localhost';
-- 使用用户 zhangsan 登录 demo01 数据库中的表
-- 登录zhangsan用户
mysql -u zhangsan -p 123
-- 执行切换到数据库demo01
use demo01;
-- 提示错误:
[Err] 1044 - Access denied for user 'zhangsan'@'localhost' to database 'demo01'
4.4 查看权限
语法:
show grants for ‘用户名’@‘主机名’;
具体操作:
-- 查看用户 zhangsan 的权限
show grants for 'zhangsan'@'localhost';
结果:
GRANT USAGE ON *.* TO 'zhangsan'@'localhost' IDENTIFIED BY
PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257'
-- 查看管理员 root 的权限
show grants for 'root'@'localhost';
结果:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY
PASSWORD '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' WITH GRANT OPTION
GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION
注意:
usage是指连接(登陆)权限,建立一个用户,就会自动授予其usage权限(默认授予).
4.5 删除用户
语法:
DROP USER ‘用户名’@‘主机名’;
具体操作:
需求: 删除用户 lisi
drop user 'lisi'@'%';
4.6 修改管理员密码
语法:
mysqladmin -u root -p password 新密码
注意:需要在未登陆MySQL的情况下操作,新密码不需要加上引号。
具体操作:
-- 将 root 管理员新密码改为123456
mysqladmin -u root -p passwprd 123456 --新密码
4.7 修改普通用户密码
语法:
set password for ‘用户名’@‘主机名’ = password(‘新密码’);
注意:需要在登陆MySQL的情况下操作,新密码要加单引号。
具体操作:
需求: 将’zhangsan’@‘localhost’的密码改成’666’
-- 登录root权限
mysql -u root -p 123456
-- 更改用户 zhangsan 的密码
set password for 'zhangsan'@'localhost'=password('666');
修改完毕 再次登录用户 zhangsan 需要使用新密码
修改完毕 再次登录用户 zhangsan 需要使用新密码