
Mysql数据库
MySQL 数据库
数据库引擎的类型
MYISAM | INNODB | |
---|---|---|
事务支持 | 不支持 | 支持 |
数据行锁定 | 不支持 | 支持 |
外键约束 | 不支持 | 支持 |
全文索引 | 支持 | 不支持 |
表空间的大小 | 较小 | 较大,约为 2 倍 |
常规使用操作:
- MYISAM 节约空间,速度较快
- INNODB 安全性高,事务的处理,多表多用户操作
所有的数据库文件都存在 data 目录下,一个文件夹对应一个数据库,本质还是文件的存储!
MySQL 引擎在物理文件上的区别:
- InnoDB 在数据库表中只有一个
*.fm
文件,以及上级目录下的ibdata1
文件; - MYISAM 对应文件
- *.frm 表结构的定义文件
- *.MYD 数据文件(data)
- *.MYI 索引文件(index)
三大范式
前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。
而通常我们用的最多的就是第一范式(1NF)、第二范式(2NF)、第三范式(3NF),也就是本文要讲的“三大范式”。
第一范式(1NF):要求数据库表的每一列都是不可分割的原子数据项。
举例说明:
在上面的表中,“家庭信息”和“学校信息”列均不满足原子性的要求,故不满足第一范式,调整如下:
可见,调整后的每一列都是不可再分的,因此满足第一范式(1NF);
第二范式(2NF):在 1NF 的基础上,非码属性必须完全依赖于候选码(在 1NF 基础上消除非主属性对主码的部分函数依赖)
第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。
举例说明:
在上图所示的情况中,同一个订单中可能包含不同的产品,因此主键必须是“订单号”和“产品号”联合组成,
但可以发现,产品数量、产品折扣、产品价格与“订单号”和“产品号”都相关,但是订单金额和订单时间仅与“订单号”相关,与“产品号”无关,
这样就不满足第二范式的要求,调整如下,需分成两个表:
第三范式(3NF):在 2NF 基础上,任何非主属性不依赖于其它非主属性(在 2NF 基础上消除传递依赖)
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
举例说明:
上表中,所有属性都完全依赖于学号,所以满足第二范式,但是“班主任性别”和“班主任年龄”直接依赖的是“班主任姓名”,
而不是主键“学号”,所以需做如下调整:
这样以来,就满足了第三范式的要求。
ps:如果把上表中的班主任姓名改成班主任教工号可能更确切,更符合实际情况,不过只要能理解就行。
(原文地址:https://www.cnblogs.com/wsg25/p/9615100.html)
JDBC
程序通过数据库驱动 JDBC 和数据库打交道;
SUN 公司为了简化开发人员的(对数据库的统一)操作,提供了一个(JAVA 操作数据库的)规范,俗称 JDBC;
对于开发人员来说,只要掌握 jdbc 就扣的操作即可;
//第一个jdbc程序
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver"); //固定写法,加载驱动
//2.用户信息url
String url = "jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf8&useSSL=false";
String name = "root";
String password = "123456";
//3.连接成功, 数据库对象 Connection 代表数据库
Connection connection = DriverManager.getConnection(url, name, password);
//4.执行SQL的对象 Statement 执行SQL的对象
Statement statement = connection.createStatement();
//5.执行SQL的对象 去执行 SQL ,可能存在结果, 查看返回对象
String sql = "SELECT * FROM user";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
System.out.println("id=" + resultSet.getObject("id"));
System.out.println("username=" + resultSet.getObject("username"));
System.out.println("address=" + resultSet.getObject("address"));
System.out.println("=====================================");
}
//6.释放连接
resultSet.close();
statement.close();
connection.close();
}
Statement
对象
- jdbc 中的 Statement 对象用于向数据库发送 SQL 语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可;
- Statement 对象的 executeUpdate 方法用于向数据库发送增、删、改的 SQL 语句,executeUpdate 执行完后,将会返回一个证书(即 SQL 语句影响的行数);
- Statement.executeQuery 方法用于向数据库发送查询语句,executeQuery 方法返回代表查询结果的 ResultSet 对象。
工具类 JdbcUtils
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static {
try {
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(in);
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
//驱动只用加载一次
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
//释放连接
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null) {
try {
st.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if (conn!=null) {
try {
conn.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
增删改都是st.executeUpdate(sql)
查询st.executeQuery(sql);
rs = st.executeQuery(sql);
while (rs.next()){
System.out.println(rs.getObject("id"));
System.out.println(rs.getObject("username"));
System.out.println(rs.getObject("address"));
}
SQL 注入
sql 存在漏洞,会被攻击导致数据泄露,SQL 会被拼接 or
public static void main(String[] args) {
login(" 'or '1=1", " 'or '1=1");
}
public static void login(String username, String password) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection(); //获取数据库连接
st = conn.createStatement();
String sql = "select username,address from user where username='" + username + "' and address='"+password+"'";
rs = st.executeQuery(sql);
while (rs.next()){
System.out.println(rs.getObject("username"));
System.out.println(rs.getObject("address"));
System.out.println("=====================================");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.release(conn, st, rs);
}
}
preparedStatement
对象
preparedStatement
防止注入的本质是它将传递进来的参数当做字符,假设其中存在转义字符,比如(`)会被直接转义;
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pst = null;
try {
conn = JdbcUtils.getConnection();
String sql = "INSERT INTO user(id,username,address) VALUES(?,?,?)";
pst = conn.prepareStatement(sql); //预编译SQL,重写SQL,然后不执行
pst.setInt(1, 22);
pst.setString(2, "wangwu");
pst.setString(3, "成华大道");
pst.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn, pst, null);
}
}
Idea 连接 mysql 数据库
- 连接错误,修改
Advanced
配置中的ServerTimeZone
为UTC
; - 连接成功后可以在
Schemas
中勾选要用到的数据库;
数据库表事务
原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户 A 和用户 B 两者的钱加起来一共是 5000,那么不管 A 和 B 之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是 5000,这就是事务的一致性。
隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务 T1 和 T2,在事务 T1 看来,T2 要么在 T1 开始之前就已经结束,要么在 T1 结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用 JDBC 操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
以上介绍完事务的四大特性(简称 ACID),现在重点来说明下事务的隔离性,当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:
脏读
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户 A 向用户 B 转账 100 元,对应 SQL 命令如下
update account set money=money+100 where name=’B’; (此时A通知B)
update account set money=money - 100 where name=’A’;
当只执行第一条 SQL 时,A 通知 B 查看账户,B 发现确实钱已到账(此时即发生了脏读),而之后无论第二条 SQL 是否执行,只要该事务不提交,则所有操作都将回滚,那么当 B 以后再次查看账户时就会发现钱其实并没有转。
不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
例如事务 T1 在读取某一数据,而事务 T2 立马修改了这个数据并且提交事务给数据库,事务 T1 再次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据 A 和 B 依次查询就可能不同,A 和 B 就可能打起来了……
虚读(幻读)
幻读是事务非独立执行时发生的一种现象。例如事务 T1 对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务 T2 又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务 T1 的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务 T2 中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
现在来看看 MySQL 数据库为我们提供的四种隔离级别:
- Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
- Repeatable read (可重复读):可避免脏读、不可重复读的发生。
- Read committed (读已提交):可避免脏读的发生。
- Read uncommitted (读未提交):最低级别,任何情况都无法保证。
以上四种隔离级别最高的是 Serializable 级别,最低的是 Read uncommitted 级别,当然级别越高,执行效率就越低。像 Serializable 这样的级别,就是以锁表的方式(类似于 Java 多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在 MySQL 数据库中默认的隔离级别为 Repeatable read (可重复读)。
在 MySQL 数据库中,支持上面四种隔离级别,默认的为 Repeatable read (可重复读);而在 Oracle 数据库中,只支持 Serializable (串行化)级别和 Read committed (读已提交)这两种级别,其中默认的为 Read committed 级别。
在 MySQL 数据库中查看当前事务的隔离级别:
select @@tx_isolation;
在 MySQL 数据库中设置事务的隔离 级别:
set [glogal | session] transaction isolation level 隔离级别名称;
set tx_isolation=’隔离级别名称;’
java 代码实现事务
- 开启事务
onn.setAutoCommit(false);
- 一组业务执行完毕,提交事务
conn.commit();
- 可以在 catch 语句中显示的定义回滚语句,但默认的,失败就是会回滚的;
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//关闭数据库自动提交,自动会开启事务
conn.setAutoCommit(false); //开启事务
String sql1 = "UPDATE ACCOUNT SET money=money-100 where name='A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
int x = 1/0; //模拟出错,失败自动回滚
String sql2 = "UPDATE ACCOUNT SET money=money+100 where name='B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
//事务完毕,提交事务
conn.commit();
System.out.println("transaction finished");
} catch (SQLException e) {
try {
conn.rollback(); //回滚
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
数据库连接池
数据库连接 - 执行完毕 - 释放
连接 - 释放十分浪费系统资源
池化技术:准备一些预先的资源,过来就连接预先准备好的
最小连接数(常用连接数):10
最大连接数:100 业务最高承载上限,超出最大连接数的等待
等待超时: 100ms
编写连接池,实现一个接口,DataSource
开源数据源实现:
- DBCP
- C3P0
- druid 德鲁伊 阿里巴巴
使用这些数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码;区别是性能上的区别;
DBCP
需要用到的 jar 包
commons-dbcp-1.4 commons-pool-1.6
C3P0
需要用到的 jar 包
c3p0-0.9.5.5 mchange-commons-java-0.2.19
无论使用什么数据源,本质是一样的,DataSource 接口不会变,方法就不会变。
druid 结合 springboot 使用。