大致分为三类:数值型(整型、小数)、字符型(短文本、长文本)、日期型
类型 | 字节大小 | 容值范围 | 显示长度n最大值 |
---|---|---|---|
tinyint | 1 byte | 有符号:(-128,127) —无符号:(0,255) | tinyint(n): n<=3 |
smallint | 2 byte | 有符号:(-32 768,32 767)—无符号:(0,65535) | smallint(n): n<=5 |
mediumint | 3 byte | 有符号:(-8 388 608,8 388 607)—无符号:很大 | medium(n): n<=7 |
int、integer | 4 byte | 有符号:(-2 147 483 648,2 147 483 647)—无符号:很大 | int(n): n<=10 |
bigint | 8 byte | 有符号:很大—无符号:很大 | bigint(n): n<=19 |
- 创建一个字段: id int; id默认使用的范围是 有符号的情况,
如果要使用无符号的情况,则类型后面加限定: id intunsigned
;- 如果赋值的数据 超过 字段数据类型的 容值范围,会爆out of range 警告,而字段最后得到的数据也只能是 容值范围
最近接
赋值数据的 数值,例如 给无符号的类型 赋值一个负数,则字段最后得到的数值是0。- int(n) 中的 n 代表的是 数据在表中的 最大
显示宽度
,以字符
为计算单位,有的人认为是以 字节 为计算单位。- 如果不设置显示长度,则类型会使用默认的显示长度。如果赋值数据的宽度 超出 n值,则直接报错。
浮点数类型 | 字节 | 范围 |
---|---|---|
float(M,D) | 4 | 很大 |
double(M,D) | 8 | 更大 |
定点数类型 | 字节 | 范围 |
---|---|---|
decimal(M,D) | M+2 | 最大取值范围 和 double相同 |
- M: 整数部分的长度 + 小数部分的长度
D: 小数部分的长度- M 和 D 都是可以省略的,如果是 定点 省略的话,则M默认为10,D默认为0。
如果是 浮点 省略的话, M 和 D 则会根据插入的数值 浮动变化。
比如 浮点类型的字段 被赋值3.14, 则浮点的M 是3,D是2。
这也就是之所以叫 浮点 和 定点 的原因。- 从第二条也就可以看出 浮点 的精度 是浮动的,不是固定的,如果需要 固定精度的字段,比如和钱相关的,推荐使用 decimal。
类型 | 大小 | 用途 |
---|---|---|
CHAR | 0-255 bytes | 定长字符串 |
VARCHAR | 0-65535 bytes | 变长字符串 |
TINYTEX | 0-255 bytes | 短文本字符串 |
TEXT | 0-65 535 bytes | 长文本数据 |
LONGTEXT | 0-4 294 967 295 bytes | 极大文本数据 |
TINYBLOB | 0-255 bytes | 不超过 255 个字符的二进制字符串 |
BLOB | 0-65 535 bytes | 二进制形式的长文本数据 |
MEDIUMBLOB | 0-16 777 215 bytes | 二进制形式的中等长度文本数据 |
- 空间消耗上, 定长的 大于 变长的,
这是因为赋值字段的 字符串的长度 没有达到显示宽度时,定长的 会占用空格来填充空间,以使得 字符长度 == 显示宽度。 变长的则不会这样做。- 查找效率上, 定长的 大于 变长的。
类型 | 字节大小 | 范围 | 格式 | 用途 |
---|---|---|---|---|
DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 |
TIME | 3 | ‘-838:59:59’/‘838:59:59’ | HH:MM:SS | 时间值或持续时间 |
YEAR | 1 | 1901/2155 | YYYY | 年份值 |
DATETIME | 8 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 日期和时间值 |
TIMESTAMP | 4 | 1970-01-01 00:00:00- | YYYYMMDD HHMMSS | 时间戳 |
timestamp 和 实际时区有关,而datetime 只反映当地时区:
假设: 字段t1 是timestamp; 字段t2是datetime
-- 将mysql的时区设为 东九区 (比东八区快一个小时)
set time_zone = '+9:00'
-- 然后给t1 和t2 字段 插入当前时间
insert into tab (t1, t2) values (now(), now());
此时 t1是 东九区的时间, 而 t2 是 东八区的时间。
create table tab (
status enum('a','b', 'c')
);
insert into tab values ('a');
insert into tab values ('B');
insert into tab values ('f');--报错
insert into tab values ('a,c');--报错
表tab:
status |
---|
a |
b |
create table tab (
habit set('a', 'b', 'c')
);
insert into tab values ('a,B');
insert into tab values ('b,d');--报错
insert into tab values ('f'); --报错
表tab:
habit |
---|
a,b |
mysql -uroot -p123456
// 登录远程数据库
mysql -h125.21.74.1 -P3333 -uroot -p123456
show databases;
use 数据库名字
create database 数据库名字;
CREATE DATABASE `database` CHARACTER SET utf8 COLLATE utf8_general_ci;
drop database 数据库名字;
本地
数据库操作无需进入MySQL,通过mysqldump命令:
mysqldump -u root -p dbname > dbname.sql
mysqldump -u root -p dbname tablename > tablename.sql
mysqldump -u root -p -d dbname > dbname.sql
扩展: 进入 mysql 命令行界面, 使用
system + linux
命令, 就可以执行linux的系统命令
远程
数据库操作远程数据库 要开启允许远程操作的权限
mysqldump -h 123.12.1.123 -u root -p dbname > dbname.sql
用户名和密码均是数据库的,不是服务器的
source
命令导入指定de本地sql文件:source /home/hero/桌面/home.sql使用 新版的mysqldump 执行
mysqldump -h 123.12.1.123 -u root -p dbname > dbname.sql
会出现一下报错:
mysqldump: Couldn’t execute ‘SELECT COLUMN_NAME, JSON_EXTRACT(HISTOGRAM, ‘$.“number-of-buckets-specified”’) FROM information_schema.COLUMN_STATISTICS WHERE SCHEMA_NAME = ‘wx_service’ AND TABLE_NAME = ‘add_user’;’: Unknown table ‘column_statistics’ in information_schema (1109)
这是因为: 新版的mysqldump默认启用了一个新标志,通过–column-statistics=0来禁用此标志。
新版的mysqldump 可以如下操作:
mysqldump -uroot -p'密码' -h 192.168.3.82 --column-statistics=0 -B rxw2 --opt > rxw2.sql
MySQL数据库服务配置好后,系统会有以下默认的数据库:
- information_schema:虚拟对象,其对象都保存在内存中
- performance_schema:服务器性能指标库
- mysql:记录用户权限,帮助,日志等信息
show tables; --查看数据库中的 全部 表
describe tableName; --(简写: desc tableName;)查看表中的 全部 字段
SHOW TABLE STATUS --查看数据表类型
SHOW TABLE STATUS LIKE 'table_name'\G --查看指定数据表的详细信息 ( \G 竖着查看字段)
CREATE TABLE `table_name`(
`id` INT(11) UNSIGNED AUTO_INCREMENT COMMENT '用户id',
`uid` INT(11) UNSIGNED AUTO_INCREMENT COMMENT '用户id',
`name` VARCHAR(40) NOT NULL,
`del_time` INT(11),
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '若值为空则采用当前时间',
PRIMARY KEY ( `id` ) USEING BTREE, -- 主键索引
KEY `idx_table_name_uid` (`uid`) USING BTREE COMMENT '用户id', --普通搜因
UNIQUE KEY `idx_table_name_name` (`name`) --唯一索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=10000 COMMENT='desc info';
DROP TABLE table_name ;
// 如果表存在,则不创建 的创建表法
create table if not exists table_name (......);
// 如果表存在,则替换掉 的创建表法
DROP TABLE IF EXISTS `table_name`;
CREATE TABLE `table_name` (......);
说明:
- DEFAULT CHARSET 设置编码
- 关键字不区分大小写,建议大写,便于区分
- 强调: 创建表时: 表名 和 字段名 都是用
反引号
包裹的,不是 单引号!!!
当然 表名 和 字段名 也可以不用任何符号包裹,直接裸漏就可以了。- ENGINE 设置 表 的 存储引擎,每个表都可以有自己的存储引擎。
- 存储引擎 说白了就是
表类型
, 表类型 的选择 是 根据表的用途
决定的, 有的存储引擎 在 存储数据方面做的很好,有的存储引擎 在 数据查询方法做的很好等等, 所以 存储引擎 的选择 因地制宜吧。- 如果不选择则默认使用 InnoDB存储引擎。
- KEY 关键字用于 创建索引
- 注意:最外层括号中的 最后的建表语句(KEY `index_table_name_uid` (`uid`) USING BTREE COMMENT ‘用户id’)之后一定 不要写
逗号
,否则报错。- 除了数字类型 单独写一个 default 就可以 让默认值生效,其他类型的default 默认值 只有 在not null 后面 或者 null default 默认值 的情况下才生效,
- AUTO_INCREMENT=10000 让主键id不再 从1开始,而是从 10000开始 自增。这个字段对于 大数据表 分解 小表 时特别重要,他能保证 所有小表中的 主键 是 连续起来的。
假设现在有表abc,要完全复制一个新的表 aaa:
-- step1: 复制 表的 字段结构
SHOW CREATE TABLE abc; -- 查看创建abc表时 的SQL语句
CREATE TABLE aaa (根据查询的SQL语句 创建 表aaa)
-- step2: 复制 表的 具体数据
insert into aaa(id, name,old) select id,name,old from abc;
在表中唯一确定一条记录,该字段 不重复 不为空。
CREATE TABLE user(id INT PRIMARY KEY );
插入数据的时候可以不写自增约束的字段值
约束字段的字段值不能重复
约束字段的字段值不能为空
约束字段的字段值未传值便使用默认值。
create table AAA(
id int primary key,
name varchar(10),
BBB_id int,
foreign key(BBB_id) references BBB(id)
)
说明:
- 被引用的 表 为 主表, 引用别人的表 为 副表。
- 表AAA的BBB_id字段 指向外表 BBB 的id字段,称AAA表为副表,BBB表为主表。
- 主表 中没有的数值,在副表中 是不可以被使用的。
- 主表中的被引用的 记录, 在主表中 是不可以 被 删除的。 除非先删除 副表 中的引用。
- 添加数据: 先主后副; 删除数据: 先副后主
- 嵌套语句查询外键:
select * from AAA where BBB_id=(select id from BBB where name=“tom”);
-- 对已有表加外键
alter table 表1-表名
add constraint 外键名称(一般外键名称为”fK_”开头)
foreign key (要设为外键的列名)
references 表2-表名(与哪个表有关联) (表2中该列列名);
-- 栗子
alter table wtt.aaa
add constraint fk_pid
foreign key (pid)
references wtt.bbb(id);
添加外键失败说明:
唯一索引列
(一般是主键)的数据类型不同create table AAA(
id int 列级约束,
表级约束
)
- 上面 六大 约束 都可以作为 列级约束,但是注意, 外键约束 放在列级约束的位置 没有效果。
- 除了 非空、默认, 都可以作为 表级约束。
约束 | 保证字段值唯一性 | 是否允许为空 | 一个表中可以有多少个 | 是否允许组合索引 |
---|---|---|---|---|
主键 | yes | no | 1 | yse,但不推荐 |
主键 | yes | yes,但最多只允许有一个空的 | 多个 | yse,但不推荐 |
关系型数据库中的表与表不是彼此独立,而是相互关联的;
表与表之间的级联关系使得整个数据库成为一个有机关联的系统;
表关系可以分为一对一、一对多、多对多三种,它们的维护方式各不相同;
表关系的管理
是关系型数据库的重要组成部分,要牢牢掌握;
如果A表记录与B表记录有双向的一 一对应关系,我们就称它们之间有一对一的关系;
一对一关系的维护,由A、B中相对次要的一方来维护(这里假设是B),维护的方式是在B中插入一个指向A表主键的外键;
在一对一关系中,本来外键放在任意一方都是可行的,之所以要选择相对次要的一方,是因为万一不得以必须删除一个表时,我们会选择删除相对次要的表,此时由他所维护的关联关系也被一并删除,不会形成脏数据;
如果A表中的一条数据对应B表中的多条数据,而B表中的每条数据都对应一条唯一的A表数据,就称A表和B表是一对多的关系;
一对多关系的维护,由多方进行维护,维护方式是多方在表中添加指向一方的外键;
如果A表中的一条记录对应B表中的多条记录,B表中的一条记录也对应A表中的多条记录,就称A表和B表是多对多的关系;
多对多关系的维护,要通过建立中间表
来维护.
-- 加入数据 (下面有 加入数据的 补充说明,要仔细看一下)
INSERT INTO table_name ( field1, field2,...fieldN ) VALUES ( value1, value2,...valueN );
-- 查找数据 (condition 中的判断符号有: = <> > < <= >=)
SELECT field1, field2,...fieldN FROM table_name1, table_name2... WHERE condition1 [AND [OR]] condition2..
-- 更新数据
UPDATE table_name SET field1=new-value1, field2=new-value2 [ WHERE condition ]
-- 删除数据 (如果没有指定 WHERE 子句,将会删除表中的所有记录。)
DELETE FROM table_name [ WHERE ...]
-- 清空数据 (truncate会删除表中所有记录,并且将 重新设置 ==高水线== 和== 所有的索引==,有外键的表 不可以 用该方法 清空数据。该方法 不会记录日志,执行速度很快)
truncate table_name;
说明:
##############################
# 1、导入多个数据是,要使用一个IO
##############################
-- 使用3个IO
INSERT INTO aaa ( name, age ) VALUES ( 'tom', 11 );
INSERT INTO aaa ( name, age ) VALUES ( 'cat', 6 );
INSERT INTO aaa ( name, age ) VALUES ( 'miao', 8 );
--以上插入数据 开启了 三个 io,下面优化为 一个 io
INSERT INTO aaa ( name, age ) VALUES ( 'tom', 11 ), ( 'cat', 6 ), ( 'miao', 8 );
##############################
# 2、导入 另一个表中的 数据
##############################
-- 向 aaa表中 插入 bbb 表中的数据, 不再需要 values 关键字:
insert into aaa (a,b,c) (select a,b,c from bbb;)
-- 若插入aaa表中的数据字段,是aaa表中的 ==全部字段==, 则 aaa表之后的字段 可以 省略不写
insert into aaa (select a, b from bbb where a = 18)
需要修改 数据表名
或者 字段
时,就要用到ALTER命令。
-- 修改 数据表 的名字
ALTER TABLE table_name RENAME TO new_table_name;
-- 增加一个name字段(默认插在最后
ALTER TABLE table_name ADD name VARCHAR(10);
-- 增加 多个 字段
ALTER TABLE orders
ADD send_at int(10) default 0 COMMENT '发货时间',
ADD fin_at int(10) default 0 COMMENT '确认收货(完成)时间',
ADD refuse_at int(10) default 0 COMMENT '拒绝退货申请',
ADD refuse_reason varchar(100) default '' COMMENT '拒绝退货申请',
ADD agree_at int(10) default 0 COMMENT '同意退货申请';
-- 删除 name 字段(表中name字段的数据也会一同被删除)
ALTER TABLE table_name DROP name ;
-- 删除 多个 字段
ALTER TABLE orders
DROP send_at,
DROP agree_at;
-- 将字段name的 字段名改为 caller, 类型改为 varchar(10)
ALTER TABLE table_name CHANGE name caller varchar(10);
说明:
FIRST
or AFTER+已有字段名
来手动控制 新加字段的摆放位置:ALTER TABLE table_name ADD name VARCHAR(10) AFTER id;
==》name字段 在 id字段 之后且紧挨着。DEFAULT
设置一个 默认值: ALTER TABLE table_name ADD name VARCHAR(10) DEFAULT ’小明’;
字段 和 表名 都可以起别名,方式有两种:
as
关键字SELECT name as 别名 FROM table_name;
空格
SELECT name 别名 FROM table_name;
注意: 如果一个别名中有空格,则外层必须用单引号包裹: ‘see you’
select id, name from aaa;
返回结果:
id | name |
---|---|
1 | tom |
select id aaa, name bbb from aaa;
返回结果:
aaa | bbb |
---|---|
1 | tom |
2.给 表名 起别名:
select * from aaa 别名a inner join bbb 别名b on 别名a.id = 别名b.id;
注意: 如果为 表名 起了 别名,那么 凡是用到 表名的地方 都要使用 别名。
SELECT * FROM table_name WHERE name LIKE '王%';
说明:
- 只要开头带王的,都可以被搜出来。
- 百分号 %字符来表示任意字符,没有百分号 %, LIKE 子句 和 等号 = 就是一样的了。
SELECT name FROM t1
UNION [ALL]
SELECT name FROM t2;
说明:
- UNION 操作符用于 查询的字段相同的 多表进行联合,一般而言被联合的表,没有什么关系。
而 join 联合的多表 一般 是有 外键的指向关系的- 默认情况下 UNION 操作符会删除了重复数据, 如果查询的多个字段,除非多个字段的值 都一致,才会去重。
- 如果不想 去除重复的数据 UNION 后面加 ALL 操作符。
demo:查询两个表的 fid 字段,要求合在一起的数据要按照 各自的时间信息 排序,且fid不能重复。
SELECT fid FROM (
SELECT fid, addtime FROM aaa
WHERE id = 754238
AND fid NOT IN (SELECT fid FROM bbb WHERE id = 754238) -- 这一句是为了fid去重
UNION
SELECT fid, addtime FROM bbb WHERE id = 754238
) tmp ORDER BY tmp.addtime
SELECT * FROM table_name ORDER BY name DESC;
说明:
- ORDER BY 默认是按照
ASCII表
升序排列,这里加了 DESC 就变成降序了。- ASC: 升序; DESC: 降序。
- order by与limit的写顺序是:先写order by,再写limit
-- 案例:按照名字的字节长度 排序
SELECT * FROM table_name ORDER BY length(name);
-- 排序: 先按照id升序,如果记录的id值有一样的 就让再按照 age 降序 排列:
select * from a order by id asc, age desc;
AAA表:
id | name | sex |
---|---|---|
1 | tom | man |
2 | cat | woman |
3 | miao | man |
BBB表: | ||
caller | gender | |
------ | ------ | |
aaa | man | |
bbb | unknow |
SELECT * FROM AAA JOIN BBB ON AAA.gender = BBB.sex;
-- 内连接的 简写,(当出现多层内连接嵌套是,这种简写就显得很好用了:)
select * from AAA,BBB where AAA.gender = BBB.sex;
联合表:内连接
id | name | sex | caller | gender |
---|---|---|---|---|
1 | tom | man | aaa | man |
3 | miao | man | aaa | man |
SELECT * FROM (AAA left join BBB ON gender = sex);
联合表:左连接
id | name | sex | caller | gender |
---|---|---|---|---|
1 | tom | man | aaa | man |
3 | miao | man | aaa | man |
2 | cat | woman | NULL | NULL |
SELECT * FROM (AAA right join BBB ON gender = sex);
联合表:右连接
id | name | sex | caller | gender |
---|---|---|---|---|
1 | tom | man | aaa | man |
3 | miao | man | aaa | man |
NULL | NULL | NULL | bbb | unknow |
SELECT *
FROM AAA
JOIN BBB
ON AAA.gender = BBB.sex
JOIN CCC
ON AAA.id=CCC.pid
WHERE AAA.id = 1
LIMET 10
表aaa:
id | name |
---|---|
1 | NULL |
2 | tom |
SELECT * FROM aaa WHERE name = NULL; --筛选结果为空
SELECT * FROM aaa WHERE name IS NULL; --筛选结果 正常
SELECT * FROM aaa WHERE name <> NULL; --筛选结果为空
SELECT * FROM aaa WHERE name IS NOT NULL; --筛选结果 正常
SELECT * FROM table_name WHERE name REGEXP '^王';
说明: 只要开头带王的,都可以被搜出来。
-- 查出3条数据 便可以停止查询
select * from aaa where id=1 limit 3;
-- 从索引为10的记录(起始索引为0)开始找,查出2条数据便可以停止查询
select * from student LIMIT 10, 2;
SELECT * FROM score WHERE age BETWEEN 60 AND 80; --包边的
SELECT * FROM score WHERE age < 81 AND age > 59 ; --不包边的
SELECT * FROM score WHERE age NOT BETWEEN 60 AND 80;
SELECT * FROM score WHERE age IN(85, 86, 88);
SELECT * FROM score WHERE age NOT IN(85, 86, 88);
SELECT * FROM student WHERE name = 'tom' OR age > 10;
SELECT * FROM student WHERE name = 'tom' AND age > 10;
常常搭配着 统计函数 使用,其语法形式如下:
select 统计函数, 分组列(出现在group by 后面)
from 表
[where 条件]
group by 分组的列
[order by ...]
select max(money), depart from employee group by depart;
select max(money), depart from employee group by depart
having max(money) > 10000;
select count(*), length(name) from employee
group by length(name)
having count(*) > 5;
-- 给以上 函数加上 别名, 在写一遍:
select count(*) num, length(name) len from employee
group by len
having num > 5;
select avg(tall), cloth, grade from shool
group by cloth, grade;
select avg(money), depart from employee
group by depart
having avg(money) > 10000
order by avg(money) DESC;
现有求职人员如下三人, 工厂招工的原则就是工作人员不能出现重名,至于性别不作要求,
但是要确切的知道 入职人员的id和sex,
请根据下面的求职人员表,给出符合进场要求的结果:
求职人员表:
id | name | sex |
---|---|---|
1 | tom | man |
2 | tom | woman |
3 | cat | man |
分析: 这里考察的就是去重, 如果用distinct 去重 name 字段,
select id, distinct name, sex from user;
会报错的,因为 两个tom的id和sex不一样,mysql 不知道该显示谁的。
如果用 分组机制去重
select * from user group by name;
也会报错,但是报错的原因和distinct不一样,报错信息如下:
which is not functionally dependent on columns in GROUP BY clause;...sql_mode=only_full_group_by
错误原因分析:
数据库为
only_full_group_by
模式, 而only_full_group_by的语义就是确定 select 之后获取的 信息值 都是明确语义,
简单的说来,在此模式下,信息值要么是来自于聚合函数(sum、avg、max等)的结果,要么是来自于group by list中的表达式的值
换句话说,信息值 不能出现多个情况都可以, 让 mysql 自己选择的情况,例如 name分组下, tom的分组的 sex字段 显示man和woman 都可以,但是mysql无法做主。
MySQL5.7之后,sql_mode中ONLY_FULL_GROUP_BY模式默认设置为打开状态。
解决方法:
MySQL提供了
any_value()函数
来抑制 only_full_group_by值被拒绝,
any_value()会选择被分到 同一组的数据里 第一条 数据的指定列值作为返回数据
注意: any_value 不要妄想和 distinct 配合使用, distinct没有组的概念,又来何来同一组的数据里 第一条
select any_value(id) id , any_value(sex) sex, name from user group by name;
这样就可以了, 就能得出 符合入厂 的人员列表了。
时间戳
按 天
分组 (统计中常用)SELECT FROM_UNIXTIME(create_time,'%Y-%m-%d') as date, COUNT(*)
FROM user
WHERE del_time <> 0
GROUP BY FROM_UNIXTIME(create_time ,'%Y-%m-%d');
select distinct name from aaa;
注意:
SELECT DISTINCT name, sex FROM aaa;
这种写法会报错,因为会出现 名字相同 性别却不同的情况, 数据库去重 却不知道该怎么舍去。
所以 去重 最好是 针对 单个字段 搜索的情况。
SELECT *, (st_distance(point(lng,lat),point(118.3365,35.0569))*111195/1000 ) as '距离'
FROM aaa
having '距离' < 10 ORDER BY '距离' ASC;
距离单位 公里
米
SELECT ST_Distance_Sphere(POINT(116.4025249,39.9251859),POINT(116.4025249,39.9250644)) AS distant;
# 距离验证 ,单位 公里
SELECT (ST_Distance_Sphere(point(117.32,37.64),point(119.43, 49.32))/1000) AS distant;
如果一个雇员的id是奇数并且他的名字不是以’M’开头,那么他的奖金是10,否则奖金为0。
select
employee_id,
case
when mod(employee_id,2)=1 and LEFT(name,1)!='M' then 10
else 0
end bonus
from Employees
order by employee_id
sex 字段中,将 man 改为 男, 将 woman 改为 女。
update Aaa
set sex=
case sex
when 'man' then '男'
when 'woman' then '女'
else '未知'
end
where sex <> '';
通过where条件,拼接两个表, 然后 将一个表中的 数据列 对应的复制到 另一个 表中 指定的列。
UPDATE user_active aaa, users bbb
set aaa.name = bbb.name
where aaa.uid = bbb.id
UPDATE users_offer a
LEFT JOIN business_offer b
ON a.id =b.users_offer_id
SET a.del_at=123, b.del_at=321,a.name=b.caller
WHERE a.id <> 0;
删除表中 重名的记录,将 年龄 最小的 保留下来
+----+--------+
|age | names |
+----+--------+
| 1 | tom |
| 2 | tom |
| 3 | tom |
+----+--------+
-- 自己 拼接 自己
DELETE aaa FROM user aaa, user bbb
WHERE aaa.names = bbb.names AND aaa.age > bbb.age;
MySQL数据去重 且 保留取最新(或 最旧)的记录
# aaa 表
id|age|name|
--+---+----+
1| 1|tom |
2| 1|tom2|
3| 2|cat1|
4| 2|cat2|
去重字段 是 age, 去除 age相同的 记录。
这里数据的新旧通过id字段体现,id 越大 记录 越新。
-- 获取 最旧 的数据
SELECT t1.*
from aaa t1
left join aaa t2
on t1.age = t2.age and t1.id > t2.id -- 通过调控这个条件 控制 最新、最旧
WHERE t2.id IS NULL
AND t1.age <> 0
ORDER by t1.id
LIMIT 0,10;
-- 结果如下:
id|age|name|
--+---+----+
1| 1|tom |
3| 2|cat1|
-- 获取 最新 的数据
SELECT t1.*
from aaa t1
left join aaa t2
on t1.age = t2.age and t1.id < t2.id
WHERE t2.id IS NULL
AND t1.age <> 0
ORDER by t1.id
LIMIT 0,10;
if 多条件 可选条件 的sql 语句
要求 搜索 临沂市的 人, 如 前端 传过来 age字段,则 搜索 age 大于18 的人,如果 前端 传过来 sex字段,则 搜索 sex=1 的人。
其中 搜索的数据 在 user表中, id字段为主键(不可为0)
select * from user
where city = '临沂市'
and if(age=0, id<>0, age > 18)
and if(sex=0, id<>0, sex = 1); // 以 id<>0 这个 无作用条件 为 托词,不行 就抛出去。
洋葱模型 的 概念 来自于 洋葱的横截面图,描述的是一种 顺序。
对于 sql 语句的 编译、执行,正是 采用了 洋葱模型。
- 编译
sql语句 先从外往里
进行 整体的编译,目的是为了 确定一下 涉及到的 表 和 相关字段。- 执行
sql语句 的 执行 是从里往外
进行的。
要求 一次性 查询到 某个 用户的 好友列表 的 好友名称、id 以及 该用户 的好友 拥有的 好友数。
其中 好友名称 和 好友的id 都是单字段
, 而 好友拥有的好友数 是复字段
。
select fid, name,
(select count(*) from user_friend as aaa where aaa.uid = bbb.fid) num
from user_friends as bbb
where bbb.uid = 1;
例子简述:
bbb 是外部的 sql语句, aaa是内部的sql语句,因为 编译的时候 已经知道 bbb代表的含义了,
所以 在 内部sql语句中 可以 直接 执行使用 bbb。
如果 编译 和 执行 都是 从里到外 的话, bbb 代表的含义 内部sql 执行的时候 是不知道的。
当有人问到索引是什么时,大家都喜欢用“书的目录”来做类比,没有索引的数据库,就像是没有目录的书,想找第3章的第7小节,就要一页一页翻过去,最可怕的是翻到了还要把这本书翻完才算完;那反之有索引的数据库,就是有目录的书了,直接按目录找到就可以了。
在数据库中 索引 说白了也是一张表, 是程序通过算法内演的一张虚表,该表保存了主键与索引字段,并指向实体表的记录。
创建索引时,你需要确保: 1、表的数据量很大; 2、该索引是应用在 SQL 查询语句
的条件(一般作为 WHERE 子句的条件)。
好处: 索引大大提高了查询速度。
坏处: 降低更新表的速度,因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
单列索引
:即一个索引只包含单个列,一个表可以有多个单列索引。组合索引
:即一个索引包含多个列。
组合索引利用好了, 比创建多个 单列索引 的效果要好。
因为 一个 组合索引 虽然 给 多个 列 做了索引规划, 但是终究是一个索引,占用的资源就是一个索引的资源。 而 对于 创建多个 单列索引 ,占用的 资源是 多个 索引的资源。
最基本的索引类型,允许在定义索引的列中插入
相同的值
和空值
。
定义索引的列 不允许出现 相同的值,但是允许有
空值
。
主键会自动创建 主键索引,主键列中的每个值是
非空
和唯一的
。
联合主键 也是 主键索引 的一种。
主要针对 多文本字段, 具体的就是 进行 全文检索,即 分词 操作 得到一个 倒排索引表。
但是 mysql的 分词 对中文很不友好, 对英文 还可以。
所以 建议 用到 全文索引 的场景 还是 使用 ESearch, 专业的事 就交给 专业的人 来做。
即一个索引包含多个列。
通过
INDEX
或KEY
选项来添加索引。这两种关键字是等效的,都可以用来创建索引
-- 普通索引
create table `user`(
id int(10),
index user_id(id) -- user_id 是 索引名
)
-- 指定索引 数据结构
create table `user`(
id int(10),
index user_id(id) USING BTREE
)
-- 唯一索引
create table `user`(
id int(10),
unique index user_id(id)
)
-- 全文索引
create table `user`(
id int(10),
fulltext index user_id(id)
)
-- 多列索引
create table `user`(
id int(10),
name varchar(5),
key user_id_name(id, name)
)
-- 主键索引
create table `user`(
id int(10),
primary key (`id`)
)
-- 外键索引
create table `user`(
id int(10),
goods_id int(10),
primary key (`id`),
-- FOREIGN KEY (`goods_id`) REFERENCES `goods` (`id`) // 自动命令 外键索引
CONSTRAINT `外键名称` FOREIGN KEY (`goods_id`) REFERENCES `goods` (`id`) // 手动命名 外键索引
)
-- 普通索引
create index user_id on user(id); -- user_id 是 索引名
-- 指定索引 数据结构
create index user_id on user(id) USING BTREE;
-- 唯一索引
create unique index index_name on table_name(fields);
-- 全文索引
create fulltext index index_name on table_name(fields);
-- 多列索引
create index index_fields1_fields2 on table_name(fields1, fields2)
-- 主键索引
ALTER TABLE user ADD PRIMARY KEY (id);
-- 查看索引(\G表示将查询到的横向表格纵向输出,方便阅读)
SHOW INDEX FROM table_name\G
-- 此时 查询 有索引的 列 就会 走索引 查询
SELECT 字段名b FROM 表名A;
-- 索引优先,这种情况 也会 走 索引
select * from 表名A where 字段名b=1;
-- 修改索引的名字
ALTER INDEX 索引名 RENAME TO 新索引名;
-- 删除索引
DELETE INDEX 索引名;
在MySQL中,变量一般可分为分为三种类型: 系统变量、用户定义变量、局部变量;
系统变量是 MySQL服务器 系统自身 提供的,分为 全局变量(GLOBAL)
、会话变量(SESSION)
;
SHOW [ SESSION | GLOBAL ] VARIABLES; -- 查看所有系统变量
SHOW [ SESSION | GLOBAL ] VARIABLES LIKE '......'; -- 可以通过LIKE模糊匹配方 式查找变量
SELECT @@[SESSION | GLOBAL] 系统变量名; -- 查看指定变量的值
如果没有指定SESSION/GLOBAL,默认是SESSION,会话变量。
SET [ SESSION | GLOBAL ] 系统变量名 = 值 ;
SET @@[SESSION | GLOBAL]系统变量名 = 值 ;
说明:
用户定义变量,是用户根据需要自己定义的变量,用户自定义变量不用提前声明,在用的时候直接用 “@变量名” 使用就可以。其 作用域 为 当前session
的连接。
SET
关键字, 赋值时,可以使用 =,也可以使用 := 。
SET @var_name = expr [, @var_name = expr] ... ;
SET @var_name := expr [, @var_name := expr] ... ;
SET
关键字, 赋值时,可以使用 =,也可以使用 := 。
SELECT @var_name := expr [, @var_name := expr] ... ;
SELECT 字段名 INTO @var_name FROM 表名;
说明:
NULL
而已;先声明 后使用。可用作存储过程内的 局部变量 和 输入参数,局部变量的 作用于 BEGIN 和 END 之前。
-- 变量类型就是数据库字段类型,可选值包括:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等。
DECLARE 变量名 变量类型 [DEFAULT ... ] ;
-- 栗子
create procedure test()
BEGIN
declare my_count int default 0;
select count(*) into my_count from account;
select my_count;
END;
declare my_count int default 0;
-- 方式一
select count(*) into my_count from account;
-- 方式二
set my_count := 123
游标(cursor):是用来存储 查询结果集
的 数据类型 , 在 存储过程 和 函数 中可以使用 游标 对结果集进行 循环 的处理
游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录进行定位,并对指向的记录中的数据进行操作的数据结构。游标让 SQL 这种面向集合的语言有了面向过
程开发的能力。
在 SQL 中,游标是一种临时的
数据库对象,可以指向存储在数据库表中的数据行指针。
游标 必须 先定义,后使用。DECLARE 命名游标,并定义相应地select 语句。
SQLSTATE'02000'
是一个未找到条件 即 select查询不到数据的 情况
DECLARE 游标名称 CURSOR FOR 查询语句(select name,age from users) ;
-- 声明游标时 建议 加上 声明条件处理程序 : 当SQL语句执行抛出的状态码为02000时,将关闭游标u_cursor,并退出
declare exit handler for SQLSTATE '02000' close u_cursor;
声明游标 仅仅是 定义 游标, 后面的 select语句不会执行。
打开游标 这个操作,才会 根据 select语句,将指定范围的数据 检索到 游标 中。
OPEN 游标名称 ;
在结束游标使用时,必须关闭游标, 会释放游标使用的所有内部内存和资源.
如果不手动的关闭游标,mysql 将会在到达end 关键字
的时候自动关闭它。
CLOSE 游标名称 ;
into 后面 接受游标记录数据 的 变量的 个数 要和 select 后的字段的个数 一致。
使用 fetch 语句分别访问它的 每一行。
FETCH 游标名称 INTO 变量1 [, 变量2 ] ;
注意:
所谓的 存储过程 就是一组预先编译好的
SQL语句
的集合, 存储的是 sql的执行过程。
好处:
(1)、封装,复用, 可以把某一业务SQL封装在存储过程中,需要用到的时候直接调用即可;。
(2)、可以接收参数,也可以返回数据, 在存储过程中,可以传递参数,也可以接收返回值;。
(3)、减少网络交互,如果一次操作涉及到多条SQL,每执行一次都是一次网络传输。 如果将这些sql操作封装在存储过程中,只需网络交互一次可能就可以了;。
说明: 如果我们开启了 bin-log
, 我们就必须为我们的 function/procedure 指定一个参数。
-- 临时
set global log_bin_trust_function_creators=TRUE;
-- 永久, 在配置文件/etc/my.cnf的[mysqld] 下:
log_bin_trust_function_creators=1
CREATE PROCEDURE 存储过程名称 ([ 参数列表 ])
BEGIN
-- 一组合法的SQL语句
END ;
-- 栗子
create procedure test(in score int,out result varchar(12))
BEGIN
if score >= 85 then
set result := '优秀';
elseif score >= 60 then
set result := '及格';
else
set result := '不及格';
end if;
END;
说明:
- 结束标志
在sql中 分号 ;代表这一条语句的结束;
存储过程体中,每一条sql语句末尾都要加上分号,所以在创建的过程中,
要改变 sql 的结束标志,一般建议改为 $$delimiter $$ # 结束标志 改成 $$ delimiter ; # 再改回来
存储过程中使用到的参数的类型,主要分为以下三种:IN、OUT、INOUT;
- 参数模式:
in:该类参数作为输入,也就是需要调用时传入值
out:该类参数作为输出,也就是该参数可以作为返回值
inout:既可以作为输入参数,也可以作为输出参数
调用函数 用 select, 而调用 存储过程用的是 call
CALL 存储过程名称;
DROP PROCEDURE [ IF EXISTS ] 存储过程名称 ;
SHOW CREATE PROCEDURE 存储过程名称;
根据传入的参数salary,来查询用户表employees中,所有工资大于等于15000的员工ID,以及first_name,并将员工的ID和first_name插入到一张新表;
create procedure p_emp(in in_salary int)
begin
declare u_empid int(12);
declare u_fname varchar(100);
declare u_cursor cursor for select employee_id,first_name from employees where salary >= in_salary;
-- 声明条件处理程序 : 当SQL语句执行抛出的状态码为02000时,将关闭游标u_cursor,并退出
declare exit handler for SQLSTATE '02000' close u_cursor;
drop table if exists tb_em_pro;
create table if not exists tb_em_pro(
id int primary key auto_increment,
employee_id int(12),
first_name varchar(20)
);
-- 打开游标
open u_cursor;
while true do
fetch u_cursor into u_empid,u_fname;
insert into tb_em_pro values (null, u_empid, u_fname);
end while;
-- 关闭游标
close u_cursor;
end;
CREATE procedure wtt_t5()
begin
START TRANSACTION;
INSERT INTO aaa.admin (name, avatar, gender, tel, password, types, stats, create_at, update_at, del_at) VALUES("caller", '', 1, '', '', 1, 1, 0, 0, 0);
UPDATE aaa.admin set name="abc" where id = 11111;
commit;
end;
CALL wtt_t5();
简单的理解 存储函数 就是 有返回值
的 存储过程,存储函数 的参数只能是IN类型的。
CREATE FUNCTION 存储函数名称 ([ 参数列表 ])
RETURNS type [characteristic ...]
BEGIN
-- SQL语句
RETURN ...;
END ;
-- 栗子
create function fun_add(n int)
returns int deterministic
BEGIN
declare sum int default 0;
while n > 0 do
set sum := sum + n;
set n := n - 1;
end while;
return sum;
END;
SELECT 存储函数名称(参数);
- 视图是一个
虚拟表
,是sql的查询结果
,是一个逻辑表
,本身并不包含数据。作为一个select语句
保存在数据字典中的。- 外表看起来 同真实的表一样,视图包含一系列带有名称的列和行数据,在使用视图时动态生成。
- 视图的数据变化会影响到
基表
,基表的数据变化也会影响到视图 ;(基表:用来创建视图的表叫做基表base table)- 创建视图 需要
create view 权限
,并且对于查询涉及到的基表 有select权限
;- 使用create or replace 或者 alter修改视图,那么还需要改视图的drop权限。
- 控制 一个表中
字段 的 隐匿 和 开放
,比如:只把可以让访问的字段放到视图中。简化 复杂的查询
,比如将多个关联表 拼合成 一个视图表。优化 查询速率
,比如:表的行数超过200万行时,就会变慢,可以通过视图将表分为4个部分表。
create [or replace]
[algorithm = {undefined | merge | temptable}]
[definer = {user | current_user}]
[sql security { definer | invoker}]
view 视图名 [(列名1, 列名2, ......)]
as
select 列名, 函数, 等 from 表名 [where ...]
[with [cascaded | local] check option]
说明:
若创建视图 已经存在,则替换已有视图
选择在处理定义视图的select语句中使用的方法
要查询一个视图,要有2个权限:
- 对 视图 的 访问权限。
- 对 视图基表 的 select权限。
如果 一个用户 只有1权限,没有2权限,那会怎么样?
SQL SECURITY选项 决定执行 的结果:
SQL SECURITY DEFINER:创建
视图 的 用户 必须对视图所访问的 表 具有select权限,也就是说将来 其他用户访 问表的时候 以定义者 的身份,此时其他用户并没有访问权限。
SQL SECURITY INVOKER:访问
视图 的 用户 必须对视图所访问的 表 具有select权限。
视图权限总结:
使用root用户定义一个视图(推荐使用第一种):u1、u2
定义者
的身份访问 可以查询 到基表的内容;调用者
的身份,此时调用者是u2, 可以查询 到基表的内容。对于可以执行DML操作的视图,定义时可以带上WITH CHECK OPTION约束
其作用: 对视图所做的DML操作的结果,不能违反视图的WHERE条件的限制。
举例说明:
create view v_aaa
as
select * from aaa where age > 10
with check option;
# 对视图进行 更新操作
update v_aaa set age = 5 where id=1;
# 以上的更新是不被允许的,因为 更新的数据 违反了 视图中的 age>10 的条件。
# 所以, 利用with check option约束限制,保证更新视图是在该视图的权限范围之内。
cascade
是默认值,表示更新视图的时候,要满足视图和表的相关条件;local
表示更新视图的时候,要满足该视图定义的一个条件即可嵌套视图:定义在另一个视图的上面的视图;
WITH CASCADED CHECK OPTION:检查所有的视图,例如:嵌套视图及其底层的视图;
WITH LOCAL CHECK OPTION:只检查将要更新的视图本身,对嵌套视图不检查其底层的视图;
-> create view v_aaa (编号,name,性别)
-> as
-> select num,name,sex from aaa where sex = 'F'
-> with check option;
select * from v_aaa;
+--------+-----------+--------+
| 编号 | name | 性别 |
+--------+-----------+--------+
| 8 | Newcastle | F |
| 27 | Collins | F |
| 28 | Collins | F |
| 104 | Moorman | F |
| 112 | Bailey | F |
+--------+-----------+--------+
-> create view v_aaa_bbb (编号,name,性别)
-> as
-> select aaa.num, aaa.name, bbb.sex from aaa,bbb where aaa.id = bbb.id;
注意:
show create view 视图名;
SELECT * FROM information_schema.views where table_name = 'my_view';
drop view [if exists] 视图名[,视图名…];
说明:
修改视图是指修改数据库中已存在的表的定义,
当基表的某些字段发生改变时,可以通过修改视图来保持视图和基本表之间一致
case1:
create or replace view view_name as select语句;
case2:
ALTER
[ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
[DEFINER = { user | CURRENT_USER }]
[SQL SECURITY { DEFINER | INVOKER }]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]
因为视图本身没有数据,因此对视图进行的 DML操作 最终都体现在基表中
所以,在创建示图时 的 select 语句中 有下列内容之一,视图不能做DML操作:
在数据表中发生了某 件事(插入、删除、更新操作),然后
自动触发
了预先编好的若干条SQL语句
的执行。
触发事件的操作和触发器里面的SQL语句是一个事物操作,具有
原子性
,要么全部执行,要么都不执行。
create trigger 触发器名字 执行时机 触发器类型
on 表明 for each row
begin
# sql执行体
一系列sql语句操作;
end;
# 说明:
执行时机: AFTER | BEFORE =》指定触发器中的 sql操作 发生在 事件发生 的前后 ;
触发器类型:INSERT | UPDATE | DELETE ;
FOR EACH ROW: 表示任何一条记录上的操作满足触发事件都会触发该触发器;
# 查看全部 类型为 INSERT 的触发器
show triggers where Event='INSERT';
# 查看指定触发器
show create trigger 触发器名字;
drop trigger 触发器名字;
new 表
和 old表
MySQL 中定义了 new 和 old 两个临时表,用来 保存 触发了触发器的那一行数据 的 前后变化的数据
- 在 INSERT 型触发器中,new 用来
拦截
并保存``将要(BEFORE)
或已经(AFTER)
插入的新数据;- 在 UPDATE 型触发器中,old用来 拦截 并 保存 将要或已经被修改的
原数据
,new 用来拦截并保存将要或已经修改为的新数据
。- 在 DELETE 型触发器中,old 用来拦截并保存将要或已经被删除的
原数据
。
例子
TABLE `aaa` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`account` varchar(20) DEFAULT '' COMMENT '帐号',
`age` tinyint(4) DEFAULT '0',
PRIMARY KEY (`uid`)
)
TABLE `abc` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
)
# 案例一:
##
在 Dbeaver 的 sql编辑板 中 要把 穿件触发器的所有`选中` 之后 再进行 语句执行,
否则 sql执行体 中的 分号 会被认为是 创建 触发器 sql 语句 结束 的标志,会报语法错误。
##
create trigger wtt_test_trg
after INSERT on aaa for each row
BEGIN
INSERT into abc (name) values (new.account);
END;
# 案例二:
##
在 命令行 中 创建 触发器 需要 将mysql默认结束符进行修改,
在 sql执行体 可以加入判断语句,让触发器更聪明。
##
delimiter $$ # 将mysql默认结束符;换为$$
create trigger tri_afrer_insert_cmd after insert on cmd for each row
begin # 触发器代码
if new.account = '123456789' then # mysql中if语句固定格式
INSERT into abc (name) values (new.account);
end if;
end $$
delimiter ; # 结束触发器要把默认结束回来 不然后续操作容易混淆
事件: 对一个数据库而言,在指定的时间,按照一定的时间间隔,执行预设的SQL语句。
优点: 一些对数据定时性操作不再依赖外部程序,而直接使用数据库本身提供的功能。
缺点: 触发事件的机制只能是时间,不能是别的动作。
进入mysql,通过:
show variables like 'event_sch%';
首次 查看 事件 是否开启,得到以下内容:
Variable_name | Value |
---|---|
event_scheduler | OFF |
可见 默认情况下 是没有 开启的。要想使用事件 首先要 开启 事件:
set global event_scheduler=1;
-- 栗子:(周期执行Every:单位有second、minute、hour、day、week、quarter、month、year;)
-- On Schedule Every 1 second // 每秒执行1次
create event 事件名 on schedule 时间间隔 starts 时间 ends 时间 do sql语句;
-- 创建事件
create event insert_data on schedule every 2 second do insert into abc (id,name,gender) values (3,'tom','man');
-- 更改事件 (最好把事件停下 来后再进行修改)
alter event insert_data on schedule every 2 second do insert into abc (id,name,gender) values (44,'cat','woman');
-- 让事件停止执行
ALTER EVENT insert_data DISABLE;
-- 让停止的事件 运行起来
alter event insert_data enable;
-- 查看所有事件
SELECT * FROM information_schema.events;
-- 删除事件
DROP EVENT insert_data;
注意:
如果没有starts 则默认为现在; 如果没有ends 则默认为 永远。
- 事务:
数据库管理系统工作的一个逻辑单位,由一个或多个SQL语句构成。
事务结束的标记: 成功下:commit; 失败下:rollback。- 场景:
资金流动相关的场景。- 支持:
InnoDB存储引擎支持 事务。- 四大特性(ACID):
- 原子性(Atomicity,或称不可分割性)
一个事务的一系列SQL操作是一个最小单位,所有的SQL操作语句是一体的,要么大家都执行成功,要么大家都执行失败。绝不会出现部分SQL语句执行成功的情况。
事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。- 一致性(Consistency)
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
举例,A表转账B表,如果A少了500,而B却增加了100,这就不满足一致性了。
一致性 通常在 代码中空值,数据库中对于一致性不好操作。- 隔离性(Isolation,又称独立性)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。- 持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
- autocommit:
【1】、每个SQL语句之所以 能改变数据表中 的数据,就是因为数据库的autocommit
是默认开启的。
【2】、每次执行完SQL语句,结果数据都是在缓存中的,只有commit一下,结果数据才会到数据库中,开启了 autocommit,意味着每次执行完SQL语句,数据库就会自动 进行commit 把缓存中的数据加载到数据库中。
【3】、autocommit开启的情况下,一句SQL语句就是一个事务。
【4】、用 SET 来改变 MySQL 的自动提交模式:
SET AUTOCOMMIT=0 禁止自动提交
SET AUTOCOMMIT=1 开启自动提交
举例子:
begin; -- 手动开启一个事务
insert into table_name (id) value(5);
commit; -- 提交事务, 事务结束, 数据表中 已添加 一条记录
begin; -- 开事务
insert into table_name (id) value(6);
rollback; -- 回滚 到事务执行前的状态,数据表中 未添加 一条记录。
【1】、脏读: 一个事务读取到另一个事务由于 添加
操作 产生在缓存中的数据
(没有commit到数据库中的数据在缓存中保存着)。
【2】、幻读: 一个事务读取到另一个事务由于 更新
or 删除
操作 产生在缓存中的数据
。
【3】、不可重复读: 两个事务同时工作,
事务A执行:select name from table_name where id=1
,
事务B执行:update table_name set name=“tom” where id=1
;
对于事务A而言,在一个事务中
,同样的操作,获取的结果却是不一样的
。
解决事务并行带来的问题:
隔离等级 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read Uncommit(未提交读) | yse | yse | yse |
Read Committed(已提交读) | 解决脏读 | yse | yse |
Repeatable Read(可重复读) | 解决脏读 | 解决不可重复读 | yse |
Serializable(串行化 即一个一个事务的执行) | 解决脏读 | 解决不可重复读 | 解决幻读 |
常见问题
:问题现象:
接口响应时间超长,耗时几十秒才返回错误提示,后台日志中出现Lock wait timeout exceeded; try restarting transaction的错误。
问题场景:
1、多事务先后顺序操作同一 记录 造成死锁的;
2、多台服务器操作同一数据库;
3、瞬时出现高并发现象;
解决方法:
SELECT * FROM information_schema.INNODB_TRX;
kill trx_mysql_thread_id;
力度上划分: 行锁(锁一行数据) 、表锁(锁一个表的数据)
用法上划分: 乐观锁、悲观锁
模式上划分: 排他锁、意向锁、共享锁、自增锁
算方上划分: 间隙锁、记录锁、插入意向锁、临键锁
加锁力度: 表 > 行
加锁效率: 表 > 行
冲突概率: 表 > 行
并发性能: 表 < 行
意向锁 是由 数据引擎 自己维护的,用户无法手动操作。
作用: 提高加表锁的效率。
作用解释:
加表锁 成功的 前提是: 这个表中的任何一条数据都没有被 事务加锁,所以在加表锁的时候,程序就会一条一条的查看数据上是否有锁,这种 全表扫描 的方式是很费事的。
意向锁的生成
是你在加 行锁
的时候(或者说之前),系统就会自动给该表先加上一个意向锁(如果加的行锁是 共享锁,那么意向锁加的就是 意向共享锁),等给该表加 表锁
的时候,就无需 全表扫描,只需查看该表有无意向锁就可以了,有就不能加表锁,没有就能加。
当我们用范围条件
检索数据时,InnoDB 会给符合条件的 已有数据记录
的索引项加锁;对于在 检索 条件范围内
但 并不存在的记录
,叫做**“间隙(GAP)”**
InnoDB也会对这个“间隙”加锁,这种锁机制是所谓的间隙锁
数据表 tt:
id | name |
---|---|
1 | tom |
3 | cat |
4 | jerry |
5 | red |
事物A先进行如下操作: |
update tt set b='aaa' where a>1 and a<5
此时事物A还为 commit,然后事物B 进行如下操作:
insert into tt (id, name) values (2, 'cookie');
事物B的 插入数据的操作 是处于 阻塞状态
的,因为事物A 锁的是一个范围,只要在这个范围的,不论是间隙还是已有数据,其他 事物 都是不允许 动的。
锁其实锁住的是索引
。
- 有的表没有索引,没有索引的表 即使加了行锁,也会锁表。
其实任何表都是有索引的,表中数据的物理排放 就是 按照 聚集索引 排放的。
- 如果表中定义了
主键
,那么主键就是 聚集索引,进而主键决定数据的物理存放顺序。- 如果表中没有主键,却又一个
唯一
且不为空
约束的字段,那么该字段 就是 聚集索引。- 如果以上都没有,那就使用 表的隐藏字段 _rowid 为聚集索引。
mysql中的函数分为两大类: 单行函数 和 统计函数
单行函数: 如果函数的参数用到 表中的字段,那么涉及到该字段的 每一条记录 都会 各自调用一次该函数, 从返回结果来讲,因为该函数被 多次调用,所以返回的结果 有 多个。
统计函数: 如果函数的参数用到 表中的字段,那么涉及到该字段的 所有记录 最后一起 调用一次函数, 从返回结果来讲,因为该函数只被 调用一次,所以返回的结果 只有 一个。
select 函数名(‘aaa’);
select 函数名(id) from tableName;
进行数学算术运算的符号
select id * age 计算结果 , id ,age from tablename;
计算结果 | id | age |
---|---|---|
10 | 1 | 10 |
20 | 2 | 10 |
返回圆周率π,默认显示6位小数
二分之一π,可以表示为: PI()/2
返回e的 n 次乘方 后的值
参数的 自然对数
参数的 基数为10的对数
返回x的绝对值
返回 数 的符号,-1表示负数,0表示0,1表示正数
返回非负数的x的二次方根
3 的 4 次方
等同于 sql的 8%3 ==》 2
上取整
下取整
四舍五入,第二个参数指定保留小数点后几位,如果不写,则四舍五入为整数。
将 角度 转为 弧度
角度 360度 对应的 弧度为 2π
三角函数的
使用的参数
是弧度
将 弧度 转为 角度
重点说明:
返回值
作为筛选条件,应该使用having而不是where,这一条也适用于其他类的函数:# 下面这样用 where 是会报错的
select id * age 计算结果 , id ,age from tablename where 计算结果 > 10 order by 计算结果 ;
# 下面这样用 having 就可以了
select id * age 计算结果 , id ,age from tablename having 计算结果 > 10 order by 计算结果 ;
# 从上可以得出,如果==排序==的话, 则直接可以使用 计算的结果
字节长度;
一个字母一个字节,一个汉字三个字节, 这是由 字符集utf-8 决定的,
如果mysql使用的时 jdk字符集 那么一个汉字 两个 字节。
字符长度
字符拼接
待拼接的内容任意一个为NULL则返回值为NULL
可以添加连接符 的 字符拼接
将字符 abcdefg, 从第1个字符开始,往后数4个字符,用’12’将其替换掉。
replace(‘a+a+a+a’, ‘+’, ‘-’) ==> a-a-a-a
upper() 和 lower() 转换大小写
repeat(‘123’, 3) ==> 123123123
concat(‘a’, space(3), ‘b’) ==> a___b (a和b之间有三个空格)
字符切割:
从左开始截取字符串: left(被截取字段,截取长度)
从右开始截取字符串: left(被截取字段,截取长度)
若未指定要删除左右两边的内容,则默认删除空格。
从第几个字符开始切, 一共要切取几个字符。
注意不是从0开始的。
根据字符切割分段, 获取切割后的几段 由 第三个参数 控制。
前者返回当前UTC(世界标准时间)日期值
now() 返回当前系统的 日期 + 时间
curdate 返回当前系统的 日期
curtime 返回当前系统的 时间
year() 获取 指定时间 的 年份
select year(now());
select year('2021-1-1');
month() 获取 指定时间 的 月份
WEEK(d)、WEEKOFYEAD(d)
前者计算日期d是一年中的第几周,
后者计算某一天位于一年中的第几周
select str_to_date('12-30 2020', '%c-%d %Y');
mysql的智能自动格式转换:
select * from aaa where bithday = '1992-3-4';
select * from aaa where age = '10';
以上两种情况字符串都会智能转换,
因为 mysql意识到 bithday 是时间类型的字段,所以 会将 该字段后面的内容 按照 默认的时间转化格式:
年 月 日
的转换顺序 进行自动转换。
如果后面的字符时:4-3-1992 自动转换就会失败了。同理 字符串 也会智能转换为 数值类型。
select str_to_date('2020/12/30', '%c月%d日---%Y年');
格式符号 | 功能样式 |
---|---|
%Y | 四位的年份 |
%y | 两位的年份 |
%m | 月份(01、02…11、12) |
%c | 月份(1、2…11、12) |
%d | 日(01、02…11、12) |
%H | 小时(24小时制) |
%h | 小时(12小时制) |
%i | 分钟(00、01、02…59) |
%s | 秒钟(00、01、02…59) |
这四个函数作用相同,返回当前日期和时间值,格式为"YYYY_MM-DD HH:MM:SS"
有参数,则返回指定时间的时间戳: unix_timestamp(‘2020-01-02 10:00:00’)
-- 当日0点0分0秒时刻的时间戳
SELECT UNIX_TIMESTAMP(CAST(SYSDATE()AS DATE));
-- 昨日0点0分0秒时刻的时间戳
SELECT UNIX_TIMESTAMP(CAST(SYSDATE()AS DATE) - INTERVAL 1 DAY)
-- 明日0点0分0秒时刻的时间戳
SELECT UNIX_TIMESTAMP(CAST(SYSDATE()AS DATE) + INTERVAL 1 DAY)
SELECT FROM_UNIXTIME(1630301610,‘%Y-%m-%d %H:%i:%S’)
====> 2021-08-30 13:33:30
类似于JavaScript的三元表达式。
select if(10 < 5, '对了', '错了');
# 多条件
select if(1=2 or 1=1, 111, 222) ⇒ 111
select if(1=2 and 1=1, 111, 222) ⇒ 222
# where 之后
select * from user where age = if(sex=1, 40, 20);
# 可选 多条件
select * from user
where city = '临沂市'
and if(age=0, id<>0, age > 18)
and if(sex=0, id<>0, sex = 1);
对 null值 进行处理:
# 如果某条记录的 age字段为null,则该null按照 第二个参数 指定的 数字10处理。
select id * ifnull(age, 10) from tablename;
等值判断 (case之后有东西)
-- 案例:部门号为10的显示工资为原来的10被,为20的显示5被,其余的显示原工资
select salary 原工资, dep 部门,
case dep
when 10 then salary * 10
when 20 then salary * 5
else salary
end '起个别名: 新工资'
from tableName;
范围判断 (case之后没有东西)
-- 案例:部门号为10的显示工资为原来的10被,为20的显示5被,其余的显示原工资
select salary 原工资, dep 部门,
case
when dep < 10 then salary * 10
when dep < 20 then salary * 5
else salary
end '起个别名: 新工资'
from tableName;
version() ==> 5.7.35-0ubuntu0.18.04.1
connection_id() ==> 38
这个函数返回的是 这个连接 的 连接ID,
对于已经建立的连接的客户端,都有一个唯一的连接ID.
通过命令查看当前的链接列表:
mysql> show processlist;
+----+------+-----------------+-------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------------+-------+---------+------+----------+------------------+
| 33 | root | localhost:42990 | mysql | Sleep | 9083 | | NULL |
| 34 | root | localhost:44620 | mysql | Sleep | 3701 | | NULL |
| 35 | root | localhost:44622 | mysql | Sleep | 3701 | | NULL |
| 38 | root | localhost | wtt | Query | 0 | starting | show processlist |
+----+------+-----------------+-------+---------+------+----------+------------------+
如果是root帐号,你能看到所有用户的当前连接。如果是其它普通帐号,只能看到自己占用的连接。
show processlist;只列出前100条,如果想全列出请使用show full processlist;
如何停止某个链接: kill id
sum() 返回 涉及到的所有记录 的某个字段 的 和。
avg() 返回 涉及到的所有记录 的某个字段 的 平均值。
max() 返回 涉及到的所有记录 的某个字段 的 最大值。
min() 返回 涉及到的所有记录 的某个字段 的 最小值。
count() 返回 涉及到的所有记录 的某个字段 的 非空值 的总个数。
重点说明: count 在使用的过程中,不能出现 跳过几个记录 的动作
:select count(*) from aaa limit 5,2; 像这种SQL语句:要求跳过两条,还要求计算所有的记录数, 明明都跳过了还得计算在内,显然是矛盾
的,所以这个属于逻辑错误
,而非语法错误
。
说明: count仅仅统计 字段值为 非空值 的记录,所以 统计表中的记录 条数的时候我们 使用 count(*)
,意思是一条记录中 只要有一个字段不是null值就可以被统计上
查看更多
用户变量 在 某些场景下 能极大的提升 查询 效率,
变量 的 定义 一般写在 from 语句之后,
变量 的 使用 一般写在 select 语句之后。
在 sql中 :=
既是 声明变量 符 又是 赋值符。
select
@i := @i+1 -- 这里是 使用 变量@i 区域
from
(select @i := 0) wtt, -- -- 这里是 定义 初始化 变量 @i 区域
users
where
@i < 10 and users.name <> ''
-- 输出:
@i := @i+1|
----------+
1.0|
2.0|
3.0|
4.0|
5.0|
6.0|
7.0|
8.0|
9.0|
10.0|
字段数值
select
@i := id,
name
from
(select @i := 0) wtt,
users
where
@i < 10 and users.name <> ''
自定义变量 的 循环次数 是由于 where 筛选出 记录数 决定的。
函数数值
select
@aaa := CONCAT(@aaa , "-",name) ,
name
from
(select @aaa := 'ha') wtt,
users
where
users.name <> '';
查询数值
经典用法
的 案例展示,select
@r,
-- 下面 是将 查询到的 fid的结果 赋值给 变量 @r, 然后进行 下一轮的 循环
(select @r := fid from user_friends where uid = @r) as tmp_id
from
(select @r := 3) wtt,
user_friends
where
fid > 0 limit 5
-- 输出:
@r|tmp_id|
--+------+
3| 2 |
2| NULL |
2| NULL |
2| NULL |
2| NULL |
重点说明:
从上面的输出可以看出, 如果 (select @r := fid from user_friends where uid = @r) 匹配不到查询结果时,
tmp_id 的值 被赋值为 NULL, 变量@r 不进行赋值操作, 即 @r的值 原先是啥 经过 赋值 之后 还会是啥。
问题说明:
我们发现 当 (select @r := fid from user_friends where uid = @r) 查询不到结果时,没能及时的将 循环操作停下来,
如果不是靠着 limit 停下来的,会遍历 user_friends表中 所有符合 where 条件的记录。
显然 这是 没必要的, 例如上面,循环了 4次,浪费了 性能,固然是需要优化的,
具体 优化代码如下:
select
@wtt_compare := @r,
-- 下面 是将 查询到的 fid的结果 赋值给 变量 @r, 然后进行 下一轮的 循环
(select @r := fid from user_friends where uid = @wtt_compare) as tmp_id,
@wtt_compare := if(@wtt_compare = @r, 0, 1) is_continue
from
(select @r := 3, @wtt_compare := 1) wtt,
user_friends
where
@wtt_compare <> 0 and fid > 0
-- 输出:
@r|tmp_id| is_continue
--+------+------------+
3| 2 | 1
2| NULL | 0
通过 比对 @r变量 赋值前后的 变化 进而 确定 赋值的select语句是否查询到结果,
进而 控制 循环的 次数, 这样以来 仅仅 多进行一次循环,这个是完全可以接受的。
-- 重点说明 ··
在 (select @r := fid from user_friends where uid = @wtt_compare) 中,
uid 的赋值 用的是 变量 @wtt_compare 而不是 @r,
fid 的给值 给的是 变量 @r 而不是 @wtt_compare。
这一点一定要注意,否则 会报错。
-- 扩展说明 ··
@wtt_compare := @r as abc
这个 起别名的操作 实际上是给 左值(等号左侧) 变量起的别名,即 @wtt_compare 变量的 别名。
所以 (select @r := fid from user_friends where uid = @wtt_compare) 可以改为:
(select @r := fid from user_friends where uid = abc)
典例二:
CREATE TABLE `tree` (
`id` bigint(11) NOT NULL,
`pid` bigint(11) NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `tree` VALUES (1, 0, '中国');
INSERT INTO `tree` VALUES (2, 1, '四川省');
INSERT INTO `tree` VALUES (3, 2, '成都市');
INSERT INTO `tree` VALUES (4, 3, '武侯区');
INSERT INTO `tree` VALUES (5, 4, '红牌楼');
INSERT INTO `tree` VALUES (6, 1, '广东省');
INSERT INTO `tree` VALUES (7, 1, '浙江省');
INSERT INTO `tree` VALUES (8, 6, '广州市');
--
SELECT *
FROM (
SELECT
t1.*,
IF(FIND_IN_SET(pid, @pids) > 0, @pids := CONCAT(@pids, ',', id), '0') AS ischild
FROM
(SELECT * FROM tree AS t ORDER BY t.id ASC) t1,
(SELECT @pids := 1) t2
) t3
WHERE ischild != '0';
-- 字符包含 函数作用说明:
-- 以逗号 为分割, 判断 第一个参数 在 第二个参数 中的位置:
SELECT FIND_IN_SET('y','ax,y,z'); -- 2
到此知道了 自定义变量 ,就可以更好的 说明 select 语句的执行顺序的问题了:
select
@wtt_compare := @r,
-- 下面 是将 查询到的 fid的结果 赋值给 变量 @r, 然后进行 下一轮的 循环
(select @r := fid from user_friends where uid = @r) as tmp_id,
@wtt_compare := if(@wtt_compare = @r, 0, 1) is_continue
from
(select @r := 3, @wtt_compare := 1) wtt,
user_friends
where
@wtt_compare <> 0 and fid > 0
-- 将 上面的sql语句,一次循环 按照 执行顺序 向下 铺开:
(select @r := 3, @wtt_compare := 1) wtt,
user_friends
@wtt_compare <> 0 and fid > 0
@wtt_compare := @r,
(select @r := fid from user_friends where uid = @r) as tmp_id,
@wtt_compare := if(@wtt_compare = @r, 0, 1) is_continue
当数据库比较复杂的时候,就需要设计了
糟糕的设计:
良好的设计:
三大范式: 可以规避信息重复、更新异常、插入异常、删除异常等问题
第一范式:保证每一列不可再分
例如:
地址: 山东省临沂市兰山区
上面 地址 的 字段值 是可以再拆分的,所以不满足 第一范式,这样写就满足了:
省份: 山东省,
市区: 临沂市,
县区: 兰山区,
第二范式: 在满足第一范式的基础上,每张表只描述一件事情,所有的属性必须完全依赖主键。
第三范式: 在满足第二范式的基础上,确保数据库表中的每一列数据和主键直接相关,而不是间接相关。
小写字母 + 下划线
(禁止出现大写字母,禁止用-连接)idx_表名_字段名_字段名
; 唯一索引uk_表名_字段名
小的数据类型占用更少的磁盘、内存和CPU缓存,所以操作效率也会更快。
举例:
处理日期的时候,存储用户日期,应该选择date类型而不是datetime,datetimek可以精确到时分秒。
在需要存储年龄、性别、状态 之类的应用场景中,应该选择 tinyint 来存储,而不是int。
能使用int就不要使用varchar、char,能用varchar(16)就不要使用varchar(255);
特别说明:
int(5)和int(10)的区别是什么?
int(x) 的x不管是什么值,存储数字的取值范围还是int本身数据类型的取值范围,x只是数据的 显示长度 而已。
存储字符时,应优先考虑char数据类型,因为char是定长的,而varchar 是变长的,mysql处理char比varchar要快一点。
char类型的最大宽度为255 字节,varchar 最大宽度为 65535 个字节。
特别说明:
char(10)和varchare(10)的区别是什么?
定义一个char[10]和varchar[10],如果存进去的是‘abcd’,那么char所占的长度依然为10,除了字符‘abcd’外,后面跟六个空格,而varchar就立马把长度变为4了。
取数据的时候,char类型的要用trim()去掉多余的空格,而varchar是不需要的。
因为固定长度的数据便于程序的存储与查找,所以char的存取数度还是要比varchar要快得多。char为此付出的代价是空间,可谓是以空间换取时间效率。
用decimal来存储金额字段,不要用float和double,会出现数据精度丢失。
若多张表之间的关系复杂,建议采用第三张映射表来关联维护表之间的关系,以降低表之间的直接耦合度。
像“创建时间”、“修改时间”、“备注”、“操作用户IP”和一些用于其他需求(如统计)的字段等,在每张表中必须都要有,不是 说只有系统中用到的数据才会存到数据库中,一些冗余字段是为了便于日后维护、分析、拓展而添加的,这点是非常重要的。
create user 'tom'@'%' identified by '666666';
说明:
drop user 'tom'@'%';
SET PASSWORD FOR 'tom'@'%' = PASSWORD("123456");
# 指定 权限 的 指定 表
grant select, insert, update on database_name.table_name to 'tom'@'%';
# 指定 权限的 所有表
grant select, insert, update on * to 'tom'@'%';
# 全部操作权限,以及全部的数据库
grant all on *.* to 'tom'@'%';
刷新权限 ,如果还是没有生效,可以重启mysql服务
flush privileges
说明:
ALL
wtt.*
,如果要访问所有数据库则使用*.*
mysql -uroot -p123456
SELECT User, Host FROM mysql.user;