1. 索引
索引是数据库中用于加速查询的数据结构,类似于书籍的目录。
索引的作用
大幅提高查询速度
加速表连接操作
保证数据唯一性(唯一索引)
优化排序和分组操作
创建主键约束( PRIMARY KEY )、唯一约束( UNIQUE )、外键约束( FOREIGN KEY )时,会自动创建对应列的索引。
-
创建索引
对于非主键、非唯一约束、非外键的字段,可以创建普通索引create index 索引名 on 表名(字段名);
-
查看索引
show index from 表名;
-
删除索引
drop index 索引名 on 表名;
对于 创建主键约束( PRIMARY KEY )、唯一约束( UNIQUE )、外键约束( FOREIGN KEY )时,自动创建对应列的索引,是无法手动删除的
自定义索引可以手动删除。
1. 按数据结构分类
如果索引不存在,当我们想要查找某个数据时,时间复杂度为O(N)
- 哈希索引
-
特点:
-
基于哈希表实现
-
精确匹配效率极高(O(1)时间复杂度)
-
不支持范围查询和排序
-
-
适用场景:等值查询(=, IN)
-
存储引擎支持:MEMORY引擎(默认)、InnoDB(自适应哈希索引)
- 二叉搜索树
有可能为单分支二叉搜索树则时间复杂度为O(N) - 红黑树
可以避免成为单分支二叉搜索树,时间复杂度为O(logN) - B 树(N叉搜索树)
- 特点:默认索引类型,多路平衡查找树
每个节点上有 M 个key ,划分出了 M+1 个区间
进行查询时,直接从根节点出发,判断当前要查的节点在哪个区间中,决定下一步往哪里走
进行 插入/删除操作时可能涉及到节点的拆分和合并
-
适用场景:
-
全值匹配(=)
-
范围查询(>, <, BETWEEN)
-
前缀匹配(LIKE 'abc%')
-
排序(ORDER BY)
-
分组(GROUP BY)
-
无法使用后缀匹配(like '%abc')
- 存储引擎支持:InnoDB、MyISAM、MEMORY等
- B+树
B 树的改进,针对数据库量身定做
特点:
- B+ 树也是一个N叉搜索树,每个节点上有 M 个key ,划分出了 M 个区间
- 父节点上的每个key都会以最大值的身份存储在子节点中,所以叶子节点这一层包含了整个树的数据全集
- B+ 树会使用链表将叶子节点串联起来
(此时就可以非常方便的完成数据集合的遍历,也可以很方便的从数据集合中按照范围取出一个子集)
优点:- N 叉搜索树,树的高度是有限的,降低 io 的次数
- 擅长范围查询
- 所有查询最终都要落到叶子节点,查询和查询之间的时间开销是稳定的
- 由于叶子节点是全集,会把行数据只存储在叶子节点上,非叶子节点只存储一个用来排序的key
- 在进行查询时,可以将硬盘中的非叶子节点加载到内存中,整体查询的比较过程就可以在内存中进行。
2. 事务
A给B转2000块钱,在转账过程中,A的账户扣除2000块钱,此时突然断网或数据库挂了,B的账号没有增加2000块钱,这是一个重大错误。
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。
在不同的环境中,都可以有事务。对应在数据库中,就是数据库事务。
使用:
( 1 )开启事务: start transaction;
( 2 )执行多条 SQL 语句
( 3 )回滚或提交: rollback/commit;
说明: rollback 即是全部失败, commit 即是全部成功。
事务特性:
- 原子性:通过回滚的方式,保证这一系列操作都能执行正确或恢复如初
- 一致性:事务执行前后,数据库从一个一致状态变到另一个一致状态
-
数据完整性约束不被破坏
-
业务规则保持一致
- 持久性:事务一旦提交,其结果就是永久性的 :即使系统崩溃,数据也不会丢失
通过redo log(重做日志)实现 - 隔离性:多个事务 并发执行时,一个事务的执行不应影响其他事务
通过锁机制和MVCC(多版本并发控制)实现
提高并发进程,数据库服务器执行效率变高,但会产生问题
数据隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
---|---|---|---|---|
READ UNCOMMITTED | 可能 | 可能 | 可能 | 可读取未提交数据 |
READ COMMITTED | 不可能 | 可能 | 可能 | 只能读取已提交数据(读加锁) |
REPEATABLE READ | 不可能 | 不可能 | 可能 | 同一事务多次读取结果一致(MySQL默认) (读写都加锁) |
SERIALIZABLE | 不可能 | 不可能 | 不可能 | 完全串行化执行 |
1. 查看和设置隔离级别
-- 查看当前隔离级别
SELECT @@transaction_isolation;
-- 设置会话级隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 设置全局级隔离级别(需重启生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2. 隔离级别问题演示
-
锁机制:
-
共享锁(S锁):读锁,其他事务可读不可写
-
排他锁(X锁):写锁,其他事务不可读写
-
(1) 脏读(Dirty Read)
一个事务A 在写数据的过程中,事务B读取了该数据,之后事务A又修改了该数据,导致事务B之前读取的数据时一个无效/过时的数据(也称为 脏数据)
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- 未提交
-- 会话2(可以读取到未提交的数据)
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT balance FROM accounts WHERE user_id = 1; -- 看到未提交的修改
解决方法:
在事务A进行写操作时加锁(即在事务A进行写操作时,事务B不可以进行读操作)
(2) 不可重复读(Non-repeatable Read)
事务A提交一份数据后,事务B进行多次读取,在事务B读取数据过程中,事务A又修改了该数据并提交,导致事务B读取的前后数据不同。
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT balance FROM accounts WHERE user_id = 1; -- 第一次读取
-- 会话2
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
COMMIT;
-- 会话1(同一事务内两次读取结果不同)
SELECT balance FROM accounts WHERE user_id = 1; -- 第二次读取
COMMIT;
解决方法:
在事务B进行读操作时加锁(即在事务B读操作时,事务B不可以进行写操作)
在事务A进行写操作时加锁(即在事务A进行写操作时,事务B不可以进行读操作)
(3) 幻读(Phantom Read)
事务A提交一份数据后,事务B进行多次读取,在事务B读取数据过程中,事务A又提交了一份数据,导致事务B读取的前后结果集不同。
-- 会话1
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM accounts WHERE balance > 1000; -- 返回2条记录
-- 会话2
INSERT INTO accounts VALUES(3, '王五', 1500);
COMMIT;
-- 会话1(同一事务内相同查询返回不同行数)
SELECT * FROM accounts WHERE balance > 1000; -- 返回3条记录
COMMIT;
解决方法:
引入串行化方式
总结:
( 1 )对于插入、删除数据频率高的表,不适用索引
( 2 )对于某列修改频率高的,该列不适用索引
( 3 )通过某列或某几列的条件查询频率高的,可以对这些列创建索引
3. JDBC 编程
编程语言,如 Java , C 、 C++ 、 Python 等
数据库,如 Oracle , MySQL , SQL Server 等
数据库驱动包:不同的数据库,对应不同的编程语言提供了不同的数据库驱动包,如: MySQL 提
供了 Java 的驱动包 mysql-connector-java ,需要基于 Java 操作 MySQL 即需要该驱动包。同样的,
要基于 Java 操作 Oracle 数据库则需要 Oracle 的数据库驱动包 ojdbc 。
JDBC ,即 Java Database Connectivity , java 数据库连接。是一种用于执行 SQL 语句的 Java API ,它是Java中的数据库连接规范。这个 API 由 java.sql.*,javax.sql.* 包中的一些类和接口组成,它为 Java 开发人员操作数据库提供了一个标准的API ,可以为多种关系数据库提供统一访问。
配置:
从中央仓库下载mysql的jdbc驱动包(jar)
将 jar 引入到项目中(拷贝,add as library)
高内聚:把相同/相关联的功能放在一起
高耦合:两个模块关联关系紧密,一个模块修改对另一个模块有很大影响
我们要求程序 高内聚,低耦合
jdbc开发案例
javax.sql jdbc 包

127.0.0.1 是一个特殊的IP地址,叫做"环回IP"(lookback)
每个机器上都有环回IP(127.0.0.1),只要把消息发送给这个IP,就等于发给自己

由于我们的jdbc程序和mysql服务器在同一个主机上,使用环回IP
import com.mysql.cj.jdbc.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class Domo {
public static void main(String[] args) throws SQLException {
Scanner scan = new Scanner(System.in);
System.out.print("请输入id:");
int id = scan.nextInt();
scan.nextLine();
System.out.print("请输入姓名:");
String name = scan.nextLine();
//1.创建DataSource(抽象接口)
//DataSource 描述"数据源头",即数据库服务器所在位置
DataSource dataSource = new MysqlDataSource();
//找到mysql服务器
//url(网址):网络上具体资源位置
((MysqlDataSource) dataSource).setURL("jdbc:mysql://127.0.0.1:3306/school?characterEncoding=utf8&useSSL=false");//向下转型
//认证用户名
//root 管理员mysql默认自带用户
((MysqlDataSource) dataSource).setUser("root");
//认证密码
((MysqlDataSource) dataSource).setPassword("123");
//2. 和数据库服务器建立连接
//getConnection()是jdbc中常见异常,如果执行sql或操作数据库过程中出现问题,一般都抛出这个异常
//connection连接
Connection connection=dataSource.getConnection();
//3. 构造sql(代码中的sql不需要写;)
String sql = "insert into student values(1,'lh'),(id,'"+ name +"'),(?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,id);
preparedStatement.setString(2,name);
//4. 把sql发送给服务器,返回值是一个整数,表示影响的行数
int n = preparedStatement.executeUpdate();
System.out.println(n);
//5. 释放资源,关闭连接(先申请资源,后释放)
preparedStatement.close();
connection.close();
}
}

Prepared :准备好的/预处理的
Statement: 语句
先解析检查 sql ,看看sql是否有问题,解析完毕后,会得到结构化数据,直接把解析好的结构化数据发送给数据库,服务器就省下了这部分解析工作
如果直接把字符串格式的 sql 发给mysql 服务器,mysql 服务器就需要对 sql 解析和校验,但 mysql 服务器是服务于多个客户端,此时总的开销和总的负担就很大。
import com.mysql.cj.jdbc.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Domo2 {
public static void main(String[] args) throws SQLException {
//建立DataSouece(抽象接口)
DataSource dataSource = new MysqlDataSource();
((MysqlDataSource) dataSource).setURL("jdbc:mysql://127.0.0.1:3306/school?characterEncoding=utf8&useSSL=false");
((MysqlDataSource) dataSource).setUser("root");
((MysqlDataSource) dataSource).setPassword("123");
//2. 和数据库服务器建立连接
Connection connection = dataSource.getConnection();
//3. 构造sql
String sql = "select * from student";
PreparedStatement statement = connection.prepareStatement(sql);
//4. 执行sql
//ResultSet 表示查询的结果集合
ResultSet resultSet = statement.executeQuery();
//5. 遍历结果集合
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.println("id = "+id +", name ="+ name);
}
//释放资源
resultSet.close();
statement.close();
connection.close();
}
}
