layout: post
#标题配置
title: MySQL数据库
#时间配置
date: 2022-01-20 20:45:00 +0800
#目录配置
categories: 数据库
#标签配置
tag: 学习笔记
- content
{:toc}
MySQL
一.基本概念以及安装
1.数据库的介绍
- 用于存储和管理数据的仓库
- 英文单词为DataBase。简称DB!
- 它的存储空间很大,可以存放百万条、千万条、上亿条数据
- 使用一种统一的方式操作教据库——SQL
- MySQL是一个最流行的关系型数据库管理系统之一。由瑞典MySQL AB公司开发,后被Oracle公司收购.
- 关系型数据库是将数据保存在不同的数据表中,而不是将所有数据放在一个大仓库内,而且表与表之间还可以有关联关系。这样就提高了访问速度以及提高了灵活性。
- MySQL所使用的SQL语句是用于访问数据库最常用的标住化语言。
- 免费(6版本之前)
2.MySQL的安装(Linux)
- 用sftp上传文件到Linux的package下
- 解压缩
tar -xzvf mysql-5.7.21-linux-glibc2.12-x86_64.tar.gz -C /usr/local/
- 修改目录名称
mv mysql-5.7.21-linux-glibc2.12-x86_64 mysql
- 进入mysql目录,在该目录下创建data目录(用于存放日志的目录)
mkdir data
- 创建mysql的用户群组
groupadd mysql
- 创建mysql群组下的用户(第一个mysql是群组名称,第二个是用户名称)
useradd -r -s /sbin/nologin -g mysql mysql -d /usr/local/mysql
- 为新创建的mysql用户授权
chown -R mysql.mysql /usr/local/mysql
- 初始化mysql数据库(在mysql目录下的bin目录中进行)
!!!注意在初始化mysql数据库的时候会出现一个默认的登录密码,记录下来,后面需要用到!!!
注意启动的是 mysqld 程序 而不是 mysql
(若初始化时报错 error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory,出现该问题首先检查该链接库文件有没有安装使用 命令进行核查
rpm -qa|grep libaio
运行该命令后发现系统中无该链接库文件
使用命令,yum install libaio-devel.x86_64
安装成功后,继续运行数据库的初始化命令,提示成功。)
./mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data
A temporary password is generated for root@localhost: 初始密码
- 修改my.cnf文件
vim /etc/my.cnf
修改为:
[mysqld]
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
socket=/tmp/mysql.sock
user=mysql
port=3306
character_set_server=utf8
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0
[mysqld_safe]
log-error=/usr/local/mysql/data/error.log
pid-file=/usr/local/mysql/data/mysqld.pid
tmpdir=/tmp
- 把mysql服务添加到系统服务中(在mysql目录下进行)
将mysql目录下的support-files目录中的mysql.server文件复制到路径/etc/init.d/mysqld
cp support-files/mysql.server /etc/init.d/mysqld
- 编辑mysqld文件
vim /etc/init.d/mysqld
完善=后面的内容。其余不动
basedir=/usr/local/mysql
datadir=/usr/local/mysql/data
mysqld_file_path=/usr/local/mysql/data/mysqld_pid
- 打开3306端口:
firewall-cmd --zone=public --add-port=3306/tcp --permanent
firewall-cmd --reload
- 更改连接权限(mysql服务中执行,服务中输入exit退出)
查看所有数据库:show databases;
使用mysql数据库:use mysql;
查看mysql数据库下的所有表名:show tables; 可以看到mysql数据库下有一个user表
执行:
select host from user where user='root';
update user set host = '%' where user ='root';
提交与刷新数据库:flush privileges;
- 启动mysql服务:
service mysqld start
15.登录mysql(在mysql的bin目录下进行)l
mysql -u root -p
- 修改root账户的密码(mysql服务中执行)
set password for 'root'@localhost=password('新密码');
- 启用远程访问(12,13步已完成)
二.DDL操作数据库和数据表
1.数据库、数据表、数据的关系
- MySQL服务器中可以创建多个数据库
- 每个数据库中可以包含多张数据表
- 每个数据表可以存储多条数据记录
- 客户端通过数据库管理系统来操作MySQL数据库
2.SQL的介绍
-
SOL(Structured Query Language):结构化查询语言。其实就是定义了操作所有关系型数据库的一种规则。
-
通用语法规则
SQL语句可以单行或多行书写,以分号结尾
可使用空格和缩进来增强语句的可读性
MySQL数据库的SQL语句不区分大小写,关键字建议使用大写
单行注释:–注释内容 #注释内容(MySQL特有)
多行注释: /* 注释内容 */
-
SQL分类
- DDL(Data Definition Language):数据定义语言。用来操作数据库,表,列等。
- DML(Data ManipulationLanguage):数据操作语言。用来对数据库中表的数据进行增删改
- DQL(Data QueryLanguage):数据查询语言。用来查询数据库中表的记录(数据)。
- DCL(DataControl Language):数据控制语言。用来定义数据库的访问权限和安全级别,及创建用户。
3.DDL查询和创建数据库
- 查询所有数据库
SHOW DATABASES;
- 查询数据库的创建语句
SHOW CREATE DATABASE 数据库名称;
- 创建数据库
CREATE DATABASE 数据库名称;
- 创建数据库(判断,如果不存在则创建)
CREATE DATABASE IF NOT EXISTS 数据库名称;
- 创建数据库
CREATE DATABASE 数据库名称 CHARACTERSET 字符集名称;
4.DDL修改、删除、使用数据库
- 修改数据库
ALTER DATABASE 数据库名称 CHARACTER SET 字符集名称;
- 删除数据库
DROP DATABASE 数据库名称;
- 删除数据库(判断,如果存在则删除)
DROP DATABASE IF EXISTS 数据库名称;
- 使用数据库
USE 数据库名称;
- 查看当前正在使用的数据库
SELECT DATABASE();
5.DDL查询数据表
- 查询所有的数据表
SHOW TABLES;
- 查询表结构
DESC 表名;
- 查询字符集
SHOW TABLE STATUS FROM 库名 LIKE '表名';
6.DDL创建数据表
- 创建数据表
CREATE TABLE 表名(
列名 数据类型 约束,
列名 数据类型 约束,
...
列名 数据类型 约束,
);
例:
-- 创建一个product商品表(商品编号、商品名称、商品价格、商品库存、上架时间)
CREATE TABLE product(
id INT,
NAME VARCHAR(20),
price DOUBLE,
stock INT,
insert_time DATE
);
- 数据类型(常用)
int:整数类型
double:小数类型
data:日期类型。包含年月日,格式yyyy-MM-dd
datatime:日期类型。包含年月日时分秒,格式yyyy-MM-dd HH:mm:ss
timestamp:时间戳类型。包含年月日时分秒,格式yyyy-MM-dd HH:mm:ss
*如果不给该列赋值、或赋值为null,则默认使用当前系统时间自动赋值
varchar(长度):字符串类型
7.DDL修改数据表
- 修改表名
ALTER TABLE 表名 RENAME TO 新表名;
例:
-- 给product表重命名为product2
ALTER TABLE product RENAME TO product2;
- 修改表的字符集
ALTER TABLE 表名 CHARACTERSET 字符集名称;
例:
-- 修改product2数据表字符集为gbk
ALTER TABLE product2 CHARACTER SET gbk;
- 单独添加一列
ALTER TABLE 表名 ADD 列名 数据类型;
例:
-- 给product2添加一列color
ALTER TABLE product2 ADD color VARCHAR(10);
- 修改某列的数据类型
ALTER TABLE 表名 MODIFY 列名 新数据类型;
例:
-- 将color数据类型修改为int
ALTER TABLE product2 MODIFY color INT;
- 修改列名和数据类型
ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
例:
-- 将color修改为address
ALTER TABLE product2 CHANGE color address VARCHAR(200);
- 删除某一列
ALTER TABLE 表名 DROP 列名;
例:
-- 删除address列
ALTER TABLE product2 DROP address;
8.DDL删除数据表
- 删除数据表
DROP TABLE 表名;
例:
-- 删除product2表
DROP TABLE product2;
- 删除数据表(判断,如果存在则删除)
DROP TABLE IF EXISTS 表名;
例:
-- 删除product2表(判断,如果存在则删除)
DROP TABLE IF EXISTS product2;
三.DML表数据的增删改
1.DML新增表数据
- 给指定列添加数据
INSERT INFO 表名(列名1,列名2,...)VALUES(值1,值2,...);
例:
-- 向product表中添加指定列数据
INSERT INTO product (id,NAME,price) VALUES (2,'电脑',3999.99);
- 给全部列添加数据
INSERT INFO 表名 VALUES(值1,值2,...);
例:
-- 默认给全部列添加数据
INSERT INTO product VALUES (3,'冰箱',1500,35,'2021-10-01');
- 批量添加数据
INSERT INFO 表名 (列名1,列名2,...) VALUES (值1,值2,...),(值1,值2,...),...;
INSERT INFO 表名 VALUES(值1,值2,...),(值1,值2,...),...;
例:
-- 批量添加数据
INSERT INTO product VALUES (4,'电视机',2000,20,'2025-01-01'),(5,'空调',3000,4,'2021-01-01');
列名和值的数量以及数据类型要对应,除了数字类型,其他数据类型的数据都需要加引号(单引双引都行,推荐单引)。
2.DML修改和删除表数据
- 修改表中的数据
UPDATE 表名 SET 列名1=值1,列名2=值2,...[WHERE 条件];
例:
-- 修改手机的价格为3500
UPDATE product SET price=3500 WHERE NAME='手机';
-- 修改电脑的价格为1800、库存为36
UPDATE product SET price=1800,stock=36 WHERE NAME='电脑';
修改语句中必须加条件,如果不加条件,则会将所有数据都修改。
- 删除表中的数据
DELETE FROM 表名 [WHERE 条件];
例:
-- 删除product表中的空调信息
DELETE FROM product WHERE NAME='空调';
-- 删除product表中库存为10的商品信息
DELETE FROM product WHERE stock=10;
删除语句中必须加条件,如果不加条件,则会将所有数据都删除。
四.DQL表数据的查询
1.查询语法
SELECT
字段列表
FROM
表名列表
WHERE
条件列表
GROUP BY
分组字段
HAVING
分组后的过滤条件
ORDER BY
排序
LIMIT
分页
2.查询全部
- 查询全部的表数据
SELECT * FROM 表名;
例:
-- 查询product表所有数据
SELECT * FROM product;
- 查询指定字段的表数据
SELECT 列名1,列名2,... FROM 表名;
例:
-- 查询名称、价格、品牌
SELECT NAME,price,brand FROM product;
- 去除重复查询
SELECT DISTINCT 列名1,列名2,... FROM 表名;
例:
-- 查询品牌、去除重复
SELECT DISTINCT brand FROM product;
- 计算列的值(四则运算)
SELECT 列名1 运算符(+ - * /) 列名2 FROM 表名;
如果某一列为null,可以进行替换
ifnull(表达式1,表达式2)
表达式1:想替换的列
表达式2:想替换的值
例:
-- 查询商品库存,库存数量在原有的基础上加10
SELECT NAME,stock+10 FROM product;
-- 查询商品名称和库存,库存数量在原有基础上加10。进行null值判断
SELECT NAME,IFNULL(stock,0)+10 FROM product;
- 起别名查询
SELECT 列名 AS 别名 FROM 表名;
例:
-- 查询商品名称和库存,库存数量在原有基础上加10。进行null值判断。起别名为getSum
SELECT NAME,IFNULL(stock,0)+10 AS getSum FROM product;
-- AS可以省略
SELECT NAME,IFNULL(stock,0)+10 getSum FROM product;
3.条件查询
- 查询条件分类
符号 | 功能 |
---|---|
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
= | 等于 |
<>或!= | 不等于 |
BETWEEN…AND… | 在某个范围之内(都包含) |
IN(…) | 多选一 |
LIKE 占位符 | 模糊查询 _单个任意字符 %多个任意字符 |
IS NULL | 是NULL |
IS NOT NULL | 不是NULL |
AND或&& | 并且 |
OR或|| | 或者 |
NOT或! | 非,不是 |
例:
-- 查询库存大于20的商品信息
SELECT * FROM product WHERE stock > 20;
-- 查询品牌为华为的商品信息
SELECT * FROM product WHERE brand='华为';
-- 查询金额在4000~6000之间的商品信息
SELECT * FROM product WHERE price >=4000 AND price <= 6000;
SELECT * FROM product WHERE price BETWEEN 4000 AND 6000;
-- 查询库存为14/30/23的商品信息
SELECT * FROM product WHERE stock=14 OR stock=30 OR stock=23;
SELECT * FROM product WHERE stock IN(14,30,23)
-- 查询库存为null的商品信息
SELECT * FROM product WHERE stock IS NULL;
-- 查询库存不为null的商品信息
SELECT * FROM product WHERE stock IS NOT NULL;
-- 查询名称以小米为开头的商品信息
SELECT * FROM product WHERE NAME LIKE '小米%';
-- 查询名称第二个字是为的商品信息
SELECT * FROM product WHERE NAME LIKE '_为%';
-- 查询名称为四个字符的商品信息
SELECT * FROM product WHERE NAME LIKE '____';
-- 查询名称中包含电脑的信息
SELECT * FROM product WHERE NAME LIKE '%电脑%'
4.聚合函数查询
-
聚合函数的介绍
将一列数据作为一个整体,进行纵向的计算
-
聚合函数的分类
函数名 | 功能 |
---|---|
count(列名) | 统计数量(一般选用不为null的列) |
max(列名) | 最大值 |
min(列名) | 最小值 |
sum(列名) | 求和 |
avg(列名) | 平均值 |
- 聚合函数查询语法
SELECT 函数名(列名) FROM 表名 [WHERE 条件]
例:
-- 计算product表中总记录条数
SELECT COUNT(*) FROM product;
-- 获取最高价格
SELECT MAX(price) FROM product;
-- 获取最低库存
SELECT MIN(stock) FROM product;
-- 获取总库存数量
SELECT SUM(stock) FROM product;
-- 获取品牌为苹果的总库存数量
SELECT SUM(stock) FROM product WHERE brand='苹果';
-- 获取品牌为小米的平均商品价格
SELECT AVG(price) FROM product WHERE brand='小米';
5.排序查询
- 排序查询语法
SELECT 列名列表 FROM [WHERE 条件] ORDER BY 列名 排序方式,列名 排序方式,...;
排序方式:ASC-升序,DESC-降序
如果有多个排序条件,只有当前边的条件值一样时,才会判断第二条件
例:
-- 按照库存升序排序
SELECT * FROM product ORDER BY stock ASC;
-- 查询名称中包含数据的商品信息。按照金额降序排序
SELECT * FROM product WHERE NAME LIKE '%手机%' ORDER BY price DESC;
-- 按照金额升序排序,如果金额相同,按照库存降序排列
SELECT * FROM product ORDER BY price ASC,stock DESC;
6.分组查询(先过滤条件再执行分组或者排序)
- 分组查询语法
SELECT 列名列表
FROM 表名
[WHERE 条件]
GROUP BY 分组列名
[HAVING 分组后的条件过滤]
[ORDER BY 排序列名 排序方式];
-- 按照品牌分组,获取每组商品的总金额
SELECT brand,SUM(price) FROM product GROUP BY brand;
-- 对金额大于4000元的商品,按照品牌分组,获取每组商品的总金额
SELECT brand,SUM(price) FROM product WHERE price > 4000 GROUP BY brand;
-- 对金额大于4000元的商品,按照品牌分组,获取每组商品的总金额,只显示总金额大于7000元的
SELECT brand,SUM(price) getSum FROM product WHERE price > 4000 GROUP BY brand HAVING getSum > 7000;
-- 对金额大于4000元的商品,按照品牌分组,获取每组商品的总金额,只显示总金额大于7000元的、并按总金额的降序排列
SELECT brand,SUM(price) getSum FROM product
WHERE price > 4000
GROUP BY brand
HAVING getSum > 7000
ORDER BY getSum DESC;
7.分页查询
- 分页查询语法
SELECT 列名列表 FROM 表名
[WHERE 条件]
[GROUP BY 分组列名]
[HAVING 分组后的条件过滤]
[ORDER BY 排序列名 排序方式]
LIMIT 当前页数,每页显示的条数;
例:
-- 每页显示三条数据
-- 第一页 当前页数=(1-1)*3
SELECT * FROM product LIMIT 0,3;
-- 第二页 当前页数=(2-1)*3
SELECT * FROM product LIMIT 3,3;
-- 第三页 当前页数=(3-1)*3
SELECT * FROM product LIMIT 6,3;
五.约束
1.约束的介绍
-
什么是约束
对表中的数据进行限定,保证数据的正确性、有效性、完整性!
-
约束的分类
约束 | 作用 |
---|---|
PRIMARY KEY | 主键约束 |
PRIMARY KEY AUTO_INCREMENT | 主键自增 |
UNIQUE | 唯一约束 |
NOT NULL | 非空约束 |
FOREIGN KEY | 外键约束 |
FOREIGN KEY ON UPDATE CASCADE | 外键级联更新 |
FOREIGN KEY ON DELETE CASCADE | 外键级联删除 |
2.主键约束
-
主键约束的特点
主键约束默认包含非空和唯一两个功能
一张表只能有一个 主键
主键一般用于表中数据的唯一标识
-
建表时添加主键约束
CREATE TABLE 表名(
列名 数据类型 PRIMARY KEY,
...
列名 数据类型 约束
)
- 删除主键约束
ALTER TABLE 表名 DROP PRIMARY KEY;
- 建表后单独添加主键约束
ALTER TABLE MODIFY 列名 数据类型 PRIMARY KEY;
例:
-- 创建学生表(编号、姓名、年龄)
CREATE TABLE student(
id INT PRIMARY KEY,
NAME VARCHAR(30),
age INT
);
-- 查询学生表的详细信息
DESC student;
-- 添加数据
INSERT INTO student VALUES (1,'张三',23);
INSERT INTO student VALUES (2,'李四',24);
-- 删除主键
ALTER TABLE student DROP PRIMARY KEY;
-- 建表后单独添加主键约束
ALTER TABLE student MODIFY id INT PRIMARY KEY;
3.主键自增约束
- 建表时添加主键自增约束
CREATE TABLE 表名(
列名 数据类型 PRIMARY KEY AUTO_INCREMENT
...
列名 数据类型 约束
);
- 删除主键自增约束
ALTER TABLE 表名 MODIFY 列名 数据类型;
- 建表后单独添加主键自增约束
ALTER TABLE 表名 MODIFY 列名 数据类型 AUTO_INCREMENT;
MySQL中的自增约束,必须配合键的约束一起使用!
例:
-- 创建学生表(编号、姓名、年龄) 编号设为主键自增
CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(30),
age INT
);
-- 查询学生表的详细信息
DESC student;
-- 添加数据
INSERT INTO student VALUES (NULL,'张三',23),(NULL,'李四',24);
-- 删除自增约束
ALTER TABLE student MODIFY id INT;
-- 建表后单独添加自增约束
ALTER TABLE student MODIFY id INT AUTO_INCREMENT;
4.唯一约束
- 建表时添加唯一约束
CREATE TABLE 表名(
列名 数据类型 UNIQUE,
...
列名 数据类型 约束
);
- 删除唯一约束
ALTER TABLE 表名 DROP INDEX 列名;
- 建表后单独添加唯一约束
ALTER TABLE 表名 MODIFY 列名 数据类型 UNIQUE;
例:
-- 创建学生表(编号、姓名、年龄) 编号设为主键自增,年龄设为唯一
CREATE TABLE student(
id INT PRIMARY KEY auto_increment,
NAME VARCHAR(30),
age INT UNIQUE
);
-- 查询学生表的详细信息
DESC student;
-- 添加数据
INSERT INTO student VALUES (NULL,'张三',23);
INSERT INTO student VALUES (NULL,'李四',23);
-- 删除唯一约束
ALTER TABLE student DROP INDEX age;
-- 建表后单独添加唯一约束
ALTER TABLE student MODIFY age INT UNIQUE;
5.非空约束
- 建表时添加非空约束
CREATE TABLE 表名(
列名 数据类型 NOT NULL,
...
列名 数据类型 约束
)
- 删除非空约束
ALTER TABLE 表名 MODIFY 列名 数据类型;
- 建表后单独添加非空约束
例:
ALTER TABLE 表名 MODIFY 列名 数据类型 NOT NULL;
-- 创建学生表(编号、姓名、年龄) 编号设为主键自增,姓名设为非空,年龄设为唯一
CREATE TABLE student(
id INT PRIMARY KEY auto_increment,
NAME VARCHAR(30) NOT NULL,
age INT UNIQUE
);
-- 查询学生表的详细信息
DESC student;
-- 添加数据
INSERT INTO student VALUES (NULL,'王志凯',23);
-- 删除非空约束
ALTER TABLE student MODIFY NAME VARCHAR(30);
INSERT INTO student VALUES (NULL,NULL,24);
-- 建表后单独添加非空约束
ALTER TABLE student MODIFY NAME VARCHAR(30) NOT NULL;
6.外键约束
-
为什么要有外键约束?
当表与表之间的数据有相关联性的时候,如果没有相关的数据约束,则无法保证数据的准确性!
-
外键约束的作用
让表与表之间产生关联关系,从而保证数据的准确性!
-
建表时添加外键约束
CREATE TABLE 表名(
列名 数据类型 约束,
...
CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名 (主表主键列名)
)
- 删除外键约束
ALTER TABLE 表名 DROP FOREIGN KEY 外键名;
- 建表后单独添加外键约束
ALTER TABLE 表名 ADD
CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主键列名);
CREATE DATABASE db2;
USE db2;
CREATE TABLE USER(
id INT PRIMARY KEY auto_increment,
NAME VARCHAR(20) NOT NULL
);
INSERT INTO USER VALUES (NULL,'张三'),(NULL,'李四');
CREATE TABLE orderlist(
id INT PRIMARY KEY auto_increment,
number VARCHAR(20) NOT NULL,
uid INT,
CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id)
);
INSERT INTO orderlist VALUES (NULL,'hm001',1),(NULL,'hm002',1),
(NULL,'hm003',2),(NULL,'hm004',2);
-- 添加一个订单,但是没有真实用户,添加失败
INSERT INTO orderlist VALUES (NULL,'hm005',3);
-- 删除李四用户
DELETE FROM USER WHERE NAME=‘李四’;
-- 删除外键约束
ALTER TABLE orderlist DROP FOREIGN KEY ou_fk1;
-- 添加外键约束
ALTER TABLE orderlist CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id);
7.外键的级联更新和级联删除
- 添加级联更新
ALTER TABLE 表名 ADD
CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主键列名)
ON UPDATE CASCADE;
- 添加级联删除
ALTER TABLE 表名 ADD
CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主键列名)
ON DELETE CASCADE;
- 同时添加级联更新和级联删除
ALTER TABLE 表名 ADD
CONSTRAINT 外键名 FOREIGN KEY (本表外键列名) REFERENCES 主表名(主键列名)
ON UPDATE CASCADE ON DELETE CASCADE;
六.多表操作
1.多表介绍
- 多表概念
说白了就是多张数据表,而表与表之间是可以有一定的关联关系,这种关联关系通过外键约束实现.
-
多表的分类
-
一对一
-
一对多
-
多对多
-
2.一对一
-
适用场景
人和身份证。一个人只有一个身份证,一个身份证只能对应一个人
-
建表原则
在任意一个表建立外键,去关联另外一个表的主健。
USE db3;
CREATE TABLE person(
id INT PRIMARY KEY auto_increment,
NAME VARCHAR(20)
);
INSERT INTO person VALUES (NULL,'张三'),(NULL,'李四');
CREATE TABLE card(
id INT PRIMARY KEY auto_increment,
number VARCHAR(20) UNIQUE NOT NULL,
pid INT UNIQUE,
CONSTRAINT cp_fk1 FOREIGN KEY (pid) REFERENCES person(id)
);
INSERT INTO card VALUES (NULL,'12345',1),(NULL,'56789',2);
3.一对多
-
适用场景
用户和订单。一个用户可以有多个订单。
商品分类和商品。一个分类下可以有多个商品。
-
建表原则
在多的一方,建立外健约束,来关联一的一方主健
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
INSERT INTO USER VALUES (NULL,'张三'),(NULL,'李四');
CREATE TABLE orderlist(
id INT PRIMARY KEY AUTO_INCREMENT,
number VARCHAR(20),
uid INT,
CONSTRAINT ou_fk1 FOREIGN KEY (uid) REFERENCES USER(id)
);
INSERT INTO orderlist VALUES (NULL,'hm001',1),(NULL,'hm002',1),(NULL,'hm003',2),(NULL,'hm004',2);
CREATE TABLE category(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10)
);
INSERT INTO category VALUES (NULL,'手机数码'),(NULL,'电脑办公');
4.多对多
-
适用场景
学生和课程。一个学生可以选择多个课程,一个课程也可以被多个学生选择。
-
建表原则
需要借助第三张中间表,中间表至少包含两个列。这两个列作为中间表的外键,分别关联两张表的主键。
CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
INSERT INTO student VALUES (NULL,'张三'),(NULL,'李四');
CREATE TABLE course(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10)
);
INSERT INTO course VALUES (NULL,'语文'),(NULL,'数学');
CREATE TABLE stu_course(
id INT PRIMARY KEY AUTO_INCREMENT,
sid INT,
cid INT,
CONSTRAINT sc_fk1 FOREIGN KEY (sid) REFERENCES student(id),
CONSTRAINT sc_fk2 FOREIGN KEY (cid) REFERENCES course(id)
);
INSERT INTO stu_course VALUES (NULL,1,1),(NULL,1,2),(NULL,2,1),(NULL,2,2);
5.多表查询
- 多表查询分类
- 内连接查询
- 外连接查询
- 子查询
- 自关联查询
6.内连接查询
- 查询原理
内连接查询的是两张表有交集的部分数据(有主外键关联的数据)。
-
查询语法
- 显式内连接
SELECT 列名 FROM 表名1 [INNER] JOIN 表名2 ON 条件;
- 隐式内连接
SELECT 列名 FROM 表名1,表名2 WHERE 条件;
例: -- 查询用户信息和对应的订单信息 SELECT * FROM USER INNER JOIN orderlist ON orderlist.uid = user.id; -- 查询用户信息和对应的订单信息,起别名 SELECT * FROM USER u INNER JOIN orderlist o ON o.uid = u.id; -- 查询用户姓名,年龄,订单编号 SELECT u.name, -- 用户姓名 u.age, -- 用户年龄 o.number -- 订单编号 FROM USER u -- 用户表 INNER JOIN orderlist o -- 订单表 ON o.uid=u.id; SELECT u.name, -- 用户姓名 u.age, -- 用户年龄 o.number -- 订单编号 FROM USER u, orderlist o WHERE o.uid=u.id;
### 7.外连接查询
+ 左外连接
+ 查询原理
查询左表的全部数据,和左右两张表有交集部分的数据。
+ 查询语法
```sql
SELECT 列名 FROM 表名1 LEFT [OUTER] JOIN 表名2 ON 条件;
-
右外连接
-
查询原理
查询右表的全部数据,和左右两张表有交集部分的数据。
-
查询语法
-
SELECT 列名 FROM 表名1 RIGHT [OUTER] JOIN 表名2 ON 条件;
例:
-- 查询所有用户信息,以及用户对应的订单信息
SELECT
u.*,
o.number
FROM
USER u
LEFT OUTER JOIN
orderlist o
ON
o.uid=u.id;
-- 查询所有订单信息,以及订单所属的用户信息
SELECT
o.*,
u.name
FROM
USER u
RIGHT OUTER JOIN
orderlist o
ON
o.uid= u.id;
8.子查询
- 子查询概念
查询语句中嵌套了查询语句,我们就将嵌套的查询称为子查询。
-
结果是单行单列的
-
查询作用
可以将查询的结果作为另一条语句的查询条件,使用运算符判断。= > > =< <=等。
-
查询语法
-
SELECT 列名 FROM 表名 WHERE 列名=(SELECT 列名 FROM 表名[WHERE条件]);
-
结果是多行单列的
-
查询作用
可以作为条件,使用运算符IN或NOT IN进行判断。
-
查询语法
-
SELECT 列名 FROM 表名 WHERE 列名 [NOT]IN (SELECT 列名 FROM 表名 [WHERE 条件]);
-
结果是多行多列的
-
查询作用
查询的结果可以作为一张虚拟表参与查询。
-
查询语法
SELECT 列名 FROM 表名 [别名],(SELECT 列名 FROM 表名 [WHERE 条件]) [别名] [WHERE 条件];
-
例:
-- 查询年龄最高的用户姓名
SELECT MAX(age) FROM USER;
SELECT NAME,age FROM USER WHERE age=(SELECT MAX(age) FROM USER);
-- 查询张三和李四的订单信息
```sql
SELECT * FROM orderlist WHERE uid IN (1,2);
SELECT id FROM USER WHERE NAME IN ('张三','李四');
SELECT * FROM orderlist WHERE uid IN (SELECT id FROM USER WHERE NAME IN ('张三','李四'));
-- 查询订单表中id大于4的订单信息和所属用户信息
SELECT * FROM orderlist WHERE id > 4;
SELECT
u.name,
o.number
FROM
USER u,
(SELECT * FROM orderlist WHERE id > 4) o
WHERE
o.uid=u.id;
9.自关联查询
-
自关联查询概念
在同一张表中数据有关联性,我们可以把这张表当成多个表来查询。
七.视图
1.视图介绍
- 视图∶是一种虚拟存在的数据表,这个虚拟表并不在数据库中实际存在。
- 作用:将一些较为复杂的查询语句的结果,封装到一个虚拟表中,后期再有相同需求时,直接查询该虚拟表即可。
2.视图的创建和查询
- 创建视图语法
CREATE VIEW 视图名称 [(列名列表)] AS 查询语句;
- 查询视图语法(与表的查询一致)
SELECT * FROM 视图名称;
3.视图的修改和删除
- 修改视图数据语法(注意:修改视图数据后,源表数据也会随之修改)
UPDATE 视图名称 SET 列名=值 WHERE 条件;
- 修改视图结构语法
ALTER VIEW 视图名称 (列名列表) AS 查询语句;
- 删除视图语法
DROP VIEW [IF EXISTS] 视图名称;
八.数据库备份和恢复
1.命令行方式
-
备份
登录到MySQL服务器(不用登录MySQL),输入:
mysqldump -u root -p 数据库名称 > 文件保存路径
-
恢复
1.登录MySQL数据库(登录到MySQL)。
2.删除已备份的数据库。
3.重新创建名称相同的数据库。
4.使用该数据库。
5.导入文件执行: source 备份文件全路径。
例:
备份:
mysqldump -u root -p db5 > /root/db5.sql
恢复:
create database db5;
use da5;
source /root/db5.sql;
2.图形化工具方式
略
九.MySQL存储过程和函数
1.存储过程和函数介绍
- 存储过程和函数是事先经过编译并存储在数据库中的一段SQL语句的集合
- 存储过程和函数的好处
- 提高代码的复用性
- 减少数据在数据库和应用服务器之间的传输,提高效率
- 减少代码层面的业务处理
- 存储过程和函数的区别
- 存储函数必须有返回值
- 存储过程可以没有返回值
2.存储过程的创建和调用
- 创建存储过程
-- 修改结束分隔符
DELIMITER $
-- 创建存储过程
CREATE PROCEDURE 存储过程名称(参数列表)
BEGIN
SQL语句列表
END$
-- 修改结束分隔符
DELIMITER ;
- 调用存储过程
CALL 存储过程名称(实际参数)
例:
-- 创建stu_grou() 存储过程,封装 分组查询总成绩,并按照总成绩升序排序的功能
DELIMITER $
CREATE PROCEDURE stu_group()
BEGIN
SELECT gender,SUM(score) getSum FROM student GROUP BY gender ORDER BY getSum ASC;
END$
DELIMITER ;
-- 调用stu_group()存储过程
CALL stu_group();
3.存储过程的查看和删除
- 查看数据库中所有的存储过程
SELECT * FROM mysql.proc WHERE db='数据库名称';
- 删除存储过程
DROP PROCEDURE [IF EXISTS] 存储过程名称;
例:
-- 查看db6数据库中所有的存储过程
SELECT * FROM mysql.proc WHERE db='db6';
-- 删除存储过程(不需要加存储过程的小括号)
DROP PROCEDURE IF EXISTS stu_group;
4.存储过程语法-变量
- 定义变量
DECLARE 变量名 数据类型 [DEFAULT 默认值];
- 变量赋值方式一
SET 变量名 = 变量值;
- 变量赋值方式二
SELECT 列名 INTO 变量名 FROM 表名 [WHERE 条件];
例:
-- 定义一个int类型变量,并赋默认值为10
DELIMITER $
CREATE PROCEDURE pro_test1()
BEGIN
-- 定义变量
DECLARE num INT DEFAULT 10;
-- 使用变量
SELECT num;
END$
DELIMITER ;
-- 变量赋值-方式一
DELIMITER $
CREATE PROCEDURE pro_test2()
BEGIN
-- 定义变量
DECLARE NAME VARCHAR(10);
-- 为变量赋值
SET NAME = '存储过程';
-- 使用变量
SELECT NAME;
END$
DELIMITER ;
-- 调用pro_test2存储过程
CALL pro_test2();
-- 变量赋值-方式二
DELIMITER $
CREATE PROCEDURE pro_test3()
BEGIN
-- 定义两个变量
DECLARE men, women INT;
-- 查询男同学的总分数,为men赋值
SELECT SUM(score) INTO men FROM student WHERE gender='男';
-- 查询女同学的总分数,为women赋值
SELECT SUM(score) INTO women FROM student WHERE gender='女';
-- 使用变量
SELECT men,women;
END$
DELIMITER ;
-- 调用pro_test3存储过程
CALL pro_test3();
5.存储过程语法-if语句
- if语句标准语法
IF 判断条件1 THEN 执行的SQL语句;
[ELSEIF 判断条件2 THEN 执行的SQL语句2;]
...
[ELSE 执行的SQL语句n;]
END IF;
DELIMITER $
CREATE PROCEDURE pro_test4()
BEGIN
-- 定义变量
DECLARE total INT;
DECLARE info varchar(10);
-- 查询总成绩,为total赋值
SELECT SUM(score) INTO total FROM student;
-- 对总成绩判断
IF total > 380 THEN
SET info = '学习优秀';
ELSEIF total >= 320 AND total <= 380 THEN
SET info = '学习不错';
ELSE
SET info = '学习一般';
END IF;
-- 查询总成绩和描述信息
SELECT total,info;
END$
DELIMITER ;
CALL pro_test4();
6.存储过程语法-参数传递
- 存储过程的参数和返回值
CREATE PROCEDURE 存储过程名称([IN|OUT|INOUT] 参数名 数据类型)
BEGIN
SQL语句列表;
END$
IN:代表输入参数,需要由调用者传递实际数据(默认)
OUT:代表输出参数,该参数可以作为返回值
INOUT:代表既可以作为输入参数,也可以作为输出参数
例:
DELIMITER $
CREATE PROCEDURE pro_test5(IN total INT, OUT info VARCHAR(10))
BEGIN
-- 对总成绩判断
IF total > 380 THEN
SET info = '学习优秀';
ELSEIF total >= 320 AND total <= 380 THEN
SET info = '学习不错';
ELSE
SET info = '学习一般';
END IF;
END$
DELIMITER ;
-- 调用pro_test5()存储过程
CALL pro_test5(383,@info);
CALL pro_test5((SELECT SUM(score) FROM student),@info)
SELECT @info;
7.存储过程语法-while循环
- while循环语法
初始化语句;
WHILE 条件判断语句 DO
循环体语句;
条件控制语句;
END WHILE;
例:
-- 计算1~100之间的偶数和
DELIMITER $
CREATE PROCEDURE pro_test6()
BEGIN
-- 定义求和变量
DECLARE result INT DEFAULT 0;
-- 定义初始化变量
DECLARE num INT DEFAULT 1;
WHERE num <= 100 DO
IF num % 2 = 0 THEN
SET result = result + num;
END IF;
SET num = num + 1;
END WHERE;
-- 查询求和结果
SELECT result;
END$
DELIMITER ;
-- 调用pro_test6存储过程
CALL pro_test6();
8.存储函数
- 存储函数和存储过程是非常相似的,区别在于存储函数必须有返回值
- 创建存储函数
CREATE FUNCTION 函数名称(参数列表)
RETURNS 返回值类型
BEGIN
SQL语句列表;
RETURN 结果;
END$
- 调用存储函数
SELECT 函数名称(实际参数);
- 删除存储函数
DROP FUNCTION 函数名称;
例:
-- 定义存储函数,获取学生表中成绩大于95分的学生数量
DELIMITER $
CREATE FUNCTION fun_test1()
RETURNS INT
BEGIN
-- 定义变量
DECLARE s_count INT;
-- 查询成绩大于95分的数量,为s_count赋值
SELECT COUNT(*) INTO s_count FROM student WHERE score > 95;
-- 返回统计结果
RETURN s_count;
END$
DELIMITER ;
-- 调用函数
SELECT fun_test1();
-- 删除函数
DROP FUNCTION fun_test1;
十.触发器
1.触发器的介绍
-
触发器是与表有关的数据库对象,可以在insert、update、delete之前或之后触发并执行触发器中执行的SQL语句。
-
这种特性可以协助应用系统在数据库端确保数据的完整性、日志记录、数据校验等操作。
-
使用别名NEW和OLD来引用触发器中发生变化的内容记录。
-
触发器分类
触发器类型 | OLD | NEW |
---|---|---|
INSERT型触发器 | 无(因为插入前无数据) | NEW表示将要或者已经新增的数据 |
UPDATE型触发器 | OLD表示修改之前的数据 | NEW表示将要或已经修改后的数据 |
DELETE型触发器 | OLD表示将要或者已经删除的数据 | 无(因为删除后状态无数据) |
2.触发器的操作
- 创建触发器
DELIMITER $
CREATE TRIGGER 触发器名称
BEFORE|AFTER INSERT|UPDATE|DELETE
ON 表名
FOR EACH ROW
BEGIN
触发器要执行的功能
END$
DELIMITER ;
例(INSERT型触发器):
-- 创建INSERT型触发器,用于对account表新增数据进行日志的记录
DELIMITER $
CREATE TRIGGER account_insert
AFTER INSERT
ON account
FOR EACH ROW
BEGIN
-- NOW()函数获取当前时间,CONCAT()实现字符串拼接
INSERT INTO account_log VALUES (NULL,'INSERT',NOW(),NEW.id,CONCAT('插入后(id=',new.id,',name=',new.name,',money=',new.money,')'));
END$
DELIMITER ;
-- 向account表添加一条记录
INSERT INTO account VALUES (NULL,'王五',2000);
-- 查询account表
SELECT * FROM account;
-- 查询account_log表
SELECT * FROM account;
例(UPDATE型触发器):
DELIMITER $
CREATE TRIGGER account_update
AFTER UPDATE
ON account
FOR EACH ROW
BEGIN
INSERT INTO account_log VALUES (NULL,'UPDATE',NOW(),CONCAT('更新前(id=',old.id,',name=',old.name,',money=',old.money,')','更新后(id=',new.id,',name=',new.name,',money=',new.money,')'))
END$
DELIMITER ;
-- 修改account表中李四的金额为2000
UPDATE account SET money=2000 WHERE id=2;
-- 查询account表
SELECT * FROM account;
-- 查询account_log表
SELECT * FROM account;
例(DELETE型触发器):
DELIMITER $
CREATE TRIGGER account_delete
AFTER DELETE
ON account
FOR EACH ROW
BEGIN
INSERT INTO account_log VALUES (NULL,'DELETE',NOW(),old.id,CONCAT('删除前(id=',old.id,',name=',old.name,',money=',old.money,')'))
END$
DELIMITER ;
-- 删除account表中王五
DELETE FROM account WHERE id=3;
-- 查询account表
SELECT * FROM account;
-- 查询account_log表
SELECT * FROM account;
3.触发器的操作
- 查看触发器
SHOW TRIGGERS;
- 删除触发器
DROP TRIGGER 触发器名称;
十一.事务
1.事务介绍
- 事务:一条或多条SQL语句组成一个执行单元,其特点是这个单元要么同时成功要么同时失败。
- 单元中的每条SQL语句都相互依赖,形成一个整体
- 如果某条SQL语句执行失败或者出现错误,那么整个单元就会撤回到事务最初的状态。
2.事务的操作
- 开启事务
START TRANSACTION;
- 回滚事务
ROLLBACK;
- 提交事务
COMMIT;
例:
-- 开启事务
START TRANSACTION;
-- 1.张三账户-500
UPDATE account SET money=money-500 WHERE NAME='张三';
-- 2.李四账户+500
UPDATE account SET money=money+500 WHERE NAME='李四';
-- 回滚事务
ROLLBACK;
-- 提交事务
COMMIT;
3.事务的提交方式
- 事务提交方式的分类
- 自动提交(MySQL默认)
- 手动提交
- 查看事务提交方式(0是手动,1是自动)
SELECT @@AUTOCOMMIT;
- 修改事务提交方式
SET @@AUTOCOMMIT=数字;
4.事务的四大特征(ACID)
-
原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
-
一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态。也就是说一个事务执行之前和执行之后都必须处于—致性状态。
-
隔离性(isolcation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务。不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。 -
持久性(durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
5.事务隔离级别
- 事务的隔离级别
多个客户端操作时,各个客户端的事务之间应该是隔离的,相互独立的,不受影响的。而如果多个事务操作 同一批数据时,就会产生不同的问题,我们需要设置不同的隔离级别来解决这些问题。
- 隔离级别分类
隔离级别 | 名称 | 会引发的问题 |
---|---|---|
read uncommintted | 读未提交 | 脏读、不可重复读、幻读 |
read commintted | 读已提交 | 不可重复读、幻读 |
repeatable read | 可重复读 | 幻读 |
serializable | 串行化 | 无 |
- 引发的问题
问题 | 现象 |
---|---|
脏读 | 在一个事务处理过程中读取到了另一个未提交事务中的数据,导致两次查询结果不一致 |
不可重复读 | 在一个事务处理过程中读取到了另一个事务中修改并已提交的数据,导致两次查询结果不一致 |
幻读 | 查询某数据不存在,准备插入此记录,但执行插入时发现此记录已存在,无法插入。或查询数据不存在执行删除操作,却发现删除成功 |
- 查询数据库隔离级别
SELECT @@TX_ISOLATION;
- 修改数据库隔离级别(修改后需要重新连接)
SET GLOBAL TRANSACTION ISOLATION LEVEL 级别字符串;
- 隔离级别总结
序号 | 隔离级别 | 名称 | 脏读 | 不可重复读 | 幻读 | 数据库默认隔离级别 |
---|---|---|---|---|---|---|
1 | read uncommitted | 读未提交 | 是 | 是 | 是 | |
2 | read committed | 读已提交 | 否 | 是 | 是 | Oracle |
3 | repeatable read | 可重复读 | 否 | 否 | 是 | MySQL |
4 | serializable | 串行化 | 否 | 否 | 否 |
注意:隔离级别从小到大安全性越来越高,但是效率越来越低,所以不建议修改数据库默认的隔离级别。
十二.存储引擎
-
客户端连接
支持接口∶支持的客户端连接,例如C、Java、PHP等语言来连接MySQL数据库。 -
第一层∶网络连接层
连接池∶管理、缓冲用户的连接,线程处理等需要缓存的需求。 -
第二层∶核心服务层
管理服务和工具:系统的管理和控制工具,例如备份恢复、复制、集群等。SQL接口∶接受SQL命令,并且返回查询结果。
查询解析器:验证和解析SQL命令,例如过滤条件、语法结构等。
查询优化器︰在执行查询之前,使用默认的一套优化机制进行优化sql语句。
缓存∶如果缓存当中有想查询的数据,则直接将缓存中的数据返回。没有的话再重新查询。 -
第三层∶存储引擎层
插件式存储引擎∶管理和操作数据的一种机制,包括(存储数据、如何更新、查询数据等) -
第四层:系统文件层
文件系统:配置文件、数据文件、日志文件、错误文件、二进制文件等等的保存。
1.存储引擎介绍
-
在生活中,引擎就是整个机器运行的核心(发动机),不同的引擎具备不同的功能,应用于不同的场景之中。
-
MySQL数据库使用不同的机制存取表文件,包括存储方式、索引技巧、锁定水平等不同的功能。这些不同的技术以及配套的功能称为存储引擎。
-
Oracle、SqlServer等数据库只有一种存储引擎。而MySQL针对不同的需求,配置不同的存储引擎,就会让数据库采取不同处理数据的方式和扩展功能。
-
MySQL支持的存储引擎有很多,常用的有三种: InnoDB、MylSAM、MEMORY.
-
特性对比
- MylSAM存储引擎:访问快,不支持事务和外键操f作。
- InnoDB 存储引擎:支持事务和外键操作,支持并发控制,占用磁盘空间大。(MySQL 5.5版本后默认)
- MEMORY存储引擎:内存存储,速度快,不安全。适合小量快速访问的数据。
2.存储引擎的操作
- 查询数据库支持的存储引擎
SHOW ENGINES;
- 查询某个数据库中所有数据表的存储引擎
SHOW TABLE STATUS FROM 数据库名称;
- 查询某个数据库中某个数据表的存储引擎
SHOW TABLE STATUS FROM 数据库名称 WHERE NAME = '数据表名称';
- 创建数据表,指定存储引擎
CREATE TABLE 表名(
列名,数据类型,
...
)ENGINE = 引擎名称;
- 修改数据表的存储引擎
ALTER TABLE 表名 ENGNE = 引擎名称;
3.存储引擎的选择
-
MylSAM
特点:不支持事务和外键操作。读取速度快,节约资源。
使用场景︰以查询操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高! -
InnoDB
特点:MySQL的默认存储引擎,支持事务和外键操f作。
使用场景∶对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,读写频繁的操作! -
MEMORY
特点∶将所有数据保存在内存中,在需要快速定位记录和其他类似数据环境下,可以提供更快的访问。使用场景:通常用于更新不太频繁的小表,用来快速得到访问的结果!
总结:针对不同的需求场景,来选择最合适的存储引擎即可!如果不确定,则使用数据库默认的存储引擎
十三.索引
1.索引介绍
- MySQL索引:是帮助MySQL高效获取数据的一种数据结构,所以,索引的本质就是数据结构!
- 在表数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引.
2.索引的分类
-
按照功能分类
- 普通索引:最基本的索引,没有任何限制。
- 唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值组合必须唯一。
- 主键索引:一种特殊的唯一索引,不允许有空值。在建表时有主键列同时创建主键索引。(主键列自带主键索引)
- 联合索引:顾名思义,就是将单列索引进行组合。
- 外键索引:只有InnoDB引擎支持外键索引,用来保证数据的一致性、完整性和实现级联操作(外键列自带外键索引)
- 全文索引:快速匹配全部文档的方式。InnoDB引擎5.6版本后才支持全文索引。MEMORY引擎不支持。
-
按照结构分类
- BTree索引:MySQL使用最频繁的一个索引数据结构,是InnoDB和MylSAM存储引擎默认的索引类型,
底层基于B+Tree数据结构。 - Hash索引:MySQL中Memory存储引擎默认支持的索引类型。
- BTree索引:MySQL使用最频繁的一个索引数据结构,是InnoDB和MylSAM存储引擎默认的索引类型,
3.索引的操作
- 创建索引
CREATE [UNIQUE | FULLTEXT] INDEX 索引名称
[USING 索引类型] -- 默认是BTREE
ON 表名(列名...);
- 查看索引
SHOW INDEX FROM 表名;
- 添加索引
-- 普通索引:
ALTER TABLE 表名 ADD INDEX 索引名称(列名);
-- 组合索引:
ALTER TABLE 表名 ADD INDEX 索引名称(列名1,列名2,...);
-- 主键索引:
ALTER TABLE 表名 ADD PRIMARY KEY(主键列名);
-- 外键索引:
ALTER TABLE 表名 ADD CONSTRAINT 外键名 FOREIGN KEY(本表外键列名) REFERENCES 主表名(主键列名);
-- 唯一索引:
ALTER TABLE 表名 ADD UNIQUE 索引名称(列名);
-- 全文索引:
ALTER TABLE 表名 ADD FULLTEXT 索引名称(列名);
- 删除索引
DROP INDEX 索引名称 ON 表名;
4.索引的原理
- 索引是在存储引擎中实现的,不同的存储引擎所支持的索引也不一样,这里我们主要介绍InnoDB引擎的BTree索引
- BTree索引类型是基于B+Tree数据结构的,而B+Tree数据结构又是BTree数据结构的变种,通常使用在数据库和操作系统中的文件系统,特点是能够保持数据稳定有序。
- 需要理解的
- 磁盘存储。
- BTree。
- B+Tree。
4.4.1索引的原理-磁盘存储
- 系统从磁盘读取数据到内存时是以磁盘块( block )为基本单位的。
- 位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。
- InnoDB存储引擎中有页(Page )的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB.
- InnoDB 引擎将若干个地址连接磁盘块,以此来达到页的大小16KB,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
4.4.2索引的原理-BTree
- 每一个节点中不仅包含key值,还有数据。会增加查询数据时磁盘的IO次数。
4.4.3索引的原理-B+Tree
-
特点
-
非叶子节点只存储key值
-
所有数据存储在叶子节点
-
所有叶子节点之间都有连接指针
-
-
好处
- 提高查询速度
- 减少磁盘的IO次数
- 树型结构较小
5.索引的设计原则
-
创建索引遵循的原则
1.对查询频次较高,且数据量比较大的表建立索引。
2.使用唯一索引,区分度越高,使用索引的效率越高。
3.索引字段的选择,最佳候选列应当从where子句的条件中提取。
4.索引虽然可以有效的提升查询数据的效率,但并不是多多益善。 -
最左匹配原则(只适用于组合索引)
-
例如:为user表中的name、address、phone列添加组合索引
ALTER TABLE user ADD INDEX idx_three(name,address,phone);
-
此时,组合索引idx_three实际建立了(name)、(name,address)、(name,address,phone)三个索引
-
下面的三个SQL语句都可以命中索引
SELECT * FROM user WHERE address = '北京'AND phone = '12345'AND name= '张三';
SELECT * FROM user WHERE name = '张三' AND address = '北京';
SELECT * FROM user WHERE name = '张三';
-
这三条SQL语句在检索时分别会使用以下索引进行数据匹配
- (name,address,phone)
- (name,address)
- (name)
-
索引字段出现的顺序可以是任意的,MySQL优化器会帮我们自动的调整where条件中的顺序
-
如果组合索引中最左边的列不在查询条件中,则不会命中索引
SELECT * FROM user WHERE address = '北京';
十四.锁机制
1.锁的介绍
-
锁机制:数据库为了保证数据的一致性,在共享的资源被并发访问时变得安全所设计的一种规则
-
锁机制类似多线程中的同步,作用就是可以保证数据的一致性和安全性。
-
按操作分类
- 共享锁:也叫读锁。针对同一份数据,多个事务读取操作可以同时加锁而不互相影响,但是不能修改数据.
- 排他锁:也叫写锁。当前的操作没有完成前,会阻断其他操作的读取和写入。
-
按粒度分类
- 表级锁:会锁定整个表。开销小,加锁快。锁定力度大,发生锁冲突概率高,并发度低。不会出现死锁情况.
- 行级锁∶会锁定当前行。开销大,加锁慢。锁定粒度小,发生锁冲突概率低,并发度高。会出现死锁情况。
-
按使用方式分类
- 悲观锁:每次查询数据时都认为别人会修改,很悲观,所以查询时加锁。
- 乐观锁:每次查询数据时都认为别人不会修改,很乐观,但是更新时会判断一下在此期间别人有没有去更新这个数据.
-
不同存储引擎支持的锁
存储引擎 | 表锁 | 行锁 |
---|---|---|
InnoDB | 支持 | 支持 |
MyISAM | 支持 | 不支持 |
MEMORY | 支持 | 不支持 |
2.InnoDB共享锁
-
共享锁特点
数据可以被多个事务查询,但是不能修改
-
创建共享锁格式
SELECT 语句 LOCK IN SHARE MODE;
- InnoDB引擎如果采用带索引的列加锁,默认加的是行锁
- InnoDB引擎如果采用不带索引的列加锁,加的是表锁
3.InnoDB排他锁
-
排他锁特点
加锁的数据,不能被其他事务加锁查询或修改
-
创建排它锁格式
SELECT 语句 FOR UPDATE;
4.MyISAM读锁
-
读锁特点
所有连接只能查询数据,不能修改
-
读锁语法格式
加锁:
LOCK TABLE 表名 READ;
解锁:
UNLOCK TABLES;
5.MyISAM写锁
-
写锁特点
其他连接不能查询和修改(包括增删改)数据,当前连接(执行加锁语句的连接)可以
-
写锁语法格式
加锁:
LOCK TABLE 表名 WRITE;
解锁:
UNLOCK TABLES;
6.悲观锁和乐观锁
-
悲观锁
就是很悲观,它对于数据被外界修改的操作持保守态度,认为数据随时会修改。整个数据处理中需要将数据 加锁。悲观锁一般都是依靠关系型数据库提供的锁机制,我们之前所学的锁机制都是悲观锁。 -
乐观锁
就是很乐观,每次自己操作数据的时候认为没有人会来修改它,所以不去加锁。
但是在更新的时候会去判断 在此期间数据有没有被修改。
需要用户自己去实现,不会发生并发抢占资源,只有在提交操作的时候检查是否违反数据完整性 -
乐观锁的实现方式
-
方式一:
给数据表中添加一个version列,每次更新后都将这个列的值加1.
读取数据时,将版本号读取出来,在执行更新的时候,比较版本号。
如果相同则执行更新,如果不相同,说明此条数据已经发生了变化。
用户自行根据这个通知来决定怎么处理,比如重新开始一遍,或者放弃本次更新。 -
方式二:
和版本号方式基本一样,给数据表中添加一个列,名称无所谓,数据类型需要是timestamp(时间戳).
每次更新后都将最新时间插入到此列。
读取数据时,将时间读取出来,在执行更新的时候,比较时间。
如果相同则执行更新,如果不相同,说明此条数据已经发生了变化。
-
十五.MyCat中间件
1.MyCat介绍
-
随着互联网的发展,数据的量级也是不断的增长,从GB到TB到PB。对数据的各种操作也是越来越困难,一台数据库服务器已经无法满足海量数据的存储需求,所以由多台数据库服务器构成的数据库集群成了必然的方式。不过,还要保证数据的一致性,查询效率等,同时又要解决多台服务器间的通信、负载均衡等问题。
-
MyCat是一款出色的数据库集群软件,不仅支持MySQL,常用关系型数据库也都支持。
-
其实就是一个数据库中间件产品,支持MySQL集群。提供高可用性数据分片集群。
-
我们可以像使用MySQL一样使用MyCat。对于开发人员来说几乎感觉不到 MyCat的存在。
2.安装配置
1. MyCat官网
http://www.mycat.io
2. 通过CRT工具上传到linux
put D:\Mycat-server-1.6.7.1-release-20190627191042-linux.tar.gz
3. 解压并查看
tar -zxvf mycat.tar.gz
cd mycat
ls
4. 为mycat目录授权
chmod -R 777 mycat
5. 配置环境变量
编辑文件:vi /etc/profile
添加内容:export MYCAT_HOME=/root/mycat
加载文件:source /etc/profile
6. 启动mycat
进入目录:cd /root/mycat/bin
执行启动:./mycat start
7. 查看端口监听
netstat -ant|grep 8066
8. SQLyog连接mycat
默认用户名:root
默认密码:123456
默认端口号:8066
3.克隆虚拟机
-
修改配置网卡
-
在第二个虚拟机中,生成全新mac地址
-
重启网络
// 重启网络 service network restart //查看ip地址 ip addr
-
-
修改mysql配置文件,更改uuid
- 在第二个服务器上,修改mysql的uuid
// 编辑配置文件 vi /var/lib/mysql/auto.cnf // 将server-uuid更改
-
启动MySQL并查看
//将两台服务器的防火墙关闭
systemctl stop firewalld
//启动两台服务器的mysql
service mysqld restart
//启动两台服务器的mycat
cd /root/mycat/bin
./mycat restart
//查看监听端口
netstat -ant|grep 3306
netstat -ant|grep 8066
//使用sqlyog测试连接
4.主从复制
-
主从复制的概念
- 为了使用Mycat进行读写分离,我们先要配置MySQL数据库的主从复制。
- 从服务器自动同步主服务器的数据,从而达到数据一致。
- 进而,我们可以写操作时,只操作主服务器,而读操作,就可以操作从服务器了。
- 原理:主服务器在处理数据时,生成binlog日志,通过对日志的备份,实现从服务器的数据同步。
-
主服务器的配置
- 在第一个服务器上,编辑mysql配置文件
// 编辑mysql配置文件 vi /etc/my.cnf //在[mysqld]下面加上: log-bin=mysql-bin # 开启复制操作 server-id=1 # master is 1 innodb_flush_log_at_trx_commit=1 sync_binlog=1
- 登录mysql,创建用户并授权
// 登录mysql mysql -u root -p // 去除密码权限 SET GLOBAL validate_password_policy=0; SET GLOBAL validate_password_length=1; // 创建用户 CREATE USER 'hm'@'%' IDENTIFIED BY 'itheima'; // 授权 GRANT ALL ON *.* TO 'hm'@'%';
- 重启mysql服务,登录mysql服务
// 重启mysql service mysqld restart // 登录mysql mysql -u root -p
- 查看主服务器的配置
// 查看主服务器配置 show master status;
-
从服务器的配置
- 在第二个服务器上,编辑mysql配置文件
// 编辑mysql配置文件 vi /etc/my.cnf // 在[mysqld]下面加上: server-id=2
- 登录mysql
// 登录mysql mysql -u root -p // 执行 use mysql; drop table slave_master_info; drop table slave_relay_log_info; drop table slave_worker_info; drop table innodb_index_stats; drop table innodb_table_stats; source /usr/share/mysql/mysql_system_tables.sql;
- 重启mysql,重新登录,配置从节点
// 重启mysql service mysqld restart // 重新登录mysql mysql -u root -p // 执行 change master to master_host='主服务器ip地址',master_port=3306,master_user='hm',master_password='itheima',master_log_file='mysql-bin.000001',master_log_pos=4642;
- 重启mysql,重新登录,开启从节点
// 重启mysql service mysqld restart // 重新登录mysql mysql -u root -p // 开启从节点 start slave; // 查询结果 show slave status\G; //Slave_IO_Running和Slave_SQL_Running都为yes才表示同步成功。
-
测试
- sqlyog连接主服务器
-- 主服务器创建db1数据库,从服务器会自动同步 CREATE DATABASE db1;
- sqlyog连接从服务器
-- 从服务器创建db2数据库,主服务器不会自动同步 CREATE DATABASE db2;
-
启动失败的解决方案
启动失败:Slave_IO_Running为 NO
方法一:重置slave
slave stop;
reset slave;
start slave ;
方法二:重设同步日志文件及读取位置
slave stop;
change master to master_log_file=’mysql-bin.000001’, master_log_pos=1;
start slave ;
5.读写分离
-
读写分离的概念
- 写操作只写入主服务器,读操作读取从服务器。
-
在主服务器上修改server.xml
- user标签主要用于定义登录mycat的用户和权限。如上面定义用户名mycat和密码123456,该用户可以访问的schema的HEIMADB逻辑库。
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">HEIMADB</property>
<!-- 表级 DML 权限设置 -->
<!--
<privileges check="false">
<schema name="TESTDB" dml="0110" >
<table name="tb01" dml="0000"></table>
<table name="tb02" dml="1111"></table>
</schema>
</privileges>
-->
</user>
- 在主服务器上修改schema.xml
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="HEIMADB" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1"></schema>
<dataNode name="dn1" dataHost="localhost1" database="db1" />
<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- 主服务器进行写操作 -->
<writeHost host="hostM1" url="localhost:3306" user="root"
password="itheima">
<!-- 从服务器负责读操作 -->
<readHost host="hostS1" url="192.168.203.135:3306" user="root" password="itheima" />
</writeHost>
</dataHost>
</mycat:schema>
-
配置详解
-
schema标签逻辑库的概念和mysql数据库中Datebase的概念相同,我们在查询这两个逻辑库中的表的时候,需要切换到该逻辑库下才可以查到所需要的表。
-
dataNode属性:该属性用于绑定逻辑库到某个具体的database上。
-
dataNode标签: dataNode标签定义了mycat中的数据节点,也就是数据分片。一个dataNode标签就是一个独立的数据分片。
-
name属性:定义数据节点的名字,这个名字需要是唯一的,我们需要在table标签上应用这个名字,来建立表与分片对应的关系。
-
dataHost属性:该属性用于定义该分片属于那个数据库实例,属性值是引用datahost标签定义的name属性。
-
database属性:该属性用于定义该分片属于那个具体数据库实例上的具体库,因为这里使用两个纬度来定义分片,就是:实例+具体的库。因为每个库上建立的表和表结构是一样的。所以这样做就可以轻松的对表进行水平拆分。
-
dataHost标签:该标签在mycat逻辑库中也是作为最底层的标签存在,直接定义了具体的数据库实例、读写分离配置和心跳语句。
-
balance属性: 负载均衡类型
balance=0: 不开启读写分离,所有读操作都发送到当前可用的writeHost上。
balance=1: 全部的readHost与Stand by writeHost都参与select语句的负载均衡
balance=2: 所有的读操作都随机在writeHost,readHost上分发。
balance=3: 所有的读请求都随机分配到writeHost对应的readHost上执行,writeHost不负担读压力。 -
switchType属性:
-1:表示不自动切换。
1 :默认值,表示自动切换
2:表示基于MySQL主从同步状态决定是否切换,心跳语句: show slave status.
3:表示基于mysql galary cluster的切换机制,适合mycat1.4之上的版本,心跳语句show status like “%esrep%”; -
writeHost标签,readHost标签:这两个标签指定后端数据库的相关配置给mycat,用于实例化后端连接池。唯一不同的是,writeHost指定写实例、readHost指定读实例,组合这些读写实例来满足系统的要求。
- host属性:用于标识不同的实例,对于writehost,一般使用M1;对于readhost一般使用S1.
- url属性:后端实例连接地址,如果使用native的dbDriver,则一般为address:port这种形式,用JDBC或其他的dbDriver,则需要特殊指定。当使用JDBC时则可以这么写:jdbc:mysql://localhost:3306/。
- user属性:后端存储实例的用户名。
- password属性:后端存储实例的密码
-
-
测试
- 重启主服务器的mycat
// 重启mycat cd /root/mycat/bin ./mycat restart // 查看端口监听 netstat -ant|grep 8066
- sqlyog连接mycat
-- 创建学生表 CREATE TABLE student( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(10) ); -- 查询学生表 SELECT * FROM student; -- 添加两条记录 INSERT INTO student VALUES (NULL,'张三'),(NULL,'李四'); -- 停止主从复制后,添加的数据只会保存到主服务器上。 INSERT INTO student VALUES (NULL,'王五');
- sqlyog连接主服务器
-- 主服务器:查询学生表,可以看到数据 SELECT * FROM student;
- sqlyog连接从服务器
-- 从服务器:查询学生表,可以看到数据(因为有主从复制) SELECT * FROM student; -- 从服务器:删除一条记录。(主服务器并没有删除,mycat中间件查询的结果是从服务器的数据) DELETE FROM student WHERE id=2;
6.分库分表
-
分库分表的概念
- 将庞大的数据进行拆分
- 水平拆分:根据表的数据逻辑关系,将同一表中的数据按照某种条件,拆分到多台数据库服务器上,也叫做横向拆分。例如:一张1000万的大表,按照一模一样的结构,拆分成4个250万的小表,分别保存到4个数据库中。
- 垂直拆分:根据业务的维度,将不同的表切分到不同的数据库之上,也叫做纵向拆分。例如:所有的订单都保存到订单库中,所有的用户都保存到用户库中,同类型的表保存在同一库,不同的表分散在不同的库中。
-
Mycat水平拆分
-
修改主服务器的server.xml
-
0:本地文件方式
在mycat/conf/sequence_conf.properties文件中:
GLOBAL.MINDI=10000最小值
GLOBAL.MAXID=20000最大值,建议修改到9999999999 -
1:数据库方式
分库分表中保证全局主键自增唯一,但是需要执行mycat函数,配置sequence_db_conf.properties
-
2:时间戳方式
mycat实现的时间戳,建议varchar类型,要注意id的长度
-
<!-- 修改主键的方式 --> <property name="sequnceHandlerType">0</property>
- 修改主服务器的sequence_conf.properties
#default global sequence GLOBAL.HISIDS= # 可以自定义关键字 GLOBAL.MINID=10001 # 最小值 GLOBAL.MAXID=20000 # 最大值 GLOBAL.CURID=10000
- 修改主服务器的schema.xml
- table标签定义了逻辑表,所有需要拆分的表都需要在这个标签中定义。
- rule属性:拆分规则。mod-long是拆分规则之一,主键根据服务器数量取模,在rule.xml中指定。如果是3个数据库,那么数据取模后,平均分配到三个库中。
- name属性:定义逻辑表的表名,这个名字就如同在数据库中执行create table命令指定的名字一样,同一个schema标签中定义的表名必须是唯一的。
- dataNode属性: 定义这个逻辑表所属的dataNode,该属性的值需要和dataNode标签中name属性的值相互对应。
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="HEIMADB" checkSQLschema="false" sqlMaxLimit="100"> <table name="product" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long"/> </schema> <dataNode name="dn1" dataHost="localhost1" database="db1" /> <dataNode name="dn2" dataHost="localhost1" database="db2" /> <dataNode name="dn3" dataHost="localhost1" database="db3" /> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- write --> <writeHost host="hostM1" url="localhost:3306" user="root" password="itheima"> <!-- read --> <readHost host="hostS1" url="192.168.203.135:3306" user="root" password="itheima" /> </writeHost> </dataHost> </mycat:schema>
- 修改主服务器的rule.xml
<function name="mod-long" class="io.mycat.route.function.PartitionByMod"> <!-- 数据库的数量 --> <property name="count">3</property> </function>
-
测试
- mycat操作
-- 创建product表 CREATE TABLE product( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(20), price INT ); -- 添加6条数据 INSERT INTO product(id,NAME,price) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'苹果手机',6999); INSERT INTO product(id,NAME,price) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'华为手机',5999); INSERT INTO product(id,NAME,price) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'三星手机',4999); INSERT INTO product(id,NAME,price) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'小米手机',3999); INSERT INTO product(id,NAME,price) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'中兴手机',2999); INSERT INTO product(id,NAME,price) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'OOPO手机',1999); -- 查询product表 SELECT * FROM product;
- 主服务器操作
-- 在不同数据库中查询product表 SELECT * FROM product;
- 从服务器操作
-- 在不同数据库中查询product表 SELECT * FROM product;
-
-
Mycat垂直拆分
- 修改主服务器的schema
<?xml version="1.0"?> <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> <mycat:schema xmlns:mycat="http://io.mycat/"> <schema name="HEIMADB" checkSQLschema="false" sqlMaxLimit="100"> <table name="product" primaryKey="id" dataNode="dn1,dn2,dn3" rule="mod-long"/> <!-- 动物类数据表 --> <table name="dog" primaryKey="id" autoIncrement="true" dataNode="dn4" /> <table name="cat" primaryKey="id" autoIncrement="true" dataNode="dn4" /> <!-- 水果类数据表 --> <table name="apple" primaryKey="id" autoIncrement="true" dataNode="dn5" /> <table name="banana" primaryKey="id" autoIncrement="true" dataNode="dn5" /> </schema> <dataNode name="dn1" dataHost="localhost1" database="db1" /> <dataNode name="dn2" dataHost="localhost1" database="db2" /> <dataNode name="dn3" dataHost="localhost1" database="db3" /> <dataNode name="dn4" dataHost="localhost1" database="db4" /> <dataNode name="dn5" dataHost="localhost1" database="db5" /> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select user()</heartbeat> <!-- write --> <writeHost host="hostM1" url="localhost:3306" user="root" password="itheima"> <!-- read --> <readHost host="hostS1" url="192.168.203.135:3306" user="root" password="itheima" /> </writeHost> </dataHost> </mycat:schema>
-
测试
- sqlyog连接mycat
-- 创建dog表 CREATE TABLE dog( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(10) ); -- 添加数据 INSERT INTO dog(id,NAME) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'哈士奇'); -- 查询dog表 SELECT * FROM dog; -- 创建cat表 CREATE TABLE cat( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(10) ); -- 添加数据 INSERT INTO cat(id,NAME) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'波斯猫'); -- 查询cat表 SELECT * FROM cat; -- 创建apple表 CREATE TABLE apple( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(10) ); -- 添加数据 INSERT INTO apple(id,NAME) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'红富士'); -- 查询apple表 SELECT * FROM apple; -- 创建banana表 CREATE TABLE banana( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(10) ); -- 添加数据 INSERT INTO banana(id,NAME) VALUES (NEXT VALUE FOR MYCATSEQ_GLOBAL,'香蕉'); -- 查询banana表 SELECT * FROM banana;
- sqlyog连接主服务器
-- 查询dog表 SELECT * FROM dog; -- 查询cat表 SELECT * FROM cat; -- 查询apple表 SELECT * FROM apple; -- 查询banana表 SELECT * FROM banana;
- sqlyog连接从服务器
-- 查询dog表 SELECT * FROM dog; -- 查询cat表 SELECT * FROM cat; -- 查询apple表 SELECT * FROM apple; -- 查询banana表 SELECT * FROM banana;
7.水平拆分常见规则
常用的分片规则:总共十个(基本够用)
一、枚举法
<tableRule name="sharding-by-intfile">
<rule>
<columns>user_id</columns>
<algorithm>hash-int</algorithm>
</rule>
</tableRule>
<function name="hash-int" class="io.mycat.route.function.PartitionByFileMap">
<property name="mapFile">partition-hash-int.txt</property>
<property name="type">0</property>
<property name="defaultNode">0</property>
</function>
partition-hash-int.txt 配置:
10000=0
10010=1
上面columns 标识将要分片的表字段,algorithm 分片函数,
其中分片函数配置中,mapFile标识配置文件名称,type默认值为0,0表示Integer,非零表示String,
所有的节点配置都是从0开始,及0代表节点1
/**
* defaultNode 默认节点:小于0表示不设置默认节点,大于等于0表示设置默认节点,结点为指定的值
*
默认节点的作用:枚举分片时,如果碰到不识别的枚举值,就让它路由到默认节点
* 如果不配置默认节点(defaultNode值小于0表示不配置默认节点),碰到
* 不识别的枚举值就会报错,
* like this:can't find datanode for sharding column:column_name val:ffffffff
*/
二、固定分片hash算法
<tableRule name="rule1">
<rule>
<columns>user_id</columns>
<algorithm>func1</algorithm>
</rule>
</tableRule>
<function name="func1" class="io.mycat.route.function.PartitionByLong">
<property name="partitionCount">2,1</property>
<property name="partitionLength">256,512</property>
</function>
配置说明:
上面columns 标识将要分片的表字段,algorithm 分片函数,
partitionCount 分片个数列表,partitionLength 分片范围列表
分区长度:默认为最大2^n=1024 ,即最大支持1024分区
约束 :
count,length两个数组的长度必须是一致的。
1024 = sum((count[i]*length[i])). count和length两个向量的点积恒等于1024
用法例子:
@Test
public void testPartition() {
// 本例的分区策略:希望将数据水平分成3份,前两份各占25%,第三份占50%。(故本例非均匀分区)
// |<---------------------1024------------------------>|
// |<----256--->|<----256--->|<----------512---------->|
// | partition0 | partition1 | partition2 |
// | 共2份,故count[0]=2 | 共1份,故count[1]=1 |
int[] count = new int[] { 2, 1 };
int[] length = new int[] { 256, 512 };
PartitionUtil pu = new PartitionUtil(count, length);
// 下面代码演示分别以offerId字段或memberId字段根据上述分区策略拆分的分配结果
int DEFAULT_STR_HEAD_LEN = 8; // cobar默认会配置为此值
long offerId = 12345;
String memberId = "qiushuo";
// 若根据offerId分配,partNo1将等于0,即按照上述分区策略,offerId为12345时将会被分配到partition0中
int partNo1 = pu.partition(offerId);
// 若根据memberId分配,partNo2将等于2,即按照上述分区策略,memberId为qiushuo时将会被分到partition2中
int partNo2 = pu.partition(memberId, 0, DEFAULT_STR_HEAD_LEN);
Assert.assertEquals(0, partNo1);
Assert.assertEquals(2, partNo2);
}
如果需要平均分配设置:平均分为4分片,partitionCount*partitionLength=1024
<function name="func1" class="org.opencloudb.route.function.PartitionByLong">
<property name="partitionCount">4</property>
<property name="partitionLength">256</property>
</function>
三、范围约定
<tableRule name="auto-sharding-long">
<rule>
<columns>user_id</columns>
<algorithm>rang-long</algorithm>
</rule>
</tableRule>
<function name="rang-long" class="io.mycat.route.function.AutoPartitionByLong">
<property name="mapFile">autopartition-long.txt</property>
</function>
# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2
或
0-10000000=0
10000001-20000000=1
配置说明:
上面columns 标识将要分片的表字段,algorithm 分片函数,
rang-long 函数中mapFile代表配置文件路径
所有的节点配置都是从0开始,及0代表节点1,此配置非常简单,即预先制定可能的id范围到某个分片
四、求模法
<tableRule name="mod-long">
<rule>
<columns>user_id</columns>
<algorithm>mod-long</algorithm>
</rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
<!-- how many data nodes -->
<property name="count">3</property>
</function>
配置说明:
上面columns 标识将要分片的表字段,algorithm 分片函数,
此种配置非常明确即根据id与count(你的结点数)进行求模预算,相比方式1,此种在批量插入时需要切换数据源,id不连续
五、日期列分区法
<tableRule name="sharding-by-date">
<rule>
<columns>create_time</columns>
<algorithm>sharding-by-date</algorithm>
</rule>
</tableRule>
<function name="sharding-by-date" class="io.mycat.route.function..PartitionByDate">
<property name="dateFormat">yyyy-MM-dd</property>
<property name="sBeginDate">2014-01-01</property>
<property name="sPartionDay">10</property>
</function>
配置说明:
上面columns 标识将要分片的表字段,algorithm 分片函数,
配置中配置了开始日期,分区天数,即默认从开始日期算起,分隔10天一个分区
还有一切特性请看源码
Assert.assertEquals(true, 0 == partition.calculate("2014-01-01"));
Assert.assertEquals(true, 0 == partition.calculate("2014-01-10"));
Assert.assertEquals(true, 1 == partition.calculate("2014-01-11"));
Assert.assertEquals(true, 12 == partition.calculate("2014-05-01"));
六、通配取模
<tableRule name="sharding-by-pattern">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-pattern</algorithm>
</rule>
</tableRule>
<function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPattern">
<property name="patternValue">256</property>
<property name="defaultNode">2</property>
<property name="mapFile">partition-pattern.txt</property>
</function>
partition-pattern.txt
# id partition range start-end ,data node index
###### first host configuration
1-32=0
33-64=1
65-96=2
97-128=3
######## second host configuration
129-160=4
161-192=5
193-224=6
225-256=7
0-0=7
配置说明:
上面columns 标识将要分片的表字段,algorithm 分片函数,patternValue 即求模基数,defaoultNode 默认节点,如果不配置了默认,则默认是0即第一个结点
mapFile 配置文件路径
配置文件中,1-32 即代表id%256后分布的范围,如果在1-32则在分区1,其他类推,如果id非数字数据,则会分配在defaoultNode 默认节点
String idVal = "0";
Assert.assertEquals(true, 7 == autoPartition.calculate(idVal));
idVal = "45a";
Assert.assertEquals(true, 2 == autoPartition.calculate(idVal));
七、ASCII码求模通配
<tableRule name="sharding-by-prefixpattern">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-prefixpattern</algorithm>
</rule>
</tableRule>
<function name="sharding-by-pattern" class="io.mycat.route.function.PartitionByPrefixPattern">
<property name="patternValue">256</property>
<property name="prefixLength">5</property>
<property name="mapFile">partition-pattern.txt</property>
</function>
partition-pattern.txt
# range start-end ,data node index
# ASCII
# 48-57=0-9
# 64、65-90=@、A-Z
# 97-122=a-z
###### first host configuration
1-4=0
5-8=1
9-12=2
13-16=3
###### second host configuration
17-20=4
21-24=5
25-28=6
29-32=7
0-0=7
配置说明:
上面columns 标识将要分片的表字段,algorithm 分片函数,patternValue 即求模基数,prefixLength ASCII 截取的位数
mapFile 配置文件路径
配置文件中,1-32 即代表id%256后分布的范围,如果在1-32则在分区1,其他类推
此种方式类似方式6只不过采取的是将列种获取前prefixLength位列所有ASCII码的和进行求模sum%patternValue ,获取的值,在通配范围内的
即 分片数,
/**
* ASCII编码:
* 48-57=0-9阿拉伯数字
* 64、65-90=@、A-Z
* 97-122=a-z
*
*/
如
String idVal="gf89f9a";
Assert.assertEquals(true, 0==autoPartition.calculate(idVal));
idVal="8df99a";
Assert.assertEquals(true, 4==autoPartition.calculate(idVal));
idVal="8dhdf99a";
Assert.assertEquals(true, 3==autoPartition.calculate(idVal));
八、编程指定
<tableRule name="sharding-by-substring">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-substring</algorithm>
</rule>
</tableRule>
<function name="sharding-by-substring" class="io.mycat.route.function.PartitionDirectBySubString">
<property name="startIndex">0</property> <!-- zero-based -->
<property name="size">2</property>
<property name="partitionCount">8</property>
<property name="defaultPartition">0</property>
</function>
配置说明:
上面columns 标识将要分片的表字段,algorithm 分片函数
此方法为直接根据字符子串(必须是数字)计算分区号(由应用传递参数,显式指定分区号)。
例如id=05-100000002
在此配置中代表根据id中从startIndex=0,开始,截取siz=2位数字即05,05就是获取的分区,如果没传默认分配到defaultPartition
九、字符串拆分hash解析
<tableRule name="sharding-by-stringhash">
<rule>
<columns>user_id</columns>
<algorithm>sharding-by-stringhash</algorithm>
</rule>
</tableRule>
<function name="sharding-by-substring" class="io.mycat.route.function.PartitionByString">
<property name=length>512</property> <!-- zero-based -->
<property name="count">2</property>
<property name="hashSlice">0:2</property>
</function>
配置说明:
上面columns 标识将要分片的表字段,algorithm 分片函数
函数中length代表字符串hash求模基数,count分区数,hashSlice hash预算位
即根据子字符串 hash运算
hashSlice : 0 means str.length(), -1 means str.length()-1
/**
* "2" -> (0,2)<br/>
* "1:2" -> (1,2)<br/>
* "1:" -> (1,0)<br/>
* "-1:" -> (-1,0)<br/>
* ":-1" -> (0,-1)<br/>
* ":" -> (0,0)<br/>
*/
public class PartitionByStringTest {
@Test
public void test() {
PartitionByString rule = new PartitionByString();
String idVal=null;
rule.setPartitionLength("512");
rule.setPartitionCount("2");
rule.init();
rule.setHashSlice("0:2");
// idVal = "0";
// Assert.assertEquals(true, 0 == rule.calculate(idVal));
// idVal = "45a";
// Assert.assertEquals(true, 1 == rule.calculate(idVal));
//last 4
rule = new PartitionByString();
rule.setPartitionLength("512");
rule.setPartitionCount("2");
rule.init();
//last 4 characters
rule.setHashSlice("-4:0");
idVal = "aaaabbb0000";
Assert.assertEquals(true, 0 == rule.calculate(idVal));
idVal = "aaaabbb2359";
Assert.assertEquals(true, 0 == rule.calculate(idVal));
}
十、一致性hash
<tableRule name="sharding-by-murmur">
<rule>
<columns>user_id</columns>
<algorithm>murmur</algorithm>
</rule>
</tableRule>
<function name="murmur" class="io.mycat.route.function.PartitionByMurmurHash">
<property name="seed">0</property><!-- 默认是0-->
<property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否则没法分片—>
<property name="virtualBucketTimes">160</property><!-- 一个实际的数据库节点被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍-->
<!--
<property name="weightMapFile">weightMapFile</property>
节点的权重,没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
<!--
<property name="bucketMapPath">/etc/mycat/bucketMapPath</property>
用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
</function>
一致性hash预算有效解决了分布式数据的扩容问题,前1-9中id规则都多少存在数据扩容难题,而10规则解决了数据扩容难点