MySQL学习历程学习笔记

# 数据库管理软件的由来:
# 数据想要永久保存,都是保存在文件中的,毋庸置疑,一个文件只能存放在一台机器上,
# 如果我们忽略掉直接基于文件来存取数据带来的效率问题,假设程序的所有组件都是运行在一台机器上,
# 则上述假设存在以下问题:
# 1. 整个程序的所有组件不可能运行在一台机器上
#    因为这台机器一旦宕机则意味着整个软件的崩溃;
#    一台机器的性能总归是有限的
#    对一台机器的性能垂直进行扩展是有极限的,
#      于是我们只能通过水平扩展来增强我们的系统性能,
#       也就是需要我们将整个程序的各个组件分布到多台机器上

# 2. 数据安全问题
#    虽然程序的各个组件分布到了多台机器上,但是他们仍是一个整体,
#    所以组件之间的数据还是要共享的。
#    每台机器上只能操作这台机器上的文件,实现不了数据共享,
#    于是我们想到了将数据与应用程序分离:
#    把文件存放于一台机器,然后将多台机器通过网络去访问这台机器上的文件(用socket实现),
#    既然要共享,那么就会带来竞争,竞争就会带来数据不安全,所以需要加锁处理。

# 3. 并发
#    基于问题2,
#    我们必须写一个socket服务端来管理这台机器(数据库服务器)上的文件,
#    后写一个socket客户端,完成如下功能:

#    1.远程连接(支持并发)
#    2.打开文件
#    3.读写(加锁)
#    4.关闭文件

# 如果我们在编写任何程序之前,都需要事先写好基于网络操作一台主机上文件的程序(socket服务端与客户端程序),那么工作是非常繁琐的
# 于是有人将此类程序写成一个专门的处理软件,这就是mysql等数据库管理软件的由来,但mysql解决的不仅仅是数据共享的问题,还有查询效率,安全性等一系列问题,
# 总之,把程序员从数据管理中解脱出来,专注于自己的程序逻辑的编写。

##############################################################################################################

# MySQL:是一个DBMS(数据库管理系统)
# 包括:服务器端(socket服务端、进行文件操作、解析SQL语句)
#       客户端(socket客户端、发送指令、解析SQL语句)

# DBMS:如MySQL、Oracle、SQLite、Access、MS SQL Server
# mysql主要用于大型门户,例如搜狗、新浪等,它主要的优势就是开放源代码。
       因为开放源代码这个数据库是免费的,由瑞典MySQL AB 公司开发,他现在是甲骨文公司的产品。
       MySQL 最流行的关系型数据库管理系统,在 WEB 应用方面MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。
# oracle主要用于银行、铁路、飞机场等。该数据库功能强大,软件费用高。也是甲骨文公司的产品。
# sql server是微软公司的产品,主要应用于大中型企业,如联想、方正等。

# 数据库分为:关系型数据库(有表结构)和非关系型数据库(key、value的形式,没有表结构)
# MySQL属于关系型数据库
# mysql就是一个基于socket编写的C/S架构的软件
# 数据库:就想象成一个文件夹,是保存、组织数据表的一个容器。
# 数据表:含有字段的文件才是数据表,就想象成一个Excel表格,是某种有着特定类型的结构化清单。
# 字段:表记录对应的标题
# 表记录:事物的一系列典型特征,一条记录是进行操作的最小的单位,不可再分割。
#         从技术上说,是行
# 数据:描述事物特征的符号

###################################################################################################

# 安装MySQL 5.7.25-winx64:
# Step 1:下载zip压缩包
# Step 2:解压
# PS:如果没有data目录,手动创建一个,再继续以下操作
# Step 3:初始化服务器端,mysqld.exe --initialize-insecure --user=mysql
# Step 4: 启动服务器端,mysqld.exe
# Step 5: 启动客户端,mysql.exe -u root -p
# 默认没有密码,所以要求输入密码的时候,直接回车即可

# 添加环境变量

# 制作服务
# Step 1:用管理员权限打开CMD,执行mysqld.exe --install 安装服务
# Step 2:启动服务 net start MySQL
# 附加:mysqld.exe --remove 移除服务(需要关闭MySQL)
#       net stop MySQL 关闭MySQL

# ==========================================
# Windows下安装步骤:
# 1. 下载zip压缩包
# 2. 解压
# 3. 将bin下的mysqld.exe添加到环境变量
# 4. 如果mysql根目录没有data目录,手动创建一个,再继续以下的操作
# 5. 初始化服务端 mysqld.exe --initialize-insecure --user=mysql
# 6. 制作服务,用管理员权限打开CMD,mysqld.exe --install MySQL(服务名)
#    移除服务(需要关闭服务)        mysqld.exe --remove MySQL(服务名)
# 7. 启动服务,net start MySQL
#    关闭服务,net stop MySQL
# 8. 启动客户端,mysql.exe -u root -p密码 -h IP(连接别的数据库) -P 3306(指定端口,默认3306) -D (--database=name)
#                         -e (--execute="") 指定语句
#                         例如:-e "select * from userinfo" hardy2_db
#                              OR
#                              -e "select * from hardy2_db.userinfo"
# ==========================================

# ====================================================================
例外: 特外注意!!!!!!!!!!!!!
shell> mysql -ptest
shell> mysql -p test
第一个命令让mysql使用密码test,但没有指定默认数据库。第二个命令让mysql提示输入 密码并使用test作为默认数据库。

2. 关于指定数据库
可以在连接的时候就指定:mysql -uroot -p123 hardy2_db

# ====================================================================

# ===================================================
# 禁用mysql自动连接
# 如果mysql客户程序发送查询时断开与服务器的连接,它立即并自动尝试重新连接服务器并再次发送查询。
# 然而,即使mysql重新连接成功,你的第1个连接也已经结束,并且以前的会话对象和设定值被丢失:包括临时表、自动提交模式,以及用户和会话变量。该行为很危险.
# 如果有必要在连接断开时终止mysql并提示错误,你可以用--skip-reconnect选项启动mysql客户程序。
#
# ===================================================

# Linux (CentOS 7)安装MySQL:
# Step 1:yum install -y mariadb-server mariadb
# Step 2: systemctl start mariadb  # 启动服务端
# Setp 3: systemctl enable mariadb  # 开机启动
# step 4: mysql -u root -p  # 连接数据库 / -h IP 连接别的数据库 / -P 指定端口 / -D (--database=name) 指定数据库

# MySQL端口:3306

# 设置登录密码:
# CMD / Linux下:
       mysqladmin -uroot -p password "123"  (如果原密码没有,可以写成)
       ==
       mysqladmin -uroot password "123"

       mysqladmin -uroot -p 123 password "456"


# 破解登录密码:
# Windows下:
# 方式一:
# Step1: 关闭MySQL服务
# Step2: 管理员终端下mysqld --skip-grant-tables
# Step3: 终端下mysql
# Step4: update mysql.user set authentication_string=password('') where user='root';
# Step5: flush privileges;
# Step6: exit
# Step7: 关闭MySQL服务
# Step8: 启动MySQL服务

# 方式二:
# Step1: 关闭MySQL服务
# Step2: 在解压目录下,新建配置文件并命名为my.ini
# Step3: 写入以下内容:
         [mysqld]
         skip-grant-tables
# Step4: 启动MySQL服务
# Step5: 终端下mysql
# Step6: update mysql.usr set authentication_string=password('') where user='root' and host='localhost';
# Step7: flush privileges;
# Step8: exit
# Step9: 将my.ini中的skip-grant-tables 注释掉
# Step10: 关闭MySQL服务
# Step11: 启动MySQL服务

##################################################################################################

# 统一字符编码
# 强调:配置文件中的注释可以有中文,但是配置项中不能出现中文
# 在mysql的解压目录下,新建my.ini,然后配置
# 1. 在执行mysqld命令时,下列配置会生效,即mysql服务启动时生效
[mysqld]
#skip-grant-tables
port=3306
character-set-server=utf8
collation-server=utf8_general_ci
default-storage-engine=innodb
innodb_file_per_table=1

#解压的目录
basedir=C:\Users\pro3\Documents\mysql-5.7.25-winx64
#data目录
datadir=C:\Users\pro3\Documents\mysql-5.7.25-winx64\data
#在mysqld --initialize时,就会将初始数据存入此处指定的目录,在初始化之后,启动mysql时,就会去这个目录里找数据

# 2. 针对客户端命令的全局配置,当mysql客户端命令执行时,下列配置生效
[client]
port=3306
default-character-set=utf8
user=root
password=123

# 3. 只针对mysql这个客户端的配置,2中的是全局配置
#     而此处的则是只针对mysql这个命令的局部配置
[mysql]
;port=3306
default-character-set=utf8
user=woai
password=456

##########################################################################################

# 安装Navicat for MySQL后,连接后出现
# 1251-Client does not support authentication protocol requested...
# 解决方法:
# 执行 alter user 'root'@'localhost' identified with mysql_native_password by '123'; 即可

# MySQL注释:
# 注释:行内注释,-- 注释
#       整行注释,# 注释
#       多行注释,/* 注释 */

#######################################################################################

# sql(结构化查询语言),由IBM开发,分为三种类型:
# 1. DDL:数据定义语言,定义数据库、表、视图、存储过程、索引等
# 2. DML:数据操纵语言,插入、删除、更新、查询数据
# 3. DCL:数据控制语言,例如控制用户的访问权限,Grant Revoke

# select user();  查看当前登录的用户

# \q / exit / quit : 退出mysql   对于exit / quit / \q加不加;都可以

# \c 终止命令(是在这条命令还未运行之前)

# \G 按行显示    ---> 使用\xx 后,最好不要加;

# \s / status 当前mysql状态信息(版本、字符集等)\s最好不要加;    status加不加;都可以
# show variables like '%char%'  查看当前MySQL字符集

#############################################################################################

# =======================================================================================
# select user();   # 返回服务器版本信息
# select database();  # 返回当前数据库名(或空)
# select version();  # 返回当前用户名
# show status;  # 返回服务器状态
# show variables;  # 返回服务器配置变量
# =======================================================================================

# ===========================================================
作为研发,有一条铁律需要记住,那就是

!!!!永远不要相信用户的数据,哪怕他一再承诺是安全的

防止 SQL 注入要诀:
1. 永远不要信任用户的输入

2. 对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和双 "-" 进行转换等

3. 永远不要使用动态拼装 SQL ,可以使用 SQL 预处理语句

4. 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接

5. 不要把机密信息直接存放,加密或者 hash 掉密码和敏感的信息

6. 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装

7. SQL 注入的检测方法一般采取辅助软件或网站平台来检测

# ======================================================

# 授权:对文件夹,对文件,对文件某一字段的权限
# 常用权限有:select,update,alter,delete
# all [privileges]可以代表除了grant之外的所有权限

# 授权表
# user          #该表放行的权限,针对:所有数据,所有库下所有表,以及表下的所有字段
# db            #该表放行的权限,针对:某一数据库,该数据库下的所有表,以及表下的所有字段
# tables_priv   #该表放行的权限。针对:某一张表,以及该表下的所有字段
# columns_priv  #该表放行的权限,针对:某一个字段

# 创建用户:
# create user 'hardy'@'192.168.240.2' identified by '123'; # 只允许来自192.168.240.2的hardy用户连接
# create user 'hardy'@'192.168.%' identified by '123';  # 允许来自192.168.0.0网段的所有名为hardy用户连接
# create user 'hardy'@'%' identified by '123';  # 允许所有hardy用户连接

# 删除用户:
# drop user 'hardy'@'192.168.240.2';

# 修改用户:
# rename user 'hardy'@'192.168.240.2' to 'hardy9sap'@'%';

# 更改用户密码:
# alter user 'hardy'@'%' identified by '456';

# 查看授权信息:
# show grants for 'hardy'@'%';

# 给某个用户授权:

# 针对所有库的授权: *.*
grant select on *.* to 'egon1'@'localhost' identified by '123'; #只在user表中可以查到egon1用户的select权限被设置为Y
(授权 + 创建用户)

# 针对某一数据库:db1.*
grant select on db1.* to 'egon2'@'%' identified by '123'; #只在db表中可以查到egon2用户的select权限被设置为Y

# 针对某一个表:db1.t1
grant select on db1.t1 to 'egon3'@'%' identified by '123';  #只在tables_priv表中可以查到egon3用户的select权限

# 针对某一个字段:
grant select (id,name),update (age) on db1.t3 to 'egon4'@'localhost' identified by '123';

# 更改密码或者授权完成后,记得...:
# flush privileges;  (这样就无需重启服务)

# 移除权限:
# revoke all privileges on hardy_db.tb1 from 'hardy9sap'@'%';

##############################################################################

#################################数据库操作#########################################

# 前言:
# 系统数据库:
# 1. information_schema:
     虚拟库,不占用磁盘空间,用于存储数据库启动后的一些参数
# 2. performance_schema:
#    收集数据库服务器性能参数,记录处理查询请求时发生的各种事件、锁等现象
# 3. mysql:
#    授权库,存储系统用户的权限信息
# 4. test:
#    MySQL数据库管理系统自动创建的用于测试的数据库

# 数据库命名规则:
1. 可以由字母、数字、下划线、$ (在设置变量的时候只有@可以用,而且要放在开头)
2. 不区分大小写
3. 唯一性
4. 不能使用关键字如 create select
5. 不能单独使用数字
6. 最长128位

# 创建数据库:
# ==================
`create database `根据给定的名称创建数据库,你需要拥有数据库的CREATE权限来使用这个语句。
`create schema`是`create database`的一个同义词。(也就是,都可以使用)
# =================
# CREATE DATABASE dbname [IF NOT EXISTS] [default character set] [collate];
# create database if not exists hardy_db [default] character set utf8 collate utf8_general_ci;
# create database hardy_db charset gbk collate gbk_chinese_ci;

# 删除数据库:
# drop database db1;

# 更改数据库的字符集:
# alter database db1 default charset=utf8 collate=utf8_general_ci;
#                    default 可以不写, charset / character set=utf8

# v8中, 使用utf8mb4字符集, 原因是早期的mysql只支持utf8的三个字节
# charset(字段), 可以查看字段的字符集

# 查看数据库创建信息:
# show create database db1;

# 查询所有的数据库:
# show databases;

# 访问某个数据库:
# use DB_NAME;

# 查询当前使用的数据库
# select database();

PS: 命令不会用,help create / help create database

###################################################################################################

# 前言:
# 什么是存储引擎?
# 表的类型,存储和操作此表的类型,类型的不同,对应着mysql不同的存取机制

# 存储引擎种类:
# 1. InnoDB,默认的,支持事务,原子性操作,支持回滚
# 2. MyISAM,不支持事务,全局索引,存储速度快
# 3. BlackHole,往表内插入任何数据,都相当于丢入黑洞,表内永远不存记录
# 5. Memory,数据存放在内存中,在重启mysql或者重启机器后,表内数据清空
# 5. mrg_myisam
# 6. performance_schema
# 7. federated   不支持

# 查看所有的存储引擎
# show engines;

# 查看当前使用的存储引擎
# show variables like 'storage_engine%';

# 指定存储引擎
# 1. 建表时指定
#    create table tb1(id int, name char) engine=innodb;
# 2. 配置文件
#    [mysqld]
#     default-storage-engine=innodb
#     innodb_file_per_table=1

# PS: .ibd innodb表数据  .frm表结构  blackhole / memory只有表结构  (也说明了一个表由多个文件组成)

####################################################################################################

# 创建表:
# 语法:
    create [temporary] table 表名(
    字段名1 类型[(宽度) 约束条件],
    字段名2 类型[(宽度) 约束条件],
    字段名3 类型[(宽度) 约束条件]
    );

#注意:
1. 在同一张表中,字段名是不能相同
2. 宽度和约束条件可选
3. 字段名和类型是必须的
4. 注意注意注意:表中的最后一个字段不要加逗号

# 创建表:
# create table [if not exists] t1(id int, name char) engine=innodb [default] charset=utf8mb4 collate=utf8mb4_0900_ai_ci;
#                                                      default可以不写
# create table t2(id int not null auto_increment primary key) engine=innodb character set=utf8 collate=utf8_general_ci;
# 表的字段默认可以为空,也就是为空值Null(default null)

# not null:
    表示该字段不能为空值Null;
# auto_increment:
    表示自增,用这个的时候一般配合 primary key 使用,
# primary key:
    它表示主键,它的作用:第一,约束该字段不能重复并且不能为空;
                       第二,增加查找速度
# 一个表只能有一个自增并且只能有一个主键


# 删除表:
# drop table [if exists] t1;


# 重命名表:
# alter table t11 rename as/to t12;   as/to不是必须的
# rename table t11 to t12;  to不可省略


# 更改存储引擎
# alter table tb3 engine=innodb;


# 更改表的字符集
# alter table tb3 [default] charset=gbk collate=gbk_chinese_ci;


# 查看表的字段信息:
# describe t11;
# desc t11;
# show columns from t11; 三者等价
# 另外,  show columns from t11; 这种形式, 是可以接like来过滤的.

# 查看表的详细索引信息
# show index from t11 \G

# 查看指定数据库下所有表的信息
# show table status from hardy2_db \G
# show table status from hardy2_db like '%tb%' \G

# 查看表的创建信息:
# show create table t11 \G     \G 竖向显示


# 查询所有的表:
# show tables;


# 从一个表复制到另一个表(复制表结构 + 表记录, 但表结构中的索引会丢失)
# create table student_copy as select * from student;
# == create table student_copy select * from student;

# 只复制表结构
create table student_copy select * from student where 1=2;  # 条件为假,查不到任何记录,但是有结构

# 只复制表结构2(只会完整复制原表的建表语句)
create table new_stu like student;


# 更改表字段(名字、类型、约束性条件):[column]可选 [完整性约束条件…]可选,但必须要指定类型
# alter table tb1 change [column] name NAME char(5) first;   first, 将字段插入到最前面
# alter table tb1 change NAME name char after ident;  after, 将字段插入到指定字段的后面
# 注意: 虽然将小写的name更改为了大写的NAME, 而且在表中也显示了大写的NAME, 但是仍然不区分大小写.

# (相当于以下两步 == 以上一步)
# 重命名表字段名字:
# alter table tb1 rename name to NAME;

# 修改表字段的定义(类型、约束性条件):
# alter table tb1 modify [column] name char(5) first;
# alter table tb1 modify name char after ident;


# 删除字段:[column]可选
# alter table userinfo drop [column] tel;
# 注意: 如果只有一个字段,不能使用这个语法,只能删除整张表.
# 因为有字段的才称之为表,连一个字段都没了,那么表也就没有存在的意义了。


# 插入字段:[column]可选 [完整性约束条件…]可选,但必须要指定类型
# alter table userinfo add [column] tel char not null;
# alter table userinf add gender char, add name char(5);
# alter table userinfo add score int default 100 FIRST;  first, 将字段插入到最前面
# alter table userinfo add address char(10) AFTER name;  after, 将字段插入到指定字段的后面


# =================================================================================

# 如果主键使用单列,那么这列必须不能为空而且不能重复;
# 如果主键为多列,那么列的组合必须唯一;
# create table t1(id int auto_increment, index(id));

# alter table t1 add primary key(id, name);
# alter table t1 modify id int primary key;
# alter table t1 drop primary key;

#################################################################################################

# 语法:

# 插入记录:[into]可以省略; values or value
# insert [into] t1(id, name) values/value(1, 'alex');  # 一次插入一行记录
# insert t1(id, name) values(2, 'egon'), (3, 'peiqi');  # 一次插入多行记录

# 如果id是auto_increment, 可以不用指定值

# insert t1(id, name) values(2, '');  # 插入''
# insert t2() values();  # t2() ==> t2(id, name), values(), 为每个字段插入Null(如果允许的话)
# insert t2 values(1, 'abc'), (2, 'abc');  # 如果为每个字段插入值, 可以直接写t2, t2 ==> t2() ==> t2(id, name)

# insert t11 select * from t12;
# insert t11(name, age) select name, age from t12;
# insert student select * from student where student.id > 1; 这种插入可以接条件

# insert t11 set a=b'11';  # 通过set插入值
# insert t11 set b=b'1000', c=b'111';

# =====================================================================
# 注意: 如果在insert的过程中, 未正确插入(是values部分, 也就是插入的值不符合定义),
# 并且表设置了auto_increment, 则auto_increment的值, 已经发生了变化.
# 例如: 当前的auto_increment值为3, name字段定义为char(1),
#       现在执行错误语法的insert语句, insert into tb5(name) values('a'), ('bb'), ('c');
#       (当然, 如果是values('bb'), ('a'), ('c'), 则auto_increment不会发生变化.)
#       执行完后, 未正确插入, 此时下一个插入的值的auto_increment的值为6
# =======================================

# 清空表中的记录:
# delete from t1;  # 不接where子句, 默认删除全部记录
# delete from t1 where id < 1;
# 这种方式清空后,再插入记录,如果有auto_increment的话,就会从删除前的位置继续增长
# 也就是, SQL DELETE 语句会保持表中的结构、属性、索引、自增计数不变
# 最佳实战:
# 使用 SQL DELETE 语句一定要附加 WHERE 子句,如果删除所有记录,也要用 WHERE 1=1;

# 注意:在删除记录时要格外小心!因为删除的数据没法恢复,没有后退机制!!!!!!!!!!!!!!!!!!!!!!!

# truncate table t2;  # 这种方式清空后,从头再来
#    truncate t2;    # (如果表中有外键等约束,是无法清空的)
# 如果要删除表的所有行的话,推荐使用truncate,效率高,因为不用保存表的状态

# 更新表中的记录:
# 单张表
# =============
UPDATE [LOW_PRIORITY] [IGNORE] table_reference
    SET assignment_list
    [WHERE where_condition]
    [ORDER BY ...]
    [LIMIT row_count]
# =============

# =============
UPDATE [LOW_PRIORITY] [IGNORE] table_references
    SET assignment_list
    [WHERE where_condition]
# =============
# update t1 set name='egon';  # 修改表中的所有记录的name字段
# update t1 set name='alex', tel='123456';  # 可以一次修改多个字段
# update t1 set name='覃东宇' where id=1997 and sex='M';  # 通过where指定条件, 修改特定的记录
# update t1 set name='eva-j' where id > 1 order by -id limit 2; 还可以指定order by 以及 limit
# 如果想删除列的某个值,可以将其设置为NULL(如果约束条件允许的话)

# 查询表中的记录:
# select ... from ...
# select ... into ...
# select * from t1;
# select id, name, age from t2;
# select id, name, age, gender as g from t2 where id > 1;  # as指定别名
# select id, name, age, gender g from t2 where id > 1;  # 也可省略as
# 实际工作中不要用 * ,效率不高。要哪些就写哪些。

####################################################################################################
# 数据类型:

# =====================================================================================================
    在 MySQL/MariaDB 中,有三种主要的类型:

        Text (文本)
        Number (数字)
        Date/Time (日期/时间) 类型

        Text 类型
            数据类型	                                            描述
            CHAR(size)	                                        保存固定长度的字符串(可包含字母、数字以及特殊字符)
                                                                在括号中指定字符串的长度
                                                                最多 255 个字符
            VARCHAR(size)	                                    保存可变长度的字符串(可包含字母、数字以及特殊字符)
                                                                在括号中指定字符串的最大长度
                                                                最多 255 个字符
                                                                注意:如果值的长度大于 255,会被转换为 TEXT 类型
            TINYTEXT	                                        存放最大长度为 255 个字符的字符串
            TEXT	                                            存放最大长度为 65,535 个字符的字符串
            BLOB	                                            用于 BLOBs(Binary Large OBjects)
                                                                存放最多 65,535 字节的数据。
            MEDIUMTEXT	                                        存放最大长度为 16,777,215 个字符的字符串
            MEDIUMBLOB	                                        用于 BLOBs(Binary Large OBjects)
                                                                存放最多 16,777,215 字节的数据
            LONGTEXT	                                        存放最大长度为 4,294,967,295 个字符的字符串
            LONGBLOB	                                        用于 BLOBs (Binary Large OBjects)
                                                                存放最多 4,294,967,295 字节的数据。
            ENUM(x,y,z,...)	                                    允许输入可能值的列表
                                                                可以在 ENUM 列表中列出最大 65535 个值
                                                                如果列表中不存在插入的值,则插入空值
                                                                注意:存储的值是按照输入的顺序排序的
                                                                可以按照此格式输入可能的值 ENUM('X','Y','Z')
            SETENUM 类似,但 SET 最多只能包含 64 个列表项且 SET 可存储一个以上的选择


            Number 类型
                数据类型	                                                        描述
                TINYINT(size)	                                                带符号 -128127 ,无符号 0255
                SMALLINT(size)	                                                带符号范围 -3276832767
                                                                                无符号 065535
                                                                                size 默认为 6
                MEDIUMINT(size)	                                                带符号范围 -83886088388607
                                                                                无符号的范围是 016777215
                                                                                size 默认为 9
                INT(size)	                                                    带符号范围 -21474836482147483647
                                                                                无符号的范围是 04294967295
                                                                                size 默认为 11
                BIGINT(size)	                                                带符号的范围是 -92233720368547758089223372036854775807
                                                                                无符号的范围是018446744073709551615
                                                                                size 默认为 20
                FLOAT(size,d)	                                                带有浮动小数点的小数字
                                                                                在 size 参数中规定显示最大位数
                                                                                在 d 参数中规定小数最大位数
                DOUBLE(size,d)	                                                带有浮动小数点的大数字
                                                                                在 size 参数中规显示定最大位数
                                                                                在 d 参数中规定小数的最大位数
                DECIMAL(size,d)	                                                作为字符串存储的 DOUBLE 类型,允许固定的小数点
                                                                                在 size 参数中规定显示最大位数
                                                                                在 d 参数中规定小数点右侧的最大位数
                上表中的 size 并不是指存储在数据库中的具体的长度,而是显示的位数。如 int(4) 并不是只能存储4个长度的数字

                int(size) 所占多少存储空间并无任何关系。
                int(3)int(4)int(8) 在磁盘上都是占用 4 btyes 的存储空间。
                就是在显示给用户的方式有点不同外,int(M)int 数据类型是相同的

                例如:int 的 值为 10 (指定zerofill)

                int9)显示结果为000000010
                int3)显示结果为010
                就是显示的长度不一样而已 都是占用四个字节的空间

            Date 类型
                数据类型	                                                                描述
                DATE()	                                                                日期。格式:YYYY-MM-DD
                                                                                        支持的范围是从 '1000-01-01''9999-12-31'
                DATETIME()	                                                            *日期和时间的组合
                                                                                        格式:YYYY-MM-DD HH:MM:SS
                                                                                        支持的范围是从 '1000-01-01 00:00:00''9999-12-31 23:59:59'
                TIMESTAMP()	                                                            *时间戳
                                                                                        TIMESTAMP 值使用 Unix 纪元('1970-01-01 00:00:00' UTC) 至今的秒数来存储
                                                                                        格式:YYYY-MM-DD HH:MM:SS
                                                                                        支持的范围是从 '1970-01-01 00:00:01' UTC 到 '2038-01-09 03:14:07' UTC
                TIME()	                                                                时间
                                                                                        格式:HH:MM:SS
                                                                                        支持的范围是从 '-838:59:59''838:59:59'
                YEAR()	                                                                2 位或 4 位格式的年
                                                                                        4 位格式所允许的值:19012155
                                                                                        2 位格式所允许的值:7069 表示从 19702069
                * 虽然 DATETIMETIMESTAMP 返回相同的格式,它们的工作方式是不同的

                在 INSERTUPDATE 查询中,TIMESTAMP 自动把自身设置为当前的日期和时间。
                TIMESTAMP 也接受不同的格式,比如 YYYYMMDDHHMMSS、YYMMDDHHMMSS、YYYYMMDD 或 YYMMDD

# =========================================================================================================



# MySQL有四个常量: true false TRUE FALSE 分别对应着 1 0 1 0

# 当然, null也算
    SQL, NULL 与 空字符串, 零 都不相同。是指为未定义或是未知的值
    NULL 值的处理方式与其它值不同: 无法比较 NULL0;它们是不等价的

# 怕忘记: DEFAULT, default表达式形式的一种插入方式.

# 位模式字面量
# b'val' 或 0bval 语法
# val 是只包含 0 和 1 的二进制值
# 字符 b 之前有无 0 都无关紧要,
# 但要注意的是 0b 是区分大小写的,0b 不能写为 0B ,
# 但用 b 的话, 不限大小写。b001 和 B001 是一个意思

# 以下这些是合法的位类型字面量
# b'01'
# B'01'
# 0b01

# 而下面这些,则是非法的位类型字面量
# b'2'    (2 不是合法的二进制数字 )
# 0B01    (0B 必须是 0b)

# 默认情况下,位类型的字面量是一个二进制字符串, 会以ASCII字符显示

# 位类型字面量可以有一个可选的字符集介绍器和 COLLATE 子句,用于指定为使用特定字符集和排序规则的字符串
# [_charset_name] b'val' [COLLATE collation_name]
# 例如: select _utf8mb4 b'1000001' collate utf8mb4_0900_ai_ci;

# 在数值上下文中,MySQL 会把位类型的数据转换为相应的整型。所以,如果要确保对位类型字面值进行数字处理,请在数字上下文中使用它。
# 至于如何做到这一点,可以在把这个位类型数据进行 + 0 操作或者使用 CAST (... AS UNSIGNED) 转换器。
# 例如,默认情况下,分配给用户定义变量的位字面量是二进制字符串。如果要将值指定为数字,请在数字上下文中使用它:
# 空位值 b'' 将被计算为零长度二进制字符串。转换为数字则位 0 :

# =================================================
# mysql> SET @v1 = b'1100001';
# mysql> SET @v2 = b'1100001'+0;
# mysql> SET @v3 = CAST(b'1100001' AS UNSIGNED);
# mysql> SET @v4 = b'';
# mysql> SELECT @v1, @v2, @v3, @v4+0;
# +------+------+------+------+
# | @v1  | @v2  | @v3  | @v4  |
# +------+------+------+------+
# | a    |   97 |   97 |  0   |
# +------+------+------+------+
# =======================================================

# 在查询时,结果集中的位值将作为二进制值返回,可读性就会差很多,
# 这时候我们可以将它们转换为十进制或者十六进制,
# 我们可以使用转换函数 bin() 或 hex() 两个函数来完成这种转换。
# 转换的时候会忽略高位中的 0

# =============================================
# mysql> SELECT b+0, BIN(b), OCT(b), HEX(b) FROM t;
# +------+----------+--------+--------+
# | b+0  | BIN(b)   | OCT(b) | HEX(b) |
# +------+----------+--------+--------+
# |  255 | 11111111 | 377    | FF     |
# |   10 | 1010     | 12     | A      |
# |    5 | 101      | 5      | 5      |
# +------+----------+--------+--------+
# =============================================

# 对于位类型,位操作被视为应当在数字上下文中进行,但在 MySQL 8.0 及更高的版本中,可以直接使用位操作数字或二进制字符串参数
# 在这种情况下,就至少要位其中一个参数使用 _binary 介绍器,将位类型的数据指定为二进制字符串上下文

# ===============================================
mysql> SET @v1 = b'000010101' | b'000101010';
mysql> SET @v2 = _binary b'000010101' | _binary b'000101010';
mysql> select @v1, @v2;
+------+------+
| @v1  | @v2  |
+------+------+
|   63 |  ?   |
+------+------+
# ===============================================
# 在上面这个范例中,没有 _binary 的结果是 BIGINT 值,
# 而 _binary 的结果是二进制字符串。


# 数字类:

#       bit [(m)]: m为二进制位数,默认为1,最多64


#       作用:存储年龄,等级,id,各种号码等
#       tinyint [(m)] [unsigned] [zerofill]:
#               m用作显示时候的宽度;
#               unsigned表示数字为无符号类型,默认为signed类型
#               zerofill表示不够宽度的时候用0来填充,默认用空格;不能用于负数
#               由于MySQL没有布尔类型所以要用:tinyint(1) 来构造。(现在可以用bit来代替)

#       如果为数值列指定了 ZEROFILL ,MySQL 会自动添加 UNSIGNED 属性到列中。
#       另外, unsigned 以及 zerofill 都不是标准属性

#           signed: -2^7 ~ 2^7 - 1
#           unsigned: 0 ~ 2^8 - 1
            +------+
            | x    |
            +------+
            | -128 | #-129存成了-128
            | -128 | #有符号,最小值为-128
            |  127 | #有符号,最大值127
            |  127 | #128存成了127
            +------+
        smallint  2个字节
        mediumint   3个字节
#       int/integer [(m)] [unsigned] [zerofill]:
#           signed: -2^31 ~ 2^31 - 1
#           unsigned: 0 ~ 2^32 - 1
#       bigint [(m)] [unsigned] [zerofill]:
#           signed: -2^63 ~ 2^63 - 1
#           unsigned: 0 ~ 2^64 - 1

在表达式或 UNION 查询中涉及列时,会自动忽略 ZEROFILL 属性。

使用 ZEROFILL 属性时要注意,如果将大于显示宽度的值存储在具有 ZEROFILL 属性的整数列中,
为某些复杂连接生成临时表时可能会遇到问题。
因为在这些情况下,MySQL 会假定数据值符合列显示宽度

PS:注意:为该整类型指定宽度时,仅仅只是指定查询结果的显示宽度,与存储范围无关,超过宽度了,原样输出
          其实我们完全没必要为整数类型指定显示宽度,使用默认的就可以了

          默认的显示宽度,都是在最大值的基础上加1
          有符号和无符号的最大数字需要的显示宽度均为10,
          而针对有符号的最小值则需要11位才能显示完全,所以int类型默认的显示宽度为11是非常合理的
          最后:整形类型,其实没有必要指定显示宽度,使用默认的就ok

#######################################################################################################

        作用:存储薪资、身高、体重、体质参数等
#       decimal [(m,[d])] [unsigned] [zerofill]:
#               m表示小数点前后的数字的总数,负号不算,m最大65
#               d表示小数点后的数字的位数,如果省略d,只有m的话,就是四舍五入取整;
                如果有d,但位数不足的话,补0,反正要d位,d最大30
#               存储数字是准确的,因为内部采用字符串的形式
#       float [(m,d)] [unsigned] [zerofill]:
                单精度浮点数(非准确小数值),数值越大越不准,4个字节
                m是数字总个数,d是小数点后个数。m最大值为255,d最大值为30
#       double [(m,d)] [unsigned] [zerofill]:
                双精度浮点数(非准确小数值),精度比float要高,数值越大也会变得不准确,8个字节
                m是数字总个数,d是小数点后个数。m最大值为255,d最大值为30

########################################################################################################
# 字符类:
#       char[(m)]: m表示字符数;最多255 (一个中文是一个字符) (默认1)
#                定长,简单粗暴,浪费空间,存取速度快
    存储:
        存储char类型的值时,会往右填充空格来满足长度
        例如:指定长度为10,存>10个字符则报错,存<10个字符则用空格填充直到凑够10个字符存储

    检索:
        在检索或者说查询时,查出的结果会自动删除尾部的空格,除非我们打开pad_char_to_full_length SQL模式(SET sql_mode = 'PAD_CHAR_TO_FULL_LENGTH';)

注:对于空间问题,假设charvarchar的字符数都是4,那么当要存储的字符数<4时,
    varchar确实会节省空间;但是当要存储的字符数=4时,varchar反而会浪费空间。

#       varchar[(m)]: m表示字符数;(v8中, 必须指定字符数)
#               变长,精准,节省空间,存取速度慢,有几位占几位


    字符长度范围:0-65535(如果大于21845会提示用其他类型 。
    mysql行最大限制为65535字节,字符编码为utf-8:https://dev.mysql.com/doc/refman/5.7/en/column-count-limit.html)
    存储:
        varchar类型存储数据的真实内容,不会用空格填充,如果'ab  ',尾部的空格也会被存起来
        强调:varchar类型会在真实数据前加1-2Bytes的前缀,该前缀用来表示真实数据的bytes字节数(1-2Bytes最大表示65535个数字,正好符合mysql对row的最大字节限制,即已经足够使用)
        如果真实的数据<255bytes则需要1Bytes的前缀(1Bytes=8bit 2**8最大表示的数字为255)
        如果真实的数据>255bytes则需要2Bytes的前缀(2Bytes=16bit 2**16最大表示的数字为65535)

    检索:
        尾部有空格会保存下来,在检索或者说查询时,也会正常显示包含空格在内的内容

1. char填充空格来满足固定长度,但是在查询时却会很不要脸地删除尾部的空格(装作自己好像没有浪费过空间一样),
   然后修改sql_mode让其现出原形
    set sql_mode = 'PAD_CHAR_TO_FULL_LENGTH';
    set sql_mode = ''; 还原

2.  虽然 CHARVARCHAR 的存储方式不太相同,但是对于两个字符串的比较,都只比 较其值,忽略 CHAR 值存在的右填充,
    即使将 SQL _MODE 设置为 PAD_CHAR_TO_FULL_ LENGTH 也一样,,但这不适用于like

注:虽然varchar使用起来较为灵活,但是从整个系统的性能角度来说,char数据类型的处理速度更快,有时甚至可以超出varchar处理速度的50%。
    因此,用户在设计数据库时应当综合考虑各方面的因素,以求达到最佳的平衡

SQL优化建议:
1. 将定长的数据放在前面,不定长的数据放在后面
3. 最好不要在一张表中混用charvarchar
2. >255个字符,超了就把文件路径存放到数据库中。
    比如图片,视频等找一个文件服务器,数据库中只存路径或url。

效率:char>varchar>text

#       text: 2^16 - 1个字符   不设置长度, 就算设置了第度, 也不起作用, (用于当不知道属性的最大长度时)
#       mediumtext: 2^24 - 1个字符
#       longtext: 2^32 - 1个字符

结论:
1、经常变化的字段用varchar2、知道固定长度的用char3、超过255字节的只能用varchar或者text4、能用varchar的地方不用text5、能够用数字类型的字段尽量选择数字类型而不用字符串类型,这会降低查询和连接的性能,并会增加存储开销。
   这是因为引擎在处理查询和连接会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了;

6、同一张表出现多个大字段,能合并时尽量合并,不能合并时考虑分表,原因请考 优化InnoDBBLOB,TEXT列的存储效率https://www.jb51.net/article/157892.htm
##################################################################################################

# 作用:存储用户注册时间,文章发布时间,员工入职时间,出生时间,过期时间等
# 时间类:
#       date: YYYY-MM-DD (1000-01-01 / 9999-12-31)
#             YY-MM-DD
#             变形,就是指中划线 ( - ) 可以替换为任意字符,除了数字 0-9:
#             例如:
#                 YYYY/MM/DD
#                 YYYY^MM^DD

#             同时还支持 YYYYMMDD 和 YYMMDD 的字符串形式,例如 '20180913' 和 '180913'。
#             还支持 YYYYMMMDD 和 YYMMDD 的数字格式,例如 20180913 和 180913。
#
#             插入两位年份时,<=69,以20开头,比如50,  结果2050
#                            >=70,以19开头,比如71,结果1971

#             插入年份时,尽量使用4位值

#       time: HH:MM:SS  ('-838:59:59'/'838:59:59')
              单独插入时间时,需要以字符串的形式,按照对应的格式(只能是:分隔)插入

#       year: YYYY (1901 - 2155)  指定类型是要么是year要么是year(4), 不管那种, 最后都是year(4)
#             4位 or 2位的 字符 or int 年份都可以

#       datetime: YYYY-MM-DD HH:MM:SS  (1000-01-01 00:00:00/9999-12-31 23:59:59)

#       timestamp: YYYY-MM-DD HH:MM:SS  (1970-01-01 00:00:00 / 2038)
#             insert into t13 values();
#             insert into t13 values(null);

# 对于 datetime 和 timestamp 类型,支持 YYYY-MM-DD HH:MM:SS 或 YY-MM-DD HH:MM:SS,以及它们的变形

# 这个变形其实和 DATE 一样,而且就是指中划线 ( - ) 可以替换为任意字符,除了数字 0-9 。

# 更进一步,这两个类型可以针对日期部分和时间部分使用不同的分隔符
# 例如:
#     YYYYY^MM^DD HH+MM+SS
#        YY@MM@DD HH^MM^SS

# datetime 和 timestamp 两种时间类型还可以精确到 微秒 ,
# 具体的形式就是支持在 秒 后面使用 6 位精度的小数来支持,
# 例如:
#     2018-09-13 21:15:10.111111
# 例如:
#     2018-09-13 21:15:10.11
# 日期和时间部分与小数秒部分之间的唯一可用的分隔符号是小数点 ( . )

# 日期与时间部分的分隔符不一定就要使用空格 ( ' ' ),还可以使用字符 T ,
# 例如:
#     2018-09-13 21:15:10 和 2018-09-13T21:15:10 是等价的

# 同时还可以使用没有任何分隔符的形式,
# 比如 YYYYMMDDHHMMSS 或 YYMMDDHHMMSS,
# 例如 '20180913211510' 与 2018-09-13 21:15:10 是等价的

# 但 071122129015 则是非法的,并不是因为年份缺少了,而是因为分钟太大 ( 90 ),然后会被视为 0000-00-00 00:00:00

# 因为 MySQL 同时支持超大整数类型,
# 所以,数字形式的 YYYYMMDDHHMMSS 或 YYMMDDHHMMSS 也是被支持的,
# 例如 20180913211510 和 180913211510 被认为与 2018-09-13 21:15:10 是等价的

============注意啦,注意啦,注意啦===========
1. 单独插入时间时,需要以字符串的形式,按照对应的格式插入

2. 插入年份时,尽量使用4位值

3. 插入两位年份时,<=69,以20开头,比如50,  结果2050
                  >=70,以19开头,比如71,结果1971

######################################################################################
datetimetimestamp的区别

在实际应用的很多场景中,MySQL的这两种日期类型都能够满足我们的需要,存储精度都为秒,但在某些情况下,会展现出他们各自的优劣。
下面就来总结一下两种日期类型的区别。

1. DATETIME的日期范围是1001——9999年,TIMESTAMP的时间范围是1970——2038年。

2. timestamp 会将值的时区从当前时区转换为 UTC 然后存储,并在需要显示和检索时从 UTC 转换回当前时区
   但对于 datetime 类型,什么都不会发生,值是什么时区,存储的就是什么时区

    默认情况下,timestampdatetime 都会将当前连接的 MySQL 服务器的时区当作当前时区,当然了,这个时区是可配置的,而且可以针对每个连接单独配置。

    从某些方面说,在数据转移或者在不同地方显示时,只要设置了一样的时区,那么数据就是一致的,否额

    datetime 的值虽然存储和显示的时候都是同一个值,但可能不是我们想要的,因为时区错了
    timestamp 虽然可以保证时间是正常的,但存储的值和显示的值可能会不一样,可能会导致我们错觉发生

3. DATETIME使用8字节的存储空间,TIMESTAMP的存储空间为4字节。因此,TIMESTAMPDATETIME的空间利用率更高。

4. MySQL 还为 datetimetimestamp 两种日期时间类型提供了自动赋值功能。
   也就是在 MySQL INSERT 时可以自动初始化为当前日期时间,在 MySQL Update 时自动更新为当前时间。

    CREATE TABLE t1 (
      ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
      dt DATETIME  DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    );

    ====================不要怀疑, 就是update, 没有insert=============================================
    两个约束
    DEFAULT CURRENT_TIMESTAMP  该列会自动设置当前时间为默认值
    ON UPDATE CURRENT_TIMESTAMP 自动更新为当前时间戳
    1. DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 指示在 insert 操作时自动插入当前日期时间
    2. DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 指示在 update 操作时自动更新为当前日期时间

    对于timestamp, insert的时候可以这样values() /  values(null) / 自已指定 / values(default);update的时候, 自动更新
    对于datetime, insert的时候可以这样values() / 自已指定 / values(default) / 不能values(null);update的时候, 自动更新


    =====================================================================================================
   2.使用了 default 约束,当没有使用 ON UPDATE CURRENT_TIMESTAMP 约束时,
     该列会自动使用给定的默认值,但不会自动更新为当前时间戳
     而默认的值取决于 default 子句是指定 CURRENT_TIMESTAMP 还是常量值。
     如果使用 CURRENT_TIMESTAMP,默认值是当前时间戳

        CREATE TABLE t1 (
          ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
          dt DATETIME DEFAULT CURRENT_TIMESTAMP
        );
    =====================================================================================================


5. 对于这三种日期时间类型,无效的值将会转换为相应的 零 值,
    也就是,date 类型会转换为 0000-00-00datetime 类型会转换为 0000-00-00 00:00:00 ,
    而 timestamp 则会转换为 1970-01-01 00:00:00

6. 日期时间类型,还可以各取所长
    例子: create table employee(d date, t time, dt datetime);
            insert employee values(now(), now(), now());
#####################################################################################################

# 枚举类型:如单选框,多选框
单选 只能在给定的范围内选一个值
# CREATE TABLE shirts (
#        name VARCHAR(40),
#        size ENUM('x-small', 'small', 'medium', 'large', 'x-large')
# );
# INSERT INTO shirts (name, size) VALUES ('dress shirt','large'), ('t-shirt','medium'),('polo shirt','small');
# 插入记录的时候,对应字段的值只能从enum()已规定好的值中选取
# ENUM 最多只能包含 65,535 个不同的枚举值
# 用于选项固定,不经常修改

# ===========================================
1. 创建表结构时指定的枚举值都会分配一个内部索引,索引的下标从 1 开始

2. 注意:下标并不是从 0 开始,而 0 则具有其它的意义
         空字符串错误值的索引为 0,这样,我们可以直接使用 0 值来查询那些插入的或更新的无效的枚举值
         (新版中, 插入不存在, 直接报错)

3. NULL 值的索引为 NULL

枚举值	        索引
NULL	        NULL
'' 空字符串	    0
'Mercury'	    1
'Venus'	        2
'Earth'	        3

4. ENUM 最多只能包含 65,535 个不同的枚举值

5. 如果在数字上下文中检索 ENUM 值,则返回列值的索引
   SELECT enum_col+0 FROM tbl_name;

6. 当在 ENUM 列上使用 SUM()AVG() 等聚合函数时,
   因为这些函数的参数必须是一个数字,所以 MySQL 会自动使用它们的索引值作为参数。
   也就是说,对于需要计算的场景,都会使用内部索引。
   其实,真实的枚举值,只有在插入或者显示或者查询时才会用到。

7. ENUM的字面量处理
    在创建表结构时,MySQL 会自动删除 ENUM 枚举值的尾随空格,例如会把 'medium ' 转换成 'medium'。
    检索时,MySQL 会自动将存储的内部索引转换为定义时指定的相应的 enum 枚举值字面量。

8. 不建议使用看起来像数字的枚举值来定义 ENUM 列,
   因为这很容易让人感到困惑,分不清传递(引用) 的到底是枚举值字面量还是内部索引。

    例如,以下列的枚举成员的字符串值为 '0''1''2',而数字索引值为 123

    numbers ENUM('0','1','2')
    如果我们在插入数据或者更新数据时指定存储 2 ,因为会被解释为索引值,所以实际存储的枚举值为 '1' ( 索引为 2 的值 )。

    而如果我们存储 '2' ,因为枚举值字面量 '2' 存在,所以存储的值也为 2 。

    但如果我们存储 '3' ,因为枚举值字面量 '3' 并不存在,那么它就会被视为是内部索引 3 ,进而存储的实际值其实是 '2'

        mysql> INSERT INTO t (numbers) VALUES(2),('2'),('3');
        mysql> SELECT * FROM t;
        +---------+
        | numbers |
        +---------+
        | 1       |
        | 2       |
        | 2       |
        +---------+
    如果要确定 ENUM 列的所有可能值,
    SHOW COLUMNS FROM tbl_name LIKE 'enum_col' 语句可以解析出 enum_col 列中的所有 enum 定义

9. 如果一个 ENUM 列添加了 NULL 约束,那么这个 ENUM 列就允许 NULL 值,且默认的值就是 NULL

   如果一个 ENUM 列添加了 NOT NULL 约束,那么它的默认值就是第一个枚举值。

10. ENUM 枚举值的排序问题
    因为 ENUM 类型存储的是枚举值的内部索引,
    所以 ENUM 值根据其索引号进行排序,具体显示出来,则取决于定义列是的枚举成员顺序。

    https://www.twle.cn/c/yufei/mysqlfav/mysqlfav-basic-enum2.html

    例如,如果在定义列时,指定了 'b''a' 前面 ('b''a'),那么 'b' 的顺序就会在 'a' 之前,且空字符串在非空字符串之前排序,NULL 值在所有其他枚举值之前排序

    也就是排序的顺序默认是 NULL '' 'b' 'a'

    这是一个大坑啊,为了避免这个坑,为了在 ENUM 列上使用 ORDER BY 子句时防止出现意外结果,则需要做如下选择

    指定 ENUM 列的排序顺序使用字母顺序表

    或者使用 ORDER BY CAST (col AS CHAR)ORDER BY CONCAT(col) 确保 enum 列按词法排序而不是索引编号排序

11. ENUM 数据类型的一些限制

    1. 枚举值不能是表达式,即使该表达式用于计算字符串值。

        例如,下面的建表语句是无效的,会执行失败,因为 CONCAT()函数不能用于构造枚举值

        CREATE TABLE sizes (
            size ENUM('small', CONCAT('med','ium'), 'large')
        );

    2. 不能使用用户变量作为枚举值。例如下面的语句也是无效的

        SET @mysize = 'medium';

        CREATE TABLE sizes (
            size ENUM('small', @mysize, 'large')
        );

    3. 我们强烈建议不要使用数字用作枚举值,
       因为它不会通过适当的 TINYINTSMALLINT 类型保存在存储上。
       而且,如果你错误地引用 ENUM 值,很容易混淆枚举字面量和底层索引值 ( 可能不相同 )

    4. ENUM 列定义中的重复值会导致警告,如果启用了严格的 SQL 模式,则会出错
# ===========================================

# 集合类型:
多选 在给定的范围内可以选择一个或一个以上的值
# CREATE TABLE myset (col SET('a', 'b', 'c', 'd'));
# INSERT INTO myset (col) VALUES ('a,d'), ('d,a'), ('a,d,a'), ('a,d,d'), ('d,a,d');
# 插入记录的时候,对应字段的值只能从set()规定好的值中任意组合进行插入
# 去重
# 最多 64 个

# 二进制类型:
# TinyBlob、Blob、MediumBlob、LongBlob

# JSON数据类型
    # =======================================================================================================
        MySQL支持JSON数据类型。相比于Json格式的字符串类型,JSON数据类型的优势有:

        存储在JSON列中的JSON文档的会被自动验证。无效的文档会产生错误;
        最佳存储格式。存储在JSON列中的JSON文档会被转换为允许快速读取文档元素的内部格式。

        存储在JSON列中的任何JSON文档的大小都受系统变量max_allowed_packet的值的限制,
        可以使用JSON_STORAGE_SIZE()函数获得存储JSON文档所需的空间。

        JSON值的局部更新
        在MySQL8.0中,优化器可以执行JSON列的局部就地更新,而不用删除旧文档再将整个新文档写入该列。局部更新的条件:

        1. 正在更新的列被声明为JSON;
        2.UPDATE语句使用任一的三个函数 JSON_SET(), JSON_REPLACE()或 JSON_REMOVE()更新列;
        3. 输入列和目标列必须是同一列;
        4. 所有更改都使用新值替换现有数组或对象值,并且不向父对象或数组添加任何新元素;
        5. 新值不能大于旧值;

        创建JSON值:
            JSON数字
                '10'
            JSON字符串
                '"HELLO"'
            JSON数组
                ["abc", 10, null, true, false]
            JSON对象
                {"k1": "value", "k2": 10}
            嵌套
                [99, {"id": "HK500", "cost": 75.99}, ["hot", "cold"]]

                {"k1": "value", "k2": [10, 20]}
            例子:
                mysql> CREATE TABLE t_json (jdoc JSON) ENGINE=InnoDB DEFAULT CHARSET=utf8;
                Query OK, 0 rows affected, 1 warning (0.73 sec)

                mysql> INSERT INTO t_json VALUES('[1,2]');
                Query OK, 1 row affected (0.17 sec

                mysql> INSERT INTO t_json VALUES('{"key1":"value1","key2":"value2"}');
                Query OK, 1 row affected (0.27 sec)

                mysql> INSERT INTO t_json VALUES('"HELLO"');
                Query OK, 1 row affected (0.20 sec)

        JSON_TYPE()函数:
            JSON_TYPE()函数尝试将传入的值其解析为JSON值。如果值有效,则返回值的JSON类型,否则产生错误:
            例如:
                SELECT JSON_TYPE('["a","b",true,13]');  -- ARRAY

        JSON_ARRAY()函数:
            JSON_ARRAY()接收传入的值列表(可以为空),返回包含这些值的JSON数组;
            例如:
                SELECT JSON_ARRAY("ab",false,13);  -- ["ab", false, 13]

                SELECT JSON_ARRAY(); -- JSON_ARRAY()

        JSON_OBJECT()函数:
            JSON_OBJECT() 接收传入的键值对列表(可以为空),并返回包含这些键值对的JSON对象:
            例如:
                select json_object("key1", "a", "key2", 20);  --  {"key1": "a", "key2": 20}

                select json_object();  --  {}

        JSON_MERGE_PRESERVE()函数:
            JSON_MERGE_PRESERVE() 获取两个或多个JSON文档并返回组合结果:
            例如:
                SELECT JSON_MERGE_PRESERVE('["a", 1]', '{"key": "value"}');  -- ["a", 1, {"key": "value"}]

        因此我们也可以使用以上三种方法向表中添加JSON值,可以一定程度地避免输入格式错误:
            mysql> INSERT INTO t_json VALUES(JSON_ARRAY('json_array'));
            Query OK, 1 row affected (0.19 sec)

            mysql> INSERT INTO t_json VALUES(JSON_OBJECT('key','hello'));
            Query OK, 1 row affected (0.09 sec)

            mysql> INSERT INTO t_json VALUES(JSON_MERGE_PRESERVE(JSON_OBJECT('key','hello'),JSON_ARRAY(1,2)));
            Query OK, 1 row affected (0.14 sec)

        JSON值的规范化,合并和自动包装:
            解析字符串并发现字符串是有效的JSON文档时,它在被解析时也会被规范化。
            对于重复的键(key,后面的值(value)会覆盖前面的值。
            例如:
                SELECT JSON_OBJECT('x',1,'y',2,'x','a','x','b');  -- {"x": "b", "y": 2}

        合并JSON值:
            MySQL8.0.3及更高版本中,有两种合并函数:JSON_MERGE_PRESERVE()和 JSON_MERGE_PATCH()。
            下面具讨论它们的区别。
                合并数组:
                    SELECT JSON_MERGE_PATCH('[1, 2]', '["a", "b", "c"]','[1, 2]', '[true, false]');
                    -- [true, false]

                    SELECT JSON_MERGE_PRESERVE('[1, 2]', '["a", "b", "c"]','[1, 2]', '[true, false]');
                    -- [1, 2, "a", "b", "c", 1, 2, true, false]
                合并数组时,
                    JSON_MERGE_PATCH只保留最后传入的数组参数,
                    而JSON_MERGE_PRESERVE则按传入顺序将数组参数连接。
                    两者必须有2个参数及以上

                合并对象:
                    SELECT JSON_MERGE_PATCH('{"a": 3, "b": 2}', '{"c": 3, "a": 4}', '{"c": 5, "d": 3}');
                    -- {"a": 4, "b": 2, "c": 5, "d": 3}

                    SELECT JSON_MERGE_PRESERVE('{"a": 3, "b": 2}', '{"c": 3, "a": 4}', '{"c": 5, "d": 3}');
                    --  {"a": [3, 4], "b": 2, "c": [3, 5], "d": 3}
                合并对象时:
                    合并对象时,对于重复键,JSON_MERGE_PRESERVE只保留最后传入的键值,
                    而JSON_MERGE_PRESERVE重复键的所有值保留为数组。

        搜索和修改JSON值:
            在了解搜索和修改JSON值之前,先来看看JSON的路径语法。
            路径语法:
                1. .keyName:JSON对象中键名为keyName的值;
                2. 对于不合法的键名(如有空格),在路径引用中必须用双引号"将键名括起来,例,."key name";
                3. [index]:JSON数组中索引为index的值,JSON数组的索引同样从0开始;
                4. [index1 to index2]:JSON数组中从index1到index2的值的集合;
                5. .*: JSON对象中的所有value;放入一个数组中
                6. [*]: JSON数组中的所有值;
                7. prefix**suffix: 以prefix开头并以suffix结尾的路径;
                8. **.keyName为多个路径,如对于JSON对象'{"a": {"b": 1}, "c": {"b": 2}}','$**.b'指路径$.a.b和$.c.b;
                9. 不存在的路径返回结果为NULL;
                10. 前导$字符表示当前正在使用的JSON文档

                例子:对于数组[3, {"a": [5, 6], "b": 10}, [99, 100]]
                    $[1]为{"a": [5, 6], "b": 10}。
                    [1].a为[5, 6]。
                    $[1].a[1]为 6。
                    $[1].b为 10。
                    $[2][0]为 99。

        搜索:
            JSON_EXTRACT提取JSON值
                JSON对象
                    SELECT JSON_EXTRACT('{"id": 29, "name": "Taylor"}', '$.name');
                    -- "Taylor"

                    SELECT JSON_EXTRACT('{"id": 29, "name": "Taylor"}', '$.*');
                    -- [29, "Taylor"]

                JSON数组
                    SELECT JSON_EXTRACT('["a", "b", "c"]', '$[1]');
                    -- "b"

                    SELECT JSON_EXTRACT('["a", "b", "c"]', '$[1 to 2]');
                    --  ["b", "c"]

                    SELECT JSON_EXTRACT('["a", "b", "c"]', '$[*]');
                    -- ["a", "b", "c"]

        修改:
            1. JSON_REPLACE 替换值(只替换已经存在的旧值)
            2. JSON_SET 设置值(替换旧值,并插入不存在的新值)
            3. JSON_INSERT 插入值(插入新值,但不替换已经存在的旧值)
            4. JSON_REMOVE 删除JSON数据,删除指定值后的JSON文档
            JSON_REPLACE与JSON_SET的区别:
            // 旧值存在
            mysql> SELECT JSON_REPLACE('{"id": 29, "name": "Taylor"}', '$.name', 'Mere');
            +----------------------------------------------------------------+
            | JSON_REPLACE('{"id": 29, "name": "Taylor"}', '$.name', 'Mere') |
            +----------------------------------------------------------------+
            | {"id": 29, "name": "Mere"}                                     |
            +----------------------------------------------------------------+
            1 row in set (0.00 sec)

            mysql> SELECT JSON_SET('{"id": 29, "name": "Taylor"}', '$.name', "Mere");
            +------------------------------------------------------------+
            | JSON_SET('{"id": 29, "name": "Taylor"}', '$.name', 'Mere') |
            +------------------------------------------------------------+
            | {"id": 29, "name": "Mere"}                                 |
            +------------------------------------------------------------+
            1 row in set (0.00 sec)

            // 旧值不存在
            mysql> SELECT JSON_REPLACE('{"id": 29, "name": "Taylor"}', '$.cat', 'Mere');
            +---------------------------------------------------------------+
            | JSON_REPLACE('{"id": 29, "name": "Taylor"}', '$.cat', 'Mere') |
            +---------------------------------------------------------------+
            | {"id": 29, "name": "Taylor"}                                  |
            +---------------------------------------------------------------+
            1 row in set (0.00 sec)

            mysql> SELECT JSON_SET('{"id": 29, "name": "Taylor"}', '$.cat', 'Mere');
            +-----------------------------------------------------------+
            | JSON_SET('{"id": 29, "name": "Taylor"}', '$.cat', 'Mere') |
            +-----------------------------------------------------------+
            | {"id": 29, "cat": "Mere", "name": "Taylor"}               |
            +-----------------------------------------------------------+
            1 row in set (0.00 sec)

            JSON_INSERT和JSON_SET:
            // 旧值存在
            mysql> SELECT JSON_INSERT('[1, 2, 3]', '$[1]', 4);
            +-------------------------------------+
            | JSON_INSERT('[1, 2, 3]', '$[1]', 4) |
            +-------------------------------------+
            | [1, 2, 3]                           |
            +-------------------------------------+
            1 row in set (0.00 sec)

            mysql> SELECT JSON_SET('[1, 2, 3]', '$[1]', 4);
            +----------------------------------+
            | JSON_SET('[1, 2, 3]', '$[1]', 4) |
            +----------------------------------+
            | [1, 4, 3]                        |
            +----------------------------------+
            1 row in set (0.00 sec)

            //旧值不存在
            mysql> SELECT JSON_INSERT('[1, 2, 3]', '$[4]', 4);
            +-------------------------------------+
            | JSON_INSERT('[1, 2, 3]', '$[4]', 4) |
            +-------------------------------------+
            | [1, 2, 3, 4]                        |
            +-------------------------------------+
            1 row in set (0.00 sec)

            mysql> SELECT JSON_SET('[1, 2, 3]', '$[4]', 4);
            +----------------------------------+
            | JSON_SET('[1, 2, 3]', '$[4]', 4) |
            +----------------------------------+
            | [1, 2, 3, 4]                     |
            +----------------------------------+
            1 row in set (0.00 sec)

            JSON_REMOVE:
            mysql> SELECT JSON_REMOVE('[1, 2, 3]', '$[1]');
            +----------------------------------+
            | JSON_REMOVE('[1, 2, 3]', '$[1]') |
            +----------------------------------+
            | [1, 3]                           |
            +----------------------------------+
            1 row in set (0.00 sec)

            mysql> SELECT JSON_REMOVE('[1, 2, 3]', '$[4]');
            +----------------------------------+
            | JSON_REMOVE('[1, 2, 3]', '$[4]') |
            +----------------------------------+
            | [1, 2, 3]                        |
            +----------------------------------+
            1 row in set (0.00 sec)

            mysql> SELECT JSON_REMOVE('{"id": 29, "name": "Taylor"}', '$.name');
            +-------------------------------------------------------+
            | JSON_REMOVE('{"id": 29, "name": "Taylor"}', '$.name') |
            +-------------------------------------------------------+
            | {"id": 29}                                            |
            +-------------------------------------------------------+
            1 row in set (0.00 sec)
    # =========================================================================================================


#############################################################################################################

# 完整性约束:用于保证数据的完整性和一致性
# 为了防止不符合规范的数据进入数据库,在用户对数据进行插入、修改、删除等操作时,
# DBMS自动按照一定的约束条件对数据进行监测,
# 使不符合规范的数据不能进入数据库,以确保数据库中存储的数据正确、有效、相容。
# 约束可以在创建表时指定 ( 通过 CREATE TABLE 语句 ),或者在表创建之后设置 ( 通过 ALTER TABLE 语句 )

# =====================================================
SQL CREATE TABLE + CONSTRAINT 语法
    CREATE TABLE table_name (
        column_name1 data_type ( size ) constraint_name ,
        column_name2 data_type ( size ) constraint_name ,
        column_name3 data_type ( size ) constraint_name ,
        ....
    );

SQL 规范中,可以使用下面这些约束
约束	            说明
NOT NULL	        指示某列不能存储 NULL 值
UNIQUE	            保证某列的每行必须有唯一的值
PRIMARY KEY	        NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识
                    有助于更容易更快速地找到表中的一个特定的记录
FOREIGN KEY	        保证一个表中的数据匹配另一个表中的值的参照完整性
CHECK	            保证列中的值符合指定的条件
DEFAULT	            规定没有给列赋值时的默认值
# =====================================================

# 1. null、not null
     null,1. 表示可以为空(默认不用传值,使用default的值,default默认值为null)
           2. 表示可以为空值(也就是传null值,并非空字符串)

     not null,1. 表示不能为空(也就是必须传值,当然也可以使用default的值,
                              但是default的值不能是null值)
               2. 表示不能为空值(也就是不能传null值)
        # =========================================================
            CREATE TABLE 时的 SQL NOT NULL 约束:
                在创建表结构时,可以给字段添加 NOT NULL 关键字来添加 NOT NULL 约束
                    CREATE TABLE lesson (
                        id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
                        name varchar(32) default '',
                        views int(11) NOT NULL default '0',
                        created_at DATETIME
                    );
            ALTER TABLE 时的 SQL not null 约束:
                如果表已经被创建,而又想添加 NOT NULL 约束,可以使用 ALTER TABLE 命令
                    ALTER TABLE lesson MODIFY COLUMN views int(11) NOT NULL default '0';

            删除 NOT NULL 约束
                如果想要删除 NOT NULL 约束,可以使用 ALTER TABLE 命令,
                也就是不指定 NOT NULL 关键字即可。
                    ALTER TABLE lesson MODIFY COLUMN views int(11) default '0';
        # ===========================================================

# 2. default

     # ===============================================================
     default,
     默认值,创建列时可以指定默认值,缺省的默认值是NULL,
     当插入数据时如果未主动设置,则自动添加默认值

     注意:当设置为not null时,default不能为null

     DEFAULT 约束中指定的默认值可以是文字常量或表达式。
     如果使用表达式作为默认值,则需要表达式默认值括在括号内 () ,以将它们与文字常量默认值区分开来。
     例如:
         CREATE TABLE t1 (
           -- 常量默认值
           i INT         DEFAULT 0,
           c VARCHAR(10) DEFAULT '',
           -- 表达式默认值
           f FLOAT       DEFAULT (RAND() * RAND()),
           b BINARY(16)  DEFAULT (UUID_TO_BIN(UUID())),
           d DATE        DEFAULT (CURRENT_DATE + INTERVAL 1 YEAR),
           p POINT       DEFAULT (Point(0,0)),
           j JSON        DEFAULT (JSON_ARRAY())
         );
    但是,这有一个例外。这个例外就是: TIMESTAMP 和 DATETIME 列。
    对于 TIMESTAMP 和 DATETIME 列,我们可以将 CURRENT_TIMESTAMP 函数指定为默认值,而需要添加括号。

    表达式默认值必须遵守以下规则。如果表达式包含不允许的构造,则会发生错误

    1. 允许使用文字,内置函数(确定性和非确定性)和运算符
    2. 不允许使用子查询,参数,变量,存储函数和用户定义的函数
    3. 表达式默认值不能依赖于具有 AUTO_INCREMENT 属性的列。
    4. 某一列的表达式默认值可以引用另外一张表中的列,但是对生成的列或具有表达式默认值的列的引用必须是对于在表定义中较早出现的列。
       也就是说,表达式默认值不能包含对生成的列或具有表达式默认值的列的前向引用。翻译成白话文就是,引用的列必须已经存在。
    5. 排序 ( ordering ) 约束也适用于使用 ALTER TABLE 重新排序表列。
       如果结果表的表达式默认值包含对具有表达式默认值的生成列或列的前向引用,则该语句将失败

    对于语句 CREATE TABLE ... LIKE 和 CREATE TABLE ... SELECT ,目标表保留原始表中的表达式默认值。

    插入新行时,可以通过省略列名或将列指定为 DEFAULT 来插入具有表达式 default 的列的默认值(就像具有文字默认值的列一样)
        mysql> CREATE TABLE t4 (uid BINARY(16) DEFAULT (UUID_TO_BIN(UUID())));
        mysql> INSERT INTO t4 () VALUES();
        mysql> INSERT INTO t4 () VALUES(DEFAULT);
        mysql> SELECT BIN_TO_UUID(uid) AS uid FROM t4;
        +--------------------------------------+
        | uid                                  |
        +--------------------------------------+
        | f1109174-94c9-11e8-971d-3bf1095aa633 |
        | f110cf9a-94c9-11e8-971d-3bf1095aa633 |
        +--------------------------------------+
    # ================================================================
        创建表时指定 DEFAULT 约束:
            CREATE TABLE `lession` (
                id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
                name varchar(32) default '',
                views int(11) NOT NULL default '0',
                created_at DATETIME
            );

        ALTER TABLE 时的 SQL DEFAULT 约束:
            当然可以使用modify来修改, 只不过用modify比较麻烦
            ALTER TABLE lession ALTER [column] views SET DEFAULT '1';

        删除 DEFAULT 约束:
            ALTER TABLE lesson ALTER views DROP DEFAULT;


    # ================================================================

# 3. unique唯一性约束
    PRIMARY KEY 约束会自动定义一个 UNIQUE 约束,
    或者说 PRIMARY KEY 是一种特殊的 UNIQUE 约束。

    但二者是有明显区别的:
    1. 每个表可以有多个 UNIQUE 约束,但只能有一个 PRIMARY KEY 约束
    2. unique可以为空,primary不能为空

    # =============================================================
        CREATE TABLE 时的 SQL UNIQUE 约束 :
            在创建表结构时,可以使用 UNIQUE 关键字给表添加 UNIQUE 约束
                CREATE TABLE lesson (
                    id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
                    name varchar(32) default '',
                    views int(11) NOT NULL default '0',
                    created_at DATETIME,
                    UNIQUE [KEY](name)
                );

                or

                create table t1(id int unique [key], depart char(5) unique [key]);
                    UNI  # 默认的名字就是字段的名字

            如果想要多加多列,可以在括号内添加列,并使用逗号 (,) 分隔
                CREATE TABLE lesson (
                    id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
                    name varchar(32) default '',
                    views int(11) NOT NULL default '0',
                    created_at DATETIME,
                    unique (name,id)
                );
                MUL # 多列的默认名字,貌似是左边第一个字段,或者通过show index from lesson \G查看

            如果还想给 UNIQUE 约束命名
            1. create table t1(
                id int, depart char(5),
                unique uq_id (id),
                unique uq_depart (depart)
            );

            2. create table t1(
                id int,
                depart char(5),
                -- constraint uq_name unique(id, depart)
                constraint unique uq_name (id, depart)
            );

            3. create table t1(
                id int,
                depart char(5),
                unique uq_name (id, depart)
            );  MUL

        ALTER TABLE 时的 SQL UNIQUE 约束:
            如果表已经被创建,而又想添加 UNIQUE 约束,可以使用 ALTER TABLE ADD 命令
                ALTER TABLE lesson ADD UNIQUE [KEY](name);
                alter table lesson modify name char unique key;
            当然了,我们的 UNIQUE 可以包含多列,添加方法就和建表时添加多列是同样的
                ALTER TABLE lesson ADD UNIQUE (id,name);
            如果还想给 UNIQUE 约束命名,可以使用 CONSTRAINT 关键字
                alter table lesson add unique names (name);
                ALTER TABLE lesson ADD [CONSTRAINT [uniq_lesson_name]] UNIQUE (id,name);

        删除 UNIQUE 约束:
            如果想要删除 UNIQUE 约束,可以使用 ALTER TABLE DROP 命令
                ALTER TABLE lesson DROP {INDEX | key} uniq_lesson_name;
    # =============================================================


    # 跟primary key 的区别就是unique 可以为空

    # not null + unique的化学反应
      create table t3(id int not null unique, name char(5));
        结果id为PRI,也就是主键

    # =============================================================

# 4. primary key主键
    primary key = not null + unique

    主键primary key是innodb存储引擎组织数据的依据,
    innodb称之为索引组织表,一张表中必须有且只有一个主键。

    如果创建表的时候没有明确指定primary key,那么innodb会搜索一个not null + unique的字段作为主键;
    如果没有符合条件的,则innodb会自动创建一个6 字节的 ROWID隐藏的字段作为主键,当然对我们没有什么意义。

        1. 主键必须包含唯一的值
        2. 主键列不能包含 NULL 值
        3. 每个表都应该有一个主键,并且每个表只能有一个主键

    # ================================================================================
        CREATE TABLE 时的 SQL PRIMARY KEY 约束:
            创建表时可以使用 PRIMARY KEY 关键字给表添加 PRIMARY KEY 约束,
            但要注意,添加的列必须设置为 NOT NULL;(v8版本,其实不用设置not null,因为primary key = not null + unique)
                CREATE TABLE lesson (
                    id int(11) NOT NULL [PRIMARY] KEY AUTO_INCREMENT,  -- primary可以不用写
                    name varchar(32) default '',
                    views int(11) NOT NULL default '0',
                    created_at DATETIME
                );

                or  特别注意:primary key的key关键字,不能少;这也是跟unique不同的地方

                create table pri_tb (id int, primary key(id));

            虽然一个表只能有一个 PRIMARY KEY,但一个 PRIMARY KEY 可以包含多个列,
            添加多个列可以使用 PRIMARY KEY,关键字,括号内添加多个列,多列之间用逗号分隔。
                CREATE TABLE lesson (
                    id int(11) NOT NULL AUTO_INCREMENT,
                    name varchar(32) default '',
                    views int(11) NOT NULL default '0',
                    created_at DATETIME,
                    PRIMARY KEY (id,name)
                );

            如果需要给 PRIMARY KEY 约束命名,可以使用 CONSTRAINT 关键字:
                1. create table t1(id int, name char(5), primary key pk_name (id));

                2. CREATE TABLE lesson (
                    id int(11) NOT NULL AUTO_INCREMENT,
                    name varchar(32) default '',
                    views int(11) NOT NULL default '0',
                    created_at DATETIME,
                    [CONSTRAINT [pk_lesson_id]] PRIMARY KEY (id,name)
                 );

            ALTER TABLE 时的 SQL PRIMARY KEY 约束:
                如果一个表已经创建,而又想给表添加 PRIMARY KEY 约束,可以使用 ALTER TABLE 命令
                    alter table t1 modify id int primary key;

                    ALTER TABLE lesson ADD PRIMARY KEY (id);

                    ALTER TABLE lesson ADD CONSTRAINT pk_lesson PRIMARY KEY (id,name);

            给 PRIMARY KEY 约束命名:
                如果想要给 PRIMARY KEY 约束命名,可以使用 ALTER TABLE CONSTRAINT 命令
                    ALTER TABLE lesson ADD CONSTRAINT pk_lesson_id PRIMARY KEY (id);

            删除 PRIMARY KEY 约束:
                ALTER TABLE lesson DROP PRIMARY KEY

    # ==================================================================================
# 5. auto_increment自增

# 前提:要想为该字段设置自增,就必须先设置一个键, 并且default不能和auto_increment同时出现
# 建议:建立一张表设置一个id字段并且为自增
       create table t1(id int primary key auto_increment);

# 注意:
     1. 将 0 存储到 AUTO_INCREMENT 列与存储 NULL 具有相同的效果,
        除非启用了 NO_AUTO_VALUE_ON_ZERO SQL 模式。

     2. AUTO_INCREMENT 序列以 1 开始而不是 0

     3. 在 MySQL 8.0 及以上的版本, AUTO_INCREMENT 列会自动添加一个隐式的 UNSIGNED 属性来保证插入的值非负。

     4. 整数或浮点数据类型可以添加附加属性 AUTO_INCREMENT。
        如果某个列添加了 AUTO_INCREMENT 属性,那么在插入数据的时候,
        如果不指定该列或者指定该列的值为 NULL or 0,
        那么 MySQL 会自动将该列的值设置为下一个序列值

     5. 在有自增和主键的情况下,想删除主键,得先删除自增再删除主键
        删除自增:alter table tb4 modify id int not null;


# 更改表的自增值:
# alter table t11 auto_increment=10;

# 创建表的时指定:
# create table t11(id int auto_increment, primary key(id) auto_increment=10;

# 查看自增列的步长和起始值:
show variables like 'auto_inc%'  默认看的是会话级别的

# 设置自增列的步长和起始值:
# 1. 基于会话级别:
# MySQL是基于会话的,每一次登录就是一个会话,自增值也不一样,本次操作只针对当前会话生效
# show session variables like 'auto_inc%';
# set session auto_increment_increment=10;  # 步长
# set session auto_increment_offset=2;  # 起始值/起始偏移量

# PS:起始值 <= 步长

# 2. 基于全局级别:(需要重新登录)
# show global variables like 'auto_inc%';
# set global auto_increment_increment=5;
# set global auto_increment_offset=3;
# 最好不要设置全局的自增

##########################################################

小坑:如果有自增列,insert插入数据的时候,values(xx),(xx),(xx),前两个都符合规则,最后一个不符合,
#     虽然数据不会插入成功,但是自增值已经改变,此时再插入数据values(xx),自增值变成了 4

################################################################

# 6. foreign key外键
# 就是在一张表中存储另一张表的唯一字段的值

# 前提:存储引擎为innodb,且被关联表的那个字段必须唯一(unique / primary key)

# 建议:在实际开发中,尽量不要在数据库中的表与表之间建议硬性的关系,造成了强耦合的问题,不方便日后的扩展
#       应在自己的应用程序级别建立逻辑上的联系, 所以开发团队要有共识。

# 作用:
#     1. FOREIGN KEY 约束用于预防破坏表之间连接的行为
#     2. FOREIGN KEY 约束也能防止非法数据插入外键列,因为插入的数据必须是外键所指向的那个表字段中的值之一
#     两个作用简单明了

         1. 当删除一个 FOREIGN KEY 指向的主表 (lesson) 记录时,如果 FOREIGN KEY 所在的表 (lesson_views) 存在记录,那么会删除失败

         2. 当在 FOREIGN KEY 表 ( lesson_views ) 插入或更新一条记录,如果 FOREIGN KEY 指向的主表 ( lesson ) 不存在该记录,那么插入或者更新失败

    # ================================================================================
        CREATE TABLE 时的 SQL FOREIGN KEY 约束:
            给一个表添加 FOREIGN KEY 约束可以使用 FOREIGN KEY 关键字
                CREATE TABLE lesson_views (
                    uniq bigint(20) primary key NOT NULL default '0' ,
                    lesson_name varchar(32) default '',
                    lesson_id int(11) default '0',
                    date_at  int(11) NOT NULL default '0',
                    views int(11) NOT NULL default '0',
                    FOREIGN KEY (lesson_id) REFERENCES lesson(id)
                );

        给 FOREIGN KEY 命名
            如果想要给 FOREIGN KEY 约束命名,可以使用 CONSTRAINT 关键字
                create table fk_tb(
                fid int auto_increment not null primary key,
                name char (5),
                main_id int,
                foreign key fk_name (main_id) references main_tb(id)
                );

                注意: 外键的类型 必须 跟关联的表的字段类型要一致, 最好后面的宽度也一样.

                CREATE TABLE lesson_views (
                    uniq bigint(20) primary key NOT NULL default '0' ,
                    lesson_name varchar(32) default '',
                    lesson_id int(11) default '0',
                    date_at  int(11) NOT NULL default '0',
                    views int(11) NOT NULL default '0',
                    CONSTRAINT fk_lesson_id FOREIGN KEY (lesson_id) REFERENCES lesson(id)
                );

        ALTER TABLE 时的 SQL FOREIGN KEY 约束:
            如果一个表已经被创建,我们仍然可以使用 ALTER TABLE FOREIGN KEY 来添加外键约束
                ALTER TABLE lesson_views ADD FOREIGN KEY (lesson_id) REFERENCES lesson(id);

            如果还想给 FOREIGN KEY 约束命名,则可以像下面这样使用
                ALTER TABLE lesson_views ADD CONSTRAINT fk_lesson_id FOREIGN KEY (lesson_id) REFERENCES lesson(id);

                # 默认的名字: 表名 + _ + ibfk_1
                # 表名, 小写
                # ib, innodb
                # fk, foreign key
                # _1, 表中的第一个外键

        删除 FOREIGN KEY 约束:
            如果想要删除一个已经命名的 FOREIGN KEY 约束,可以使用 DROP 关键字
                ALTER TABLE lesson_views DROP FOREIGN KEY fk_lesson_id;

    # ================================================================================
        # 创建步骤:
        # 1. 首先要创建被关联的表:
             create table department(
                id int auto_increment primary key,
                name char(10),
                comment char(50)
            ) engine = innodb auto_increment = 1 default charset = utf8;

        # 2. 再创建主动关联的表:
             create table employee(
                id int auto_increment,
                name char(5),
                dep_id int,
                primary key(id),
                constraint fk_name foreign key(dep_id) references department(id)
            );

        # 插入数据步骤:
        # 1. 首先往被关联的表插入数据
             insert into department(name, comment) values('技术', '技术能力有限部门'),
                                                         ('销售', '销售能力不足部门'),
                                                         ('财务', '花钱特别多部门');
        # 2. 再往主动关联的表插入数据
             insert into employee(name, dep_id) values('alex', 1),
                                                      ('egon', 1),
                                                      ('taiba', 1),
                                                      ('peiqi', 2),
                                                      ('eva-j', 3);
        # 删除步骤:(不作特殊处理)(说明:这是在fk所在的表存在该记录时,才要先删主动关联表,再删被关联表)
        # 1. 首先删除主动关联的表的数据
             delete from employee where id = 1;  (id为1的都要删干净)
        # 2. 再删除被关联表的数据
             delete from department where id = 1;

        # 如果说我想更改被关联表的数据,同时,主动关联的表的数据也随之发生变化:
        # (更新的时候,同步更新;删除的时候,同步删除)
        # 创建主动关联表的时候需要加额外的参数:
             create table employee(
                id int auto_increment,
                name char(10),
                dep_id int,
                primary key(id),
                constraint fk_name foreign key(dep_id) references department(id)
                on delete cascade
                on update cascade
            );

        # 例如下拉框中的信息,就可以使用外键
        # 可以将多个列组合成一个外键,前提是此表中要设置自增还有主键(在v8中, 我试了一下, 并不用),
        并且与另一个表的类型要一致,要跟另一个表的主键挂钩
        # 除此之外另一个表的主键是多列组合的,
        # 外键的名字要唯一,不能与同一个数据库中的其他表的外键名字重复

    # ========================================================================================

# 7. check约束
#    CHECK 约束用于限制列中的值的范围

    CHECK 约束既可以用于某一列也可以用于某张表:

    1. 如果对单个列定义 CHECK 约束,那么该列只允许特定的值
    2. 如果对一个表定义 CHECK 约束,那么此约束会基于行中其他列的值在特定的列中对值进行限制

    # ===========================================================================================
        CREATE TABLE 添加 CHECK 约束:
            创建表结构时可以使用 CHECK 关键字给表或者字段添加 CHECK 约束
            例如我们在创建 lesson 表时可以给 id 字段加上一个大于 0 的 约束

                CREATE TABLE lesson (
                    id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
                    name varchar(32) default '',
                    views int(11) NOT NULL default '0',
                    created_at DATETIME,
                    CHECK ( id>0 )
                );

        多个字段添加约束:
            如果想给一个表中多个字段添加约束,直接在 CHECK 关键字后的括号内添加,每个约束使用 AND 连接
                CREATE TABLE lesson (
                    id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
                    name varchar(32) default '',
                    views int(11) NOT NULL default '0',
                    created_at DATETIME,
                    CHECK ( id>0 AND views >= 0 );
                );

        给 CHECK 约束命名:
            CREATE TABLE lesson (
                id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
                name varchar(32) default '',
                views int(11) NOT NULL default '0',
                created_at DATETIME,
                CONSTRAINT chk_lesson_id CHECK ( id>0 )
            );

        默认的名字: 表名 + _ + chk_1

        ALTER TABLE 时的 SQL CHECK 约束:
            如果表已经被创建,我们可以使用 ALTER TABLE ADD CHECK 添加约束
                ALTER TABLE lesson ADD CHECK (id>0);
                并不能alter table lesson add check 名字 (id > 0);
            如果还想要命名 CHECK 约束,并定义多个列的 CHECK 约束
                ALTER TABLE lesson ADD CONSTRAINT chk_lesson CHECK (id>0 AND views >= 0);

        删除 CHECK 约束:
            ALTER TABLE lesson DROP CHECK chk_lesson_id


        最佳实战:
            虽然各个数据库系统都可以添加 CHECK 约束,但我们不推荐你这么做,因为这会影响数据插入和更新的速度

    # ============================================================================================


# 表与表之间的关系:
# 分析步骤:
# 1. 先站在左表的角度分析:
#    左表的多条记录是否可以对应右表的一条记录,如果是,则需要在左表新建一个foreign key 字段关联右表的一个唯一字段(通常是id)
# 2. 再站在右表的角度分析:
#    右表的多条记录是否可以对应左表的一条记录,如果是,则需要在右表新建一个foreign key 字段关联左表的一个唯一字段(通常是id)

# 总结:
# 多对一 / 一对多:
# 1. 如果只有步骤1成立,则表明是左表多对一右表
# 2. 如果只有步骤2成立,则表明是右表多对一左表
# 关联方式:foreign key即可

create table book(
        id int primary key auto_increment,
        name varchar(20),
        press_id int not null,
        foreign key(press_id) references press(id)
        on delete cascade
        on update cascade
);
# 在条件为 左表的多条记录可以对应右表的一条记录,反之不成立的情况下,则表明是左表多对一右表,需要在左表新建一个foreign key字段关联右表的一个唯一字段
# 上述将条件反过来的话,则表明是右表多对一左表,需要在右表新建一个foreign key字段关联左表的一个唯一字段

# 多对多:
# 如果步骤1和2同时成立,则证明这两张表示一个双向的多对一,
  即多对多,需要定义一个存放二者关系的关系表
# 关联方式:foreign key + 一张新的表         (个人看例子得出:根据情况加联合主键)

# 新的表:
create table author2book(
        id integer not null unique auto_increment,
        author_id int not null,
        book_id int not null,
        constraint fk_author foreign key(author_id)
        references author(id)
        on delete cascade
        on update cascade,
        constraint fk_book foreign key(book_id)
        references book(id)
        on delete cascade
        on update cascade,
        primary key(author_id, book_id)
);

# 左表的多条记录可以对应右表的一条记录, 右表的多条记录也可以对应左表的一条记录,
# 则证明这两张表表示的是一个双向的多对一, 即多对多, 需要定义一张新的表来存放他们二者之间的关系
# 关联方式: foreign key + 一张新的表


# 一对一:
    如果1和2都不成立,而是左表的一条记录唯一对应右表的一条记录,反之亦然。
    这种情况很简单,就是在左表foreign key右表的基础上,将左表的外键字段设置成unique即可
# (个人心得:问题就在于在哪一方设置foreign key,我觉得就是在需要填数据的一方)
# 关联方式:foreign key + unique

create table student(
        id int primary key auto_increment,
        class_name varchar(20) not null,
        customer_id int,
        unique(customer_id),
        constraint foreign key(customer_id)
        references customer(id)
        on delete cascade
        on update cascade
);

#####################################################################################################

# 单表查询:
语法:
SELECT distinct 字段1, 字段2... FROM 表名
                               WHERE 条件
                               GROUP BY field
                               HAVING 筛选
                               ORDER BY field
                               LIMIT 限制条数;

优先级:
    from
    where
    group by
    having
    select
    distinct
    order by
    limit
1. 找到表:from

2. 拿着where指定的约束条件,去文件/表中取出一条条记录

3. 将取出的一条条记录进行分组group by,如果没有group by,则整体作为一组

4. 将分组的结果进行having过滤

5. 执行select

6. distinct去重

7. 将结果按条件排序:order by

8. limit限制结果的显示条数


# DISTINCT: 放在字段的前面,对其后所有的字段都生效,记录重复的只显示一次(记录去重)
# distinct 效率不高,如果能用其他方式实现,首选其他
    # =============================================================
        MySQL DISTINCT 的作用:
            排除重复的行
            (根据参数所指定的列排除重复的行)

        DISTINCT 有带括号和没括号的两种用法,这两种是如何指定参数的呢 ?
        我们使用过程中发现

        1. 如果跟其它函数结合使用,那么只会使用小括号内的参数
        2. 否则,那么 DISTINCT 关键字后的所有列都是参数

        MySQL DISTINCT 的位置:
        千万不要认为 DISTINCT 只能放在开头,也不要认为 DISTINCT 可以放在任意位置。

        1. 单独的 DISTINCT 关键字只能放在开头,放在其它位置会报错

        2. 如果是配合其它的函数使用,比如 COUNT(), 应该是其它函数可以任意位置时,DISTINCT 也可以任意位置

        DISTINCT 中的小括号 ():
            SQL 解析器会忽略 DISTINCT 关键字后面的小括号,而把 DISTINCT 关键字后面的所有列都作为唯一条件

        SQL DISTINCT 的基本用法:
            在日常使用 DISTINCT 关键字时,一般有以下几种
                ###注意: 请留意每种的列的数量###

            1. 单独获取某一列不重复的值
                这种情况下,有无小括号的结果是一样的
                SELECT DISTINCT user FROM fruits;
                SELECT DISTINCT(user) FROM fruits;

            2. 单独获取某一列不重复值的数量
                SELECT COUNT(DISTINCT(user)) FROM fruits;

            3. 以多列作为条件获取不同的值
                一定要记住,当你使用多列时,并不仅仅时使用小括号内的列,而是全部列
                SELECT DISTINCT(user),fruit FROM fruits;
    # ============================================================

# 计算字段,可以进行四则运算
    # ======================================================================
     字段一般是计算字段:
     例如:
         select 3 * 2;
         select 3 * 2 as res;
         SELECT name, salary*12 Annual_salary FROM employee;
         select null * 1; -- null
    # ========================================================================

# where子句:
    # =======================================================================
       WHERE 子句要在 FROM 子句之后给出
       文本字段 vs 数值字段
           1. SQL 使用单引号来环绕文本值
              虽然大部分数据库系统也接受双引号,但我们极力反对使用双引号

           2. 如果是数值字段,请不要使用引号
              虽然使用数值字段也可以使用单引号,但数据库系统要经过一次数据类型转换,增加了数据库系统的开销

        # where子句中的运算符:
            运算符	            描述
            =	                等于
            <>	                不等于
            >	                大于
            <	                小于
            >=	                大于等于
            <=	                小于等于
            BETWEEN	            在某个范围内
            LIKE	            搜索某种模式
            IN	                指定针对某个列的多个可能值

            ##  在 SQL 的一些版本中,<>操作符可被写成 !=  ##

        # > / >= / < / <= / != / <> / = / <=> (<=>是MYSQL特有的)
                安全等于<=>
                1.可作为普通运算符的=
                2.也可以用于判断是否是NULL 如:where salary is NULL/(is not NULL) ->where salary <=> NULL

        # between ... and ... / not between .... and ....
            # =====================================================================
                SQL BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期
                注意

                不同的数据库中,BETWEEN 操作符会产生不同的结果

                在某些数据库中,BETWEEN 选取介于两个值之间但不包括两个测试值的字段
                在某些数据库中,BETWEEN 选取介于两个值之间且包括两个测试值的字段   -----> MySQL属于这种
                在某些数据库中,BETWEEN 选取介于两个值之间且包括第一个测试值但不包括最后一个测试值的字段
                因此,请检查你的数据库系统是如何处理 BETWEEN 操作符

                例子:
                    1. 选取 id 介于 1 和 3 之间的所有课程
                        SELECT * FROM lesson WHERE id BETWEEN 1 AND 3;
                    2. 选取 id 不在 1 和 3 之间的所有课程
                        SELECT * FROM lesson WHERE id NOT BETWEEN 1 AND 3;

                    在 VARCHAR 等文本类型上使用 BETWEEN 操作符
                    1. 选取 name 以介于 'O' 和 'S' 之间字母开始的所有课程
                        SELECT * FROM lesson WHERE name BETWEEN 'O' AND 'S';
                    2. 选取 name 不介于 'O' 和 'S' 之间字母开始的所有课程
                        SELECT * FROM lesson WHERE name NOT BETWEEN 'O' AND 'S';

                    在 DATETIME 等日期类型列上使用 BETWEEN 操作符
                    1. 选取 created_at 介于 2017-04-18 16:03:32 和 2017-05-01 06:16:14 之间的数据
                        SELECT * FROM lesson WHERE created_at BETWEEN '2017-04-18 16:03:32' AND '2017-05-01 06:16:14';

                最佳实战:
                一般情况下,我们不推荐使用 BETWEEN
                为什么呢?
                    因为 BETWEEN 并不是所有开发者都熟悉,而且不同数据库实现有不一样的实现
                那我们可以用什么代替呢?
                    我们可以用 > 或 < 代替,比如下面的 SQL 语句选取 id 介于 1 和 3 之间的所有课程
                    SELECT * FROM lesson WHERE id >=1 AND id <=3;
            # ==========================================================================

        # not > and > or

        # in / not in
            # =========================================================
                SQL IN 操作符用于在 WHERE 子句中限制一列只能包含有限个值
                例如:
                    SELECT * FROM lesson WHERE id IN (1,3);

                    where id in (select id from t11);
            # ===========================================================

        # is / is not null (判断某个字段是否为NULL,不能用等号,需要用IS)

        # like / not like

        # rlike / not rlike

        # regexp / not regexp
    # =============================================================================
#####################################################################################

    #1:子查询是将一个查询语句嵌套在另一个查询语句中。
    #2:内层查询语句的查询结果,可以为外层查询语句提供查询条件。
    #3:子查询中可以包含:IN、NOT IN、ANY、ALL、EXISTS 和 NOT EXISTS等关键字
    #4:还可以包含比较运算符:= 、 !=、> 、<等

    EXISTS关字键字表示存在。在使用EXISTS关键字时,内层查询语句不返回查询的记录。
    而是返回一个真假值。True或False
    当返回True时,外层查询语句将进行查询;当返回值为False时,外层查询语句不进行查询

    drop table if exists department;

    select * from employee where exists (select id from department where id=204);

    select * from employee where not exists (select id from department where id=204);

######################################################################################
# ==================================================================
MySQL在Linux下数据库名、表名、列名、别名大小写规则是这样的:

1、数据库名与表名是严格区分大小写的;

2、表的别名是严格区分大小写的;

3、列名与列的别名在所有的情况下均是忽略大小写的;

4、变量名也是严格区分大小写的;

MySQL在Windows下都不区分大小写!!!

Mysql默认的字符检索策略:
*_bin: 表示的是binary case sensitive collation,也就是说是区分大小写的
*_cs: case sensitive collation,区分大小写  注意:在Mysql5.6.10版本中,不支持utf8_genral_cs!!!!
*_ci: case insensitive collation,不区分大小写

1. 创建表时,直接设置表的collate属性为utf8mb4_general_cs或者utf8mb4_bin;

2. 如果已经创建表,则直接表的collate

3. 修改字段, 添加binary约束
ALTER TABLE TABLENAME MODIFY COLUMN COLUMNNAME VARCHAR(50) BINARY CHARACTER SET utf8 COLLATE
utf8_bin DEFAULT NULL;

4. 对某字段手动指定binary约束
    CREATE TABLE NAME(
    name VARCHAR(10) BINARY
    );

5. 在每一个条件前加上binary关键字
select * from user where binary username = 'admin' and binary password = 'admin';

6. 将参数以binary('')包围
select * from user where username = binary('clip');

# =======================================================================

# like子句:
    # =====================================================================
        SQL 中的 LIKE 子句用于在 WHERE 子句中搜索列中的指定模式
            SELECT column_name(s) FROM table_name WHERE column_name LIKE pattern;
        pattern 是一个合法的模式字符串,有很多种 模式,但最常用的也是最容易记住的就是百分号 ( % ) 可以代替任意字符

        SQL LIKE 操作符:
        1. 如果我们不使用任何通配符,那么 LIKE 的效果相当于 = 操作符
            SELECT * FROM lession WHERE name LIKE 'Python 基础教程';

        2. 因为百分号 ( % ) 可以代替任何任意数量的字符,所以下面的 SQL 语句选取 name 以 S 开头的课程
            SELECT * FROM lession WHERE name LIKE 'S%';

        3. 下面的 SQL 语句选取 name 以字符串 "教程" 结尾的所有课程
            SELECT * FROM lession WHERE name LIKE '%教程';

        4. 下面的 SQL 语句选取 name 包含 y 字母的课程
            SELECT * FROM lession WHERE name LIKE '%y%';

        5. 通过使用 NOT 关键字,我们可以选取不匹配模式的记录
            下面的 SQL 语句选取 name 不包含 "y" 的所有课程
                SELECT * FROM lession WHERE name NOT LIKE '%y%';
    # =====================================================================

# 通配符: 特别注意(默认情况下, windows下的MySQL什么都不分大小写, 包括, 查询参数)
    # ======================================================================
        SQL 通配符:
            SQL 中,通配符与 SQL LIKE 操作符一起使用,可用于替代字符串中的任何其他字符

            SQL 中规定可以使用以下通配符:

            通配符	                    描述
            %	                        替代 0 个或多个字符
            _	                        替代一个字符
            [charlist]	                字符列中的任何单一字符
            [^charlist]或[!charlist]	不在字符列中的任何单一字符

        使用 SQL % 通配符:
            SELECT * FROM lession WHERE name LIKE 'S%';

        使用 SQL _ 通配符:
            选取 name 以一个任意字符开始,然后是 "ython" 的所有课程
                SELECT * FROM lession WHERE name LIKE '_ython%';

            选取 name 以 "S" 开始,然后是一个任意字符,然后是 "a",然后是一个任意字符,然后是 "a" 的所有课程
                SELECT * FROM lession WHERE name LIKE 'S_a_a%';

        使用 SQL [charlist] 通配符:
            MySQL 中使用 REGEXP 或 NOT REGEXP 运算符 (或 RLIKE 和 NOT RLIKE) 来操作正则表达式
                1. 选取 name 以 "P"、"S" 开始的课程
                    SELECT * FROM lession WHERE name REGEXP '^[PS]';

            # 使用正则进行查询:
                SELECT * FROM employee WHERE name REGEXP '^ale';

                SELECT * FROM employee WHERE name REGEXP 'on$';

                SELECT * FROM employee WHERE name REGEXP 'm{2}';

        其它:
        # where name like 'a%'  # 以a开头的字符串,%代表0个或者多个字符
        # where name like 'a_'  # _代表一个字符
        # 使用通配符进行检索是很慢的,如果能用其他操作符达到相同的效果,应该首选操作符;
        # 并且不要把通配符放在模式的第一个,这样做的话,效率很低

        # 特别的:
        如果字段是整型,也能使用like
    # =======================================================================


# group by 子句:
    # =======================================================================================================
        # 分组指的是:将所有记录按照某个相同字段进行归类,比如针对员工信息表的职位分组,或者按照性别进行分组等
        # 小窍门:‘每’这个字后面的字段,就是我们分组的依据
        # 提醒:
              可以按照任意字段分组,但是分组完毕后,比如group by post,只能查看post字段,
              如果想查看组内信息,需要借助于聚合函数

        ##############################################################################################

        1. 查看MySQL 5.7默认的sql_mode
           select @@global.sql_mode;


           在ONLY_FULL_GROUP_BY模式下,
           target list中的值要么是来自于聚集函数的结果,
           要么是来自于group by list中的表达式的值。

        #设置sql_mole如下操作(我们可以去掉ONLY_FULL_GROUP_BY模式):
        set global sql_mode='STRICT_TRANS_TABLES,
                            NO_ZERO_IN_DATE,
                            NO_ZERO_DATE,
                            ERROR_FOR_DIVISION_BY_ZERO,
                            NO_AUTO_CREATE_USER,
                            NO_ENGINE_SUBSTITUTION';

        执行select * from employee group by post;
        #由于没有设置ONLY_FULL_GROUP_BY,于是也可以有结果,默认都是组内的第一条记录,但其实这是没有意义的

        set global sql_mode='ONLY_FULL_GROUP_BY';
        #设置成功后,一定要退出,然后重新登录方可生效

        #######################################################################################################

        范例:
            统计各个课程的总访问量
               SELECT lesson_name, SUM(views) FROM lesson_views GROUP BY lesson_name;

                GROUP BY X, Y意思是将所有具有相同X字段值和Y字段值的记录放到一个分组里。

        SQL GROUP BY 多表连接
            SELECT lesson.name,SUM(lesson_views.views)
                FROM lesson,lesson_views
                WHERE lesson.id=lesson_views.lesson_id
                GROUP BY lesson.name;


        ############################################

        强调:
        (在单表下)如果我们用unique的字段作为分组的依据,则每一条记录自成一组,这种分组没有意义
        多条记录之间的某个字段值相同,该字段通常用来作为分组的依据

        ##############################################
        # select num from t11 group by num;  根据num把相同的聚合为一组, 分组就是分类

        # GROUP BY关键字和GROUP_CONCAT()函数一起使用
        # 将一组中的字段值, 拼接在一起显示出来.
        SELECT post,GROUP_CONCAT(name) as emp_members FROM employee GROUP BY post;

        # ===================================================================================

# having 子句:
    # ================================================================================
        SQL 中的 HAVING 子句用于筛选分组 ( GROUP BY ) 后的各组数据,相当于 SELECT 语句中的 WHERE 语句

        HAVING 子句一般跟在 GROUP BY 子句后面

        可以看作是where做了第一次筛选,having进行第二次筛选

        SQL HAVING 范例:
            选择总访问量在 100 以内的课程
            SELECT lesson_name, SUM(views) as total_views
                FROM lesson_views
                GROUP BY lesson_name
                HAVING total_views < 100;   # 别名

            又学习了:

            select lesson_name, sum(views) as total_views
                from lesson_views
                group by lesson_name
                having sum(views) < 100;
        #####################################################################################
        HAVINGWHERE不一样的地方在于!!!!!!

        #!!!执行优先级从高到低:where > group by > having
        #1. Where 发生在分组group by之前,因而Where中可以有任意字段,但是绝对不能使用聚合函数。

        #2. Having发生在分组group by之后,因而Having中可以使用分组的字段,无法直接取到其他字段,可以使用聚合函数

        可以看作where做了第一次筛选,having进行第二次筛选
        ############################################################################################

        # select count(id), num from t11 group by num having max(id) > 5;
        # 反正分组最后只会显示一组,
        # 聚合后,在having 看同一组中的id哪个大,然后和5判断,如果大于5,执行count(id), num
        # 不大于,这一组就不会执行后面的语句。

    # ================================================================================

# 别名as:
    # =================================================================================================
        1. SQL 中允许临时给表名或列名称指定别名,创建别名是为了让列名称的可读性更强

        2. 别名只是当前 SQL 语句执行过程中临时的改变,在数据库中实际的表的名称不会改变

        3. SQL 中创建别名使用 AS 关键字, 可以省略

        4. 如果列名称包含空格,要求使用引号, 最好使用单引号

        5. 在MySQL中,可以在ORDER BYGROUP BYHAVING子句中使用列别名来引用该列

        6. 不能在WHERE子句中使用列别名。(使用表别名是允许的)
           原因是当MySQL评估求值WHERE子句时,SELECT子句中指定的列的值可能尚未确定。



        列的 SQL 别名
            SELECT column_name AS alias_name FROM table_name;

                SELECT lesson_id as lid, lesson_name as name, date_at, views FROM lesson_views;

        表的别名
            SELECT column_name(s) FROM table_name AS alias_name;

                SELECT * FROM lesson_views as lv WHERE lv.lesson_id = 2;

        最佳实战:
            如果出现以下几种情况之一,使用别名很有用:

            1. 在查询中涉及超过一个表
            2. 在查询中使用了函数
            3. 列名称很长或者可读性差
            4. 需要把两个列或者多个列结合在一起
    # ===================================================================================================

# SQL 函数
    # ===================================================================================================

        SQL 函数
        任何一个数据库系统都内置了数量相当可观的又非常实用的小函数

        这些函数可以根据实现功能的不同划分为不同的类,当然,除了很明显的日期时间和字符串函数两大类外

        我们还可以把这些函数归纳为两大类: Aggregate 函数 和 Scalar 函数

        这两个英文单词,字面理解它的意思即可

        SQL Aggregate 函数
            SQL Aggregate 函数用于计算从列中取得的值,并返回一个单一的值

        常用的 Aggregate 函数(聚合函数)有:

            函数	    说明
            AVG()	    返回平均值
            COUNT()	    返回行数
            MAX()	    返回最大值
            MIN()	    返回最小值
            SUM()	    返回总和

        # 聚合函数:max() / min() / sum() / avg() / count()

            avg() 忽略列值为NULL的行

            count()
                根据参数的不同,COUNT() 大致有三种用法

                1. COUNT ( column_name )
                    COUNT(column_name) 函数返回指定列的值的数目,NULL 值除外

                    SELECT COUNT(column_name) FROM table_name;

                2. COUNT(*)
                    COUNT(*) 函数返回表中的记录数,包括 NULLSELECT COUNT(*) FROM table_name;

                3. COUNT(DISTINCT column_name )
                    COUNT(DISTINCT column_name) 函数返回指定列的不同值的数目

                    SELECT COUNT(DISTINCT column_name) FROM table_name;

                # 统计一个表有多少行:
                # select count(*) from t11;  # 不写group的话,默认这个表整体为一组
                # count(1) / count(主键) 效率高
                # count(* | 1) 统计行数,不会忽略列值为NULL的行
                # count(column) 会忽略列值为NULL的行

        # max() 会忽略列值为NULL的行
        # min() 会忽略列值为NULL的行
        # sum(column) or sum(column * column) 会忽略列值为NULL的行

        # 聚集函数的默认行为是对所有行进行计算,也就是ALL
        # 如果想改变这个行为,只想对不同的值进行计算,用DISTINCT
        # DISTINCT 不能用于count(*)
        # 虽然也可以用于max / min, 但是最后的结果与不用distinct是相同的。
        # distinct 只能用于列,不能用于表达式


        SQL Scalar 函数
        SQL Scalar 函数基于输入值,返回一个单一的值

        常用的 Scalar 函数有:

            函数	                        说明
            UCASE()	                        将某个字段转换为大写
            LCASE()	                        将某个字段转换为小写
            MID()	                        从某个文本字段提取字符,MySQL 中使用
                SELECT MID( column_name ,start[,length]) FROM table_name;
                参数	            描述
                column_name	        必需。要提取字符的字段
                start	            必需。设置开始位置 ( 起始值是 1 ),这个一定要注意
                length	            可选。要返回的字符数。如果省略,则 MID() 函数返回剩余文本

            SubString(fieldname,1end)	从某个文本字段提取字符
            LENGTH()	                    返回某个文本字段的长度
                LENGTH() 返回的是数据库服务器编码下的字符串长度,
                如果数据库服务器的编码是 UTF-8,那么 '中国' 将返回 6
            ROUND()	                        对某个数值字段进行指定小数位数的四舍五入
                ROUND( column_name ,decimals)
                参数	        描述
                column_name	    必需。要舍入的数值或字段
                decimals	    可选。设置要返回的小数位数。默认为 0
            NOW()	                        返回当前的系统日期和时间

            范例:
                select ucase('abc');
                select lcase('ABC');
                SELECT MID('www.twle.cn',3,4);  --> w.tw
                SELECT LENGTH('www.twle.cn 基础教程');
                INSERT INTO lession(name, views, created_at) VALUES('SQL 基础教程', 0, NOW());

            说明:
                一般情况下我们不推荐使用UCASE LCASE MID LENGTH函数,
                因为应用的瓶颈一般在数据库,这些显然会降低数据库的并发能力
    # ==================================================================================================

# order by 子句
    # ======================================================================================================
        SQL ORDER BY 关键字用于对结果集按照一个列或者多个列进行排序
            SELECT column_name(s) FROM table_name ORDER BY column_name [ASC|DESC] [, column_name [ASC|DESC]];

            SQL 排序有几个重点:

            1. ORDER BY 关键字默认按照升序对记录进行排序
               如需要按照降序对记录进行排序,可以使用 DESC 关键字

            2. 如果没有 ORDER BY 语句,结果集会以 主键 升序排序
               一般情况下,主键都是 id

        # 排序:
        # order by id desc; 降序
        # order by id asc; 升序

        多列排序,那么每个排序字段使用逗号 ( , ) 分隔
            # order by id desc, age asc;  # 根据id降序排列,如果有相同的,按照age升序排列
            按照排序字段从左往右 ( id, age )
                如果第一个排序字段 ( id ) 的值不一样,则按照第一个排序字段的值排序
                如果第一个排序字段 ( id ) 的值一样,则按照第二个排序字段 ( age ) 的值排序
                以此类推

        # order by 可以使用别名进行排序
        # 通常ORDER BY使用的列将是为显示而显示的列,但不一定总是这样,可以用非检索的列排序。
        # 对某一列排序,影响的是整张表
    # =======================================================================================================

# limit 子句:
    # ========================================================================================================
        用于限制返回结果集的条数

        # select * from t11 limit 5;  # 取前5行, # 默认初始位置为0
        # select * from t11 limit 5,5;  # 从索引为5的行开始, 然后包含这一条在内往后查5条
        # select * from t11 limit 5 offset 4;  等价 limit 5,4;

        因为LIMIT的机制是每次都是从头开始扫描,如果需要从第60万行开始,读取3条数据
        就需要先扫描定位到60万行,然后再进行读取,而扫描的过程是一个非常低效的过程。
        所以,对于大数据处理时,是非常有必要在应用层建立一定的缓存机制
        (现在的大数据处理,大都使用缓存)

    # ========================================================================================================

###############################################################################################################

# join 子句:
    # ==========================================================================================================
        # join 连表:
        # SQL 表连接 用于把来自两个或多个表的行结合起来
          表连接有啥作用呢?
          当我们的数据横跨 2 个或 2 个以上的表时,我们就要考虑要怎么排列这两个表中的数据,好让它们组成一个大的表
          数据库系统把这种多个表数据的排列方式叫做表连接 ( SQL JOIN )

        # SQL JOIN 子句通过两个或两个以上的表的共有字段,将这些表的行结合起来

        # 连表太多会影响性能

        # 1. 交叉联结:
        #           select * from tb1, tb2;
        #           多张表直接连生成笛卡尔积

        # 2. 内联结:只连接匹配的行
        #          找两张表共有的部分,相当于利用条件从笛卡尔积结果中筛选出了正确的结果
                   select * from tb1 [inner] join tb2 on tb1.id = tb2.id;
                   等同于
                   select * from tb1, tb2 where tb1.id = tb2.id;
                   这种形式为内联结(不显示值为Null的行)

        # 3. 外联结之左联结:优先显示左表全部记录
                   select * from tb1 left [outer] join tb2 on tb1.id = tb2.id;
        #          #以左表为准,即找出所有员工信息,当然包括没有部门的员工
        #           本质就是:在内连接的基础上增加左边有右边没有的结果

        # 4. 外联结之右联结:优先显示右表全部记录
        #         select * from tb1 right [outer] join tb2 on tb1.id = tb2.id;
        #         #以右表为准,即找出所有部门信息,包括没有员工的部门
        #          本质就是:在内连接的基础上增加右边有左边没有的结果

        # 5. 全外联结:显示左右两个表全部记录
                  在内连接的基础上增加左边有右边没有的和右边有左边没有的结果
        #         注意:mysql不支持全外连接 full JOIN
        #         强调:mysql可以使用UNION操作符间接实现全外连接

# UNION 操作符
    # =========================================================================================================
        SQL UNION 操作符合并两个或多个 SELECT 语句的结果

        UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名

        1. 不允许重复 union

            SELECT column_name(s) FROM table1 UNION SELECT column_name(s) FROM table2;

        2. 允许重复值 union all

            SELECT column_name(s) FROM table1 UNION ALL SELECT column_name(s) FROM table2;

        但这些 SELECT 语句 的结果集必须符合一定的要求:
            1. 每个 SELECT 语句必须拥有相同数量的列
            2. 列也必须拥有相似的数据类型
            3. 每个 SELECT 语句中的列的顺序必须相同


         union 上下联表,自动去重,all选项可保留重复的行
         select * from employee left join department on employee.dep_id = department.id
          union
         select * from employee right join department on employee.dep_id = department.id
          ;
    # ===========================================================================================================
##############################################################################################SELECT语句关键字的定义顺序

SELECT DISTINCT <select_list>
FROM <left_table>
<join_type> JOIN <right_table>
ON <join_condition>
WHERE <where_condition>
GROUP BY <group_by_list>
HAVING <having_condition>
ORDER BY <order_by_condition>
LIMIT <limit_number>;SELECT语句关键字的执行顺序

(7)     SELECT
(8)     DISTINCT <select_list>
(1)     FROM <left_table>
(3)     <join_type> JOIN <right_table>
(2)     ON <join_condition>
(4)     WHERE <where_condition>
(5)     GROUP BY <group_by_list>
(6)     HAVING <having_condition>
(9)     ORDER BY <order_by_condition>
(10)    LIMIT <limit_number>


###################################################################################

# 数据库的导入与导出:
# CMD终端运行:
# MySQL导出数据库(包括数据结构+数据):
# mysqldump -u root -p db-name > db_nickname_bak.sql

# MySQL导出数据库(只包括数据结构):
# mysqldump -u root -p -d db-name > db_nickname_bak.sql

# MySQL导出数据表(包括数据结构+数据):
# mysqldump -u root -p db-name tb-name1 tb-name2 ...  > tb_name_name_bak.sql

# MySQL导出数据表(只包括数据结构):
# mysqldump -u root -p -d db-name tb-name1 tb-name2 ...  > tb_name_name_bak.sql

# MySQL导入数据库:
# 1 进入MySQL命令行
# 2 创建一个数据库 db
# 3 进入db
# 4 执行source 路径(数据库备份文件的路径)(这条语句最后不加分号;)

# MySQL导入数据表:
# 1 进入CMD命令行
# 2 执行 mysql -uroot -p db_name < tb_bak.sql

########################################################################################

# 基于<角色>的权限管理 #

# 需求分析

#############################################################################

# 视图:
    # =====================================================================================
        SQL 视图 ( Views )
            其本质是【根据SQL语句获取动态的数据集,并为其命名】,
            用户使用时只需使用【名称】即可获取结果集,可以将该结果集当做表来使用。

        视图的特征:
            1. 视图总是显示最新的数据

            2. 每当用户查询视图时,数据库引擎通过使用视图的 SQL 语句重建数据

        # 视图:是虚拟的,数据来源于物理表。
        # 虽然在单表的情况下,可以修改视图,但是我们不应该这样做,视图改了,源数据也会跟着改。
        # 而且涉及到多张表的情况下,是根本无法进行修改的。

        SQL CREATE VIEW 创建视图:
            CREATE VIEW view_name AS SELECT column_name(s) FROM table_name WHERE condition;
                说明: AS 关键字后面的 SQL 语句可以是任何合法的 SQL SELECT 语句
                例如: create view view_one as (select student_id, num from score);


            从视图中创建视图:
                非常有意思的是,可以从一个视图中创建另一个视图:
                    CREATE VIEW lesson_all_views AS SELECT sum(views) as views FROM lession_total_view;

        查看当前数据库中所有的视图:
            视图在数据库中类似于表的存在,所以,可以使用 show tables; 语句查看所有的视图
                show tables;

        SQL 修改视图:
            很多人都会把这个翻译成 更新视图,我觉得吧,有点不妥,因为很容易和 更新表 联系起来
            修改视图的意思,其实只能修改 AS 后面的 SQL 查询语句
            如果要修改一个视图,可以使用 CREATE OR REPLACE VIEW 关键字

            1. CREATE OR REPLACE VIEW view_name AS SELECT column_name(s) FROM table_name WHERE condition

            2. alter view view_one as (select student_id, course_id, num from score);

        SQL 删除视图:
            DROP VIEW view_name;

            例如: drop view view_one;

    #!!!注意注意注意:
    #1. 使用视图以后就无需每次都重写子查询的sql,
        但是这么做效率并不高,还不如我们写子查询的效率高

    #2. 而且有一个致命的问题:视图是存放到数据库里的,如果我们程序中的sql过分依赖于数据库中存放的视图,
        那么意味着,一旦sql需要修改且涉及到视图的部分,则必须去数据库中进行修改,
        而通常在公司中数据库有专门的DBA负责,
        你要想完成修改,必须付出大量的沟通成本DBA可能才会帮你完成修改,极其地不方便

    # ==============================================================================================
###########################################################################

# 更改分隔符/定界符
# delimiter // : 表示SQL语句碰到 // 才结束。改完之后,记得改回来。

# 触发器:如果你想在对表进行增(insert)、删(delete)、改(update)操作的前(before)后(after)
#         触发某种特定的行为的时候,就可以使用触发器 (没有查select)

# 创建:
#       插入前
        CREATE TRIGGER tri_before_insert_tb1 BEFORE INSERT ON tb1 FOR EACH ROW
        BEGIN
            ...
        END

        # 插入后
        CREATE TRIGGER tri_after_insert_tb1 AFTER INSERT ON tb1 FOR EACH ROW
        BEGIN
            ...
        END

        # 删除前
        CREATE TRIGGER tri_before_delete_tb1 BEFORE DELETE ON tb1 FOR EACH ROW
        BEGIN
            ...
        END

        # 删除后
        CREATE TRIGGER tri_after_delete_tb1 AFTER DELETE ON tb1 FOR EACH ROW
        BEGIN
            ...
        END

        # 更新前
        CREATE TRIGGER tri_before_update_tb1 BEFORE UPDATE ON tb1 FOR EACH ROW
        BEGIN
            ...
        END

        # 更新后
        CREATE TRIGGER tri_after_update_tb1 AFTER UPDATE ON tb1 FOR EACH ROW
        BEGIN
            ...
        END


        delimiter //
        CREATE TRIGGER tri_after_insert_cmd AFTER INSERT ON cmd FOR EACH ROW
        BEGIN
            IF NEW.success = 'no' THEN #等值判断只有一个等号
                INSERT INTO errlog(err_cmd, err_time) VALUES(NEW.cmd, NEW.sub_time) ; #必须加分号
            END IF ; #必须加分号
        END//

        delimiter ;

        # insert 只有NEW
        # delete 只有OLD
        # update 有NEW、OLD

# 删除
    drop trigger tri_before_insert_cmd;

#############################################################################################

# 流程控制
# case when .. then ... else ... end

# CASE [test] WHEN[val1] THEN [result]...ELSE [default] END
    如果test和valN相等,则返回resultN,否则返回default

# if(isnull(), t, f)
    如果test是真,返回t;否则返回f
    例如: select lession_name, if(isnull(views), 1, 0) from lession_views;

# IFNULL(arg1,arg2)
    如果arg1不是空值,返回arg1,否则返回arg2
    例如: SELECT lession_name,IFNULL( views,0) + 1000  FROM lession_views;

# NULLIF(arg1,arg2)
    如果arg1=arg2返回NULL;否则返回arg1

# if ... then ... end if;
# if ... then ... else ... end if;
# if ... then ... elseif ... then ... else... end if;

# PS: while num < 10 do .... end while;
# PS: repeat ... until num >= 5 end repeat;

#########################################################################################

# 内置函数
user()
database()

bin()
hex()
oct()


soundex()
binary()  区分大小写

charset()
char_length() 以字符为单位
length() 以字节为单位

concat() 字符串拼接,如果有一个参数为NULL,则返回值为NULL
concat_ws() 可以指定连接符,忽略NULL,但不会忽略任何空字符串; concat_ws('-', 1, 2, 3)
group_concat()

cast()  例如: cast(num as char)
conv(num, c_sys, to_sys) 进制转换
format(n,d) 将数字n以'#,###,###.###'的字符串形式返回,以四舍五入的方式保留小数点后d位,若d为0,则表示结果没有小数部分

upper()  将文本的字母部分转化为大写
lower()  将文本的字母部分转化为小写
ucase()
lcase()

insert(str, pos, len, newstr)
# 第一个字符的索引为1
# 如果pos超过str的长度,则直接返回原str;
# 如果len超过str的长度,则从pos开始由newstr完全替换
# 如果newstr长度小于len,则多余的被删除
replace(str, oldstr, newstr)  返回str中的oldstr被newstr替换后的最终字符串。如果oldstr不在里面,返回原str

left(str, len)  返回从str开头开始的len长度字符串  left('abc', 2) --> ab
right(str, len)  返回从str末尾开始的len长度字符串 right('abc', 2) --> bc

mid()
substring(str, pos, len)  取str的pos位置开始返回len长度的字符串
    substring(str from pos for len)
    substring(str from -pos for len)
    substring(str, pos)
    substring(str from pos)
    substring(str from -pos)

# 第一个字符的索引为1
instr(str, substr) 返回substr在str中出现的第一个位置。若没有,返回0
locate(substr, str, pos)  返回substr在str中从pos开始的第一个索引位置,pos可以没有;如果没有,返回0

repeat(str, count) 返回count*str组成的字符串。如果count或者str为NULL,结果为NULL;如果count为0,结果为空字符串
space(n)  返回一个由n个空格组成的字符串
reverse(str)  返回倒序的str

ltrim()  剔除字符串左边的空白字符
rtrim()  剔除字符串右边的空白字符
trim()   剔除字符串两边的空白字符 =
trim(both ' ' from str)
trim(leading 'x' from str)
trim(trailing 'x' from str)

#############################################################################

# 数值处理函数
# sin() / cos() / tan() / abs() / sqrt() / exp() / pow() / pi() /
# log() / -- 默认以e为底; log(2, 4), 表示以2为底
# log10() /
# round(x, y) 机制跟python中的差不多,忽略5那个特性
# RAND()
  返回0到1内的随机值,可以通过提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。

################################################################################

########################################################################################

加密函数
    MD5()
        计算字符串str的MD5校验和
    PASSWORD(str)
        返回字符串str的加密版本,这个加密过程是不可逆转的,和UNIX密码加密过程使用不同的算法。

############################################################################################

# 获取日期时间
curdate() / current_date()  # 返回当前日期
curtime() / current_time()  # 返回时间
current_timestamp()
now()  # 返回当前日期和时间
    例子: create table employee(d date, t time, dt datetime);
            insert employee values(now(), now(), now());

# 提取
EXTRACT() 函数
    MySQL EXTRACT() 函数返回日期/时间的单独部分,比如年、月、日、小时、分钟
        EXTRACT(unit FROM date):
        参数	说明
        date	一个合法的日期表达式
        unit	返回值的类型

        unit 参数可以是以下值
        值	                    说明
        MICROSECOND	            毫秒
        SECONDMINUTEHOURDAY	                    天
        WEEK	                周
        MONTH	                月
        QUARTER	                季度
        YEAR	                年
        SECOND_MICROSECOND	    秒.毫秒
        MINUTE_MICROSECOND	    分..毫秒
        MINUTE_SECOND	        分.秒
        HOUR_MICROSECOND	    小时...毫秒
        HOUR_SECOND	            小时..秒
        HOUR_MINUTE	            小时.分
        DAY_MICROSECOND	        小时...毫秒
        DAY_SECOND	            小时..秒
        DAY_MINUTE	            小时.分钟
        DAY_HOUR	            小时
        YEAR_MONTH	            年.月
        忽略说明中的点号 (.)

        当 unit 为复合类型时,它就是把这些元数据简单的拼接在一起

DATE_ADD() 函数
    MySQL DATE_ADD() 函数用于向日期添加指定的时间间隔
    DATE_ADD(date,INTERVAL expr type)
    参数	说明
    date	合法的日期表达式
    expr	希望添加的时间间隔
    type	时间间隔的类型
    type 参数可选值如下
    值	                    说明
    MICROSECOND	            毫秒数
    SECOND	                秒数
    MINUTE	                分钟数
    HOUR	                小时数
    DAY	                    天数
    WEEK	                周数
    MONTH	                月数
    QUARTER	                季度数
    YEAR	                年数
    SECOND_MICROSECOND	    秒.豪秒
    MINUTE_MICROSECOND	    分.豪秒
    MINUTE_SECOND	        分.秒
    HOUR_MICROSECOND	    小时.豪秒
    HOUR_SECOND	            小时.秒
    HOUR_MINUTE	            小时.分
    DAY_MICROSECOND	        天.豪秒
    DAY_SECOND	            天.秒
    DAY_MINUTE	            天.分钟
    DAY_HOUR	            天.小时
    YEAR_MONTH	            年.月

    例如:
        1. SELECT DATE_ADD(NOW(),INTERVAL 30 DAY );

        2. 给当前时间加上 1天又1小时
           SELECT NOW(), DATE_ADD(NOW(),INTERVAL 1.1 DAY_HOUR );

DATE_SUB() 函数
    MySQL DATE_SUB() 函数从日期减去指定的时间间隔
    DATE_SUB(date,INTERVAL expr type)

DATEDIFF() 函数
    MySQL DATEDIFF() 函数返回两个日期之间的天数,准确的说,是返回两个日期时间午夜 ( 00:00:00 ) 的数目
    DATEDIFF(enddate,startdate)
        如果 enddate 大于 startdate ,那么返回的是正数,反之,返回的是负数
    参数说明
    参数	            说明
    enddate	            开始时间,必须是合法的日期或日期/时间表达式
    startdate	        结束时间,必须是合法的日期或日期/时间表达式

    例如:
        1. 假设我们要返回日期 2017-06-192017-05-18 之间的天数,
           SELECT DATEDIFF('2017-06-19','2017-05-18') AS days;

           如果我们把两个时间对调一下,那么返回的结果就是负数
           SELECT DATEDIFF('2017-05-18','2017-06-19') AS days;

year()
month()
day()
hour()
minute()
second()
microsecond()
date()
time()
week()  # 一年第几周
quarter()  # 季度
monthname()  # 返回当前月的英文名
dayname()
dayofyear()
dayofweek()  返回日期星期几的索引 (1 = Sunday, 2 = Monday, ., 7 = Saturday);这些索引值对应于ODBC标准。
dayofmonth()


# 日期时间格式化:(重点)
date_format(timestr, 日期时间占位符)
    参数	说明
    date	合法的日期
    format	日期/时间的输出格式

    format 参数可使用的元字符有
    元字符	                说明
    %Y	                    年,4%y	                    年,2%m	                    月,数值 ( 00-12 )
    %c	                    月,数值 ( 0 - 12)
    %d	                    月的天,数值 ( 00-31 )
    %e	                    月的天,数值 ( 0-31 )
    %D	                    带有英文后缀的月中的天

    %H	                    小时 ( 00-23 )
    %h	                    小时 ( 01-12 )
    %I	                    小时 ( 01-12 )
    %k	                    小时 ( 0-23 )
    %l	                    小时 ( 1-12 )
    %i	                    分钟,数值 ( 00-59 )
    %S	                    秒 ( 00-59 )
    %s	                    秒 ( 00-59 )
    %f	                    微秒
    %p	                    AM 或 PM
    %r	                    时间,12-小时 ( hh:mm:ss AM 或 PM )
    %T	                    时间, 24-小时 ( hh:mm:ss )

    %j	                    年的天 ( 001-366 )
    %w	                    周的天 ( 0=星期日, 6=星期六 )

    %a	                    缩写星期名
    %W	                    全称星期名
    %b	                    缩写月名
    %M	                    全称月名

    %U	                    周 ( 00-53 )星期日是一周的第一天
    %u	                    周 ( 00-53 )星期一是一周的第一天
    %V	                    周 ( 01-53 )星期日是一周的第一天,与 %X 使用
    %v	                    周 ( 01-53 )星期一是一周的第一天,与 %x 使用

    %X	                    年,其中的星期日是周的第一天,4 位,与 %V 使用
    %x	                    年,其中的星期一是周的第一天,4 位,与 %v 使用

    例子:博客园中的博客多少年多少月发布了几篇博客
            select data_format(sub_time, '%Y-%m'), count(1) from blog group by data_format(sub, '%Y-%m');
            占位符一部分跟Python里面的一样
            https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date-format

# 执行函数:
select upper('egon');

# 获取返回值
select UPPER('egon') into @res;
SELECT @res;

更多信息:
https://dev.mysql.com/doc/refman/5.7/en/functions.html

##################################################################

# 自定义函数,不能进行select 等操作
#   !!!注意!!!
#   函数中不要写sql语句(否则会报错),函数仅仅只是一个功能,是一个在sql中被应用的功能
#   若要想在begin...end...中写sql,请用存储过程
#
#   在v8中:
#   you *might* want to use the less safe log_bin_trust_function_creators variable
#   解决:
#   SET GLOBAL log_bin_trust_function_creators = 1;

delimiter //
create function f1(i1 int, i2 int)
returns int
begin
    declare num int default 0;
    set num = i1 + i2;
    return(num)
end //
delimiter ;

# 执行函数, 在子查询中:
# select f1(100, 200);

# 删除函数
# drop function [if exists] f1;

#####################################################################

# 存储过程:是存储在数据库中的一个别名
# 这个别名对应着一个SQL语句集合

使用存储过程的优点:

#1. 用于替代程序写的SQL语句,实现程序与sql解耦

#2. 基于网络传输,传别名的数据量小,而直接传sql数据量大

使用存储过程的缺点:

#1. 程序员扩展功能不方便

# 存储过程和视图的区别:
# 1,视图是虚拟的一张表,数据来源于物理表
# 2,存储过程可以进行任意操作
# 3,视图的调用方式是select
# 4,存储过程的调用方式是call

########################################################

补充:程序与数据库结合使用的三种方式

#方式一:
    MySQL:存储过程
    程序:调用存储过程

#方式二:
    MySQL:
    程序:纯SQL语句  --> 速度快,但是开发效率低

#方式三:
    MySQL:
    程序:类和对象,即ORM(本质还是纯SQL语句) -->  因为要转化,所以速度慢,但是开发效率高

##########################################################

# 存储过程在实际工作中的几种实现方式:
# 方式一:(将存储过程写好并放在MySQL中,程序员只需在代码中调用即可)
#       MySQL:存放存储过程
#       程序员:调用
#       (这种方式实现了SQL和代码的分离,解耦)
#       规模不大的公司

# 方式二:(避免DBA把存储过程修改了,导致程序无法正常执行)
        MySQL:不放存储过程
        程序员:将SQL语句写在代码中(可能会将SQL语句交给DBA看,如果合格了,就直接写在代码中;不合格的话,DBA提出建议,然后放在程序中)
        或者是MySQL进行设置一个慢日志,意思是:如果SQL执行的速度慢于一定的速度,DBA查看这个日志,将指定的SQL进行修改,交给程序员
        规模比较大的公司

# 方式三:
        MySQL:不放存储过程
        程序员:类+对象 (但内部还是转化成了SQL语句)

######################################################

# 建议: 在哪个库就把存储过程建在那里。

# 存储过程的创建:
# 形式一:无参数

          MySQL中:
            delimiter //
            create procedure p1()
            BEGIN
                select * from student;
                select * from score where class_id = 1;
                update student set sname = '牛奶' where sname = '理解';
            END //
            delimiter ;

          MySQL执行:
            call p1();

          Python中执行:
            cursor.callproc('p1')

          删除:
            drop procedure [if exists] p1;

# 形式二:有参数
            参数的类型有三种:
                in:仅用于传参用
                out:仅用于返回值
                inout:既可用于传参,又可用于返回值
            创建:
                create PROCEDURE p1(in num1 int, in num2 int)
                begin
                select * from student where sid > num1;
                select repeat('abc', num2);
                end
            执行:
                call p1(1, 4)
                Python中执行:
                    cursor.callproc('p1', (1, 4))
            创建二:
                create PROCEDURE p1(in num1 int, out num2 int)
                begin
                select * from student where sid > num1;
                set num2 = 123123;
                end
            执行:
                set @v1=1;  # 创建变量,因为out num2 只能接受一个变量,并且是session级别的
                call p1(1, @v1);
                select @v1;
                Python中执行:
                    ret = cursor.callproc('p1', (1, 3))
                    print(cursor.fetchall())
                    cursor.execute('select @_p1_0, @_p1_1')
                    print(cursor.fetchall()
            创建三:
                    delimiter //
                    create procedure p2(
                        in n1 int,
                        inout n3 int,
                        out n2 int
                    )
                    begin
                        declare temp1 int ;
                        declare temp2 int default 0;
            # declare在存储过程或函数执行过程中生效 (必须要写在最顶部)
            # set是整个会话期间都起作用,相当于一个会话期间的全局变量
                        select * from student;
                        set n2 = n1 + 100;
                        set n3 = n3 + n1 + 100;
                    end //
                    delimiter ;
            执行:
                set @v2 = 1;
                set @v3 = 3;
                call p2(100, @v3, @v2);
                select @v2, @v3;
# 为什么既需要结果集也需要out伪造的返回值?
# 为了标识存储过程的执行结果。


# 存储过程之事务操作:
# 用于将某些操作的多个SQL作为原子性操作,一旦有某一个出现错误,
# 即可回滚到原来的状态,从而保证数据库数据完整性。
create procedure p6(out p_status_code tinyint)
BEGIN

	declare exit handler for sqlexception
    BEGIN
            -- 失败
        set p_status_code = 1;
        rollback; # 回滚
    END;

    DECLARE exit handler for sqlwarning
    BEGIN
        -- WARNING
        set p_return_code = 2;
        rollback;
    END;

	start transaction;
		delete from transaction_test;  # 返回2
		# delete from transaction_test_1;  返回1
		insert into student(gender, class_id, sname)values('女', 2, 'belle');
	commit;
    -- 成功
	set p_status_code = 2;
END;

set @status_code = 0;
call p6(@status_code);
select @status_code;

# 存储过程之游标:
# 如果想对表的每一行都进行操作,就用游标,其实游标的性能不高
# 例子:将transaction_test表的每一行的id, num相加,赋值给tran_test_B的num
create procedure p8()
begin
	declare row_id int;
	declare row_num int;
	declare temp int;
	declare done int default false;
	declare my_cursor cursor for select id, num from transaction_test;
	declare continue handler for not found set done = true;

	open my_cursor;
		xxoo: loop
			fetch my_cursor into row_id, row_num;
			if done then
				leave xxoo;    # iterate xxoo; 相当于python中的continue
			end if;
			set temp = row_id + row_num;
			insert into tran_test_B(number) values(temp);
		end loop xxoo;
	close my_cursor;
end;


# 存储过程之动态执行SQL:防SQL注入
create procedure p9(in username varchar(255), in passcode varchar(255))
BEGIN
	set @username = username;
	set @passcode = passcode;
	prepare pre_sql from 'select * from userinfo where uname = ? and upwd = ?';
	execute pre_sql using @username, @passcode;
	deallocate prepare pre_sql;
end;

###########################################################################################

# 索引:
# 1. 为什么需要索引?
# 在平常的应用系统中,一般的插入和修改操作很少出现性能方面的问题,
# 平时我们常遇到的、也是最容易出现问题的还是查询操作,所以对查询语句的优化是非常重要的。
# 索引可以在不读取整个表的情况下,使数据库应用程序可以更快地查找数据。

# 2. 索引有什么副作用
# 索引会减慢数据插入和更新的速度
# 更新一个包含索引的表需要比更新一个没有索引的表花费更多的时间,这是由于索引本身也需要更新
# 因此,理想的做法是仅仅在常常被搜索的列 ( 以及表 ) 上面创建索引

# 2. 索引是什么?
# 索引在MySQL中也称为“键”,是存储引擎快速查找到记录的一种数据结构。

# 3. 索引多与少?
# 不管索引多还是少都会出现性能问题,所以需要找到一个平衡点。

# 4. 索引的目的
# 加快数据的查询速度

# 4. 索引的本质。
# 本质就是不断地缩小想要查询数据内容的范围来获取最终我们想要的结果,
# 同时将随机事件变成顺序事件

# 5. 预读
# 当经历一次IO时,OS不光把当前磁盘地址的数据读入内存,
# 还把相邻的磁盘地址的数据读入内存缓冲区内。
# 经历一次IO读入的数据称之为页,具体多少与OS有关

# 6. 索引的实现方式
# b+树,由二叉查找树以及平衡二叉树演变而来
# 一般树的高度为2 ~ 4,也就是经历2 ~4次IO就能查找到我们想要的数据。
# 叶子结点存放真实的数据,非叶子结点存放指引搜索方向的数据项

# 7. b+树索引的分类:
# 索引一般分为两大类: 聚集索引 和 非聚集索引(辅助索引)

# 聚集索引和辅助索引的区别在于:
        1. 叶子结点是否存放的是一整行的信息
        2. 一张表中聚集索引只能有一个,辅助索引可以有多个

        # 1. 聚集索引
            就是表记录的排列顺序和索引的排列顺序一模一样。
            聚集索引,不会有单独的空间存储索引数据,而是在存储数据的时候就已经根据索引排好序。

            通过表的主键来生成b+树,主键索引就属于 聚合索引

            好处:
                1. 对主键的排序查找和范围查找非常快,叶子结点存放的是数据一整行的信息。
                2. 范围查询,即如果想查找主键某一范围内的数据,通过叶子结点的上层中间节点就可以得到页的范围,之后直接读取数据即可。

        # 2. 辅助索引
            用另外的空间存储了记录的顺序,但是记录本身的物理顺序可以和索引不一样

            表中除了聚集索引以外的都是辅助索引(非聚集索引)
            例如:唯一索引 和 普通索引 都是非聚集索引


# 8. MySQL中索引管理

# 索引的功能:
        加速查找

# 索引种类:
    主键索引
    唯一索引
    普通索引
        1. 普通索引允许重复的值,就是两个记录的索引字段的值可以重复
        2. 唯一索引不允许重复的值,两个记录的索引字段不允许重复,
           其实 主键索引 也是一种特殊的唯一索引
# 注意:
# 组合索引?
# 组合索引不属于它们,
# 如果三个索引中包含了两个及以上字段,其实就是组合索引,不如组合主键索引
# 遵循最左前缀规则。对where,order by,group by 都生效。

# 覆盖索引
# 针对的是辅助索引
select name from t1 where id = 20   命中了辅助索引但是未覆盖到索引,还需要从聚集索引中查找name
select id  name from t1  where id =  20  命中索引  一次就找到 覆盖到了索引

# 索引种类:
#       普通索引INDEX:
            加速查找

        主键索引PRIMARY KEY:
            加速查找+约束(不为空、不能重复)

        唯一索引UNIQUE:
            加速查找+约束(不能重复)

        联合索引:
            -PRIMARY KEY(id,name):联合主键索引
            -UNIQUE(id,name):联合唯一索引
            -INDEX(id,name):联合普通索引


        举个例子来说,比如你在为某商场做一个会员卡的系统。

        这个系统有一个会员表
        有下列字段:
        会员编号 INT
        会员姓名 VARCHAR(10)
        会员身份证号码 VARCHAR(18)
        会员电话 VARCHAR(10)
        会员住址 VARCHAR(50)
        会员备注信息 TEXT

        那么这个 会员编号,作为主键,使用 PRIMARY
        会员姓名 如果要建索引的话,那么就是普通的 INDEX
        会员身份证号码 如果要建索引的话,那么可以选择 UNIQUE (唯一的,不允许重复)

        #除此之外还有全文索引,即FULLTEXT
        会员备注信息 , 如果需要建索引的话,可以选择全文搜索。
        用于搜索很长一篇文章的时候,效果最好。
        用在比较短的文本,如果就一两行字的,普通的 INDEX 也可以。
        但其实对于全文搜索,我们并不会使用MySQL自带的该索引,而是会选择第三方软件如Sphinx,专门来做全文搜索。

        #其他的如空间索引SPATIAL,了解即可,几乎不用

# 9. 索引的两大类型hash与btree

    hash类型的索引:查询单条快,范围查询慢
    btree类型的索引:b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它)

    #不同的存储引擎支持的索引类型也不一样
    InnoDB 支持事务,支持行级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;
    MyISAM 不支持事务,支持表级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引;


    1、普通索引
        普通索引仅有一个功能:加速查询

        创建表 + 索引
        create table in1(
            nid int not null auto_increment [primary] key,
            name varchar(32) not null,
            email varchar(64) not null,
            extra text,
            -- index ix_name (name)  -- 只能写在后面
            key ix_name (name)  -- 默认名字就是字段名
)
        创建索引
        create index index_name on table_name(column_name)

        删除索引
        drop index index_name on table_name;

        alter table table_name drop {index | key} index_name;

        查看索引
        show index from table_name;

    2、唯一索引
        唯一索引有两个功能:加速查询 和 唯一约束(可含nullcreate table in1(
            nid int not null auto_increment primary key,
            name varchar(32) not null,
            email varchar(64) not null,
            extra text,
            unique ix_name (name)
)
        创建索引
        create unique index 索引名 on 表名(列名)

        删除索引
        drop index 索引名 on 表名

        alter table table_name drop {index | key} index_name;

    3. 主键索引
        主键有两个功能:加速查询 和 唯一约束(不可含nullcreate table in1(
            nid int not null auto_increment [primary] key,
            name varchar(32) not null,
            email varchar(64) not null,
            extra text,
            index ix_name (name)
)

        OR

        create table in1(
            nid int not null auto_increment,
            name varchar(32) not null,
            email varchar(64) not null,
            extra text,
            primary key(ni1),
            index ix_name (name)
)
    创建主键
    alter table 表名 add primary key(列名);

    删除主键
    alter table 表名 drop primary key;

    4. 组合索引 (默认名字为第一个字段名)
        组合索引是将n个列组合成一个索引
        其应用场景为:频繁的同时使用n列来进行查询,如:where n1 = 'alex' and n2 = 666create table in3(
            nid int not null auto_increment primary key,
            name varchar(32) not null,
            email varchar(64) not null,
            extra text
)

        create index ix_name_email on in3(name,email);

# ==============================
# 记录一个案例:
# create table users2(id int, uname char, primary key(id, uname));
#  create table role2(id int, rname char, primary key(id));
# mysql> create table user2torole4(id int, uid int, rid int, foreign key(uid) refe
rences users2(id), foreign key(rid) references role(id));

# 一个联合主键,第三张表也可以不用联合外键,记住一下

# =================================



# 10. 索引总结:
        #1. 一定是为搜索条件的字段创建索引,比如select * from s1 where id = 333;就需要为id加上索引

        #2. 在表中已经有大量数据的情况下,建索引会很慢,且占用硬盘空间,建完后查询速度加快
        比如create index idx on s1(id);会扫描表中所有的数据,然后以id为数据项,创建索引结构,存放于硬盘的表中。
        建完以后,再查询就会很快了。

        #3. 需要注意的是:innodb表的索引会存放于s1.ibd文件中,而myisam表的索引则会有单独的索引文件table1.MYI

        MySAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。
        而在innodb中,表数据文件本身就是按照B+Tree(BTree即Balance True)组织的一个索引结构,
        这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此innodb表数据文件本身就是主索引。
        因为inndob的数据文件要按照主键聚集,所以innodb要求表必须要有主键(Myisam可以没有),
        如果没有显式定义,则mysql系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,
        则mysql会自动为innodb表生成一个隐含字段作为主键,这字段的长度为6个字节,类型为长整型.

# 编写高性能的SQL语句
# 建表时
    1. 能用数字类型就不用字符类型

    2. char代替varchar

    3. 固定长度的放在前面

    4. 能用varchar就不用text

    5. 避免将列设为NULL

    6. 用索引提高查询效率

    7. 可以的话,将SQL转成大写执行

    8. 查看执行计划

# 连表时
    1. 连表join代替子查询

    2. 连表时类型一致

    3. 使用表的别名,减少解析时间和歧义

# 删除表记录
    如果就是想删除全表的记录:truncate table table_name;
    使用 sql delete 语句一定要附加 WHERE 子句,如果删除所有记录,也要用 WHERE 1=1;

# 提高SQL查询的效率
# ========================

# 当数据量大到一定的程度,不管你如何优化语句都达不到需求,
# 最关键的是改变设计方法,分库分表分拆业务,再加上全文搜索和缓存等等多种方法,
# 不能都指望数据库来提高检索效效率,当然写出高性能的sql语句是必要前提。

# ========================
    建索引:
        1. 首先应考虑在常常用于条件判断的字段上创建索引

        2. 尽量选择区分度高的列建立索引
            如果为区分度低的列建立索引,会导致索引树很高
            一般在2 ~ 4层
            区分度公式:count(distinct col) / count(*)
            数值越大表示查询的记录越少,表示字段不重复的比例

        3. 当需要组合搜索时, 使用组合索引代替多个单列索引

        4. 在创建联合索引时,根据业务需求,将where子句中使用最频繁的一列放在最左边, 像这种范围的放到后面去

    查询时要正确命中索引:
        1. select子句中尽量避免使用*

        2. count(1) / count(主键/) 代替 count(*)

        3. 避免对字段使用例如:UCASE() / LCASE() / MID() 等内置函数

        4. where子句比较符号左侧避免表达式、函数
            尽量避免在where条件子句中,比较符号的左侧出现表达式、函数等操作
            例如:
                where 成绩 + 5 > 90  (表达式在比较符号的左侧)

                优化方法:

                where 成绩 > 905(表达式在比较符号的右侧)

        5. 索引字段不要参与计算

        6. 索引字段不要使用函数

        7. 索引类型要一致
         例:where id = 3  而不是 where id = '3'

        8. 尽量避免在 where 子句中对字段进行 null 值判断

        9. 对于范围查询:
            查询的范围较大时,速度肯定很慢
            查询的范围小,速度依然很快
            特别的:
                对于 != 或者 <> 来说,如果是主键,还是会走索引
                对于 > 来说,如果是主键,还是会走索引,而且类型为整型,也会走索引

        10. 在MySQL中,模糊查询避免开头就使用“%”
            例如: LIKE  '%C%';

        11. 尽量避免使用innot in
            in里面的结果集如果是通过一个子查询出来的是不走索引的
            in 可以用 between and
            not in 可以用 not exists

        12. 尽量避免使用or,使用union all代替
           (如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描)
            例如:elect 学号 from 成绩表 where 成绩 = 88 or 成绩 = 89
            优化后:
                select 学号 from 成绩表 where 成绩 = 88

                union

                select 学号 from 成绩表 where 成绩 = 89

        13. 对于联合索引,要遵循最左前缀匹配原则

            联合索引的最左前缀:

            A、B、C3个字段--联合索引

            这个时候,可以使用的查询条件有:A、A+B、A+C、A+B+C,唯独不能使用B+C,即最左侧那个字段必须匹配到

            解释一下最左前缀原则:
            当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+树是按照从左到右的顺序来建立搜索树的,
            比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,
            如果name相同再依次比较age和sex,最后得到检索的数据;

            但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,
            因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。
            比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,
            所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了,
            这个是非常重要的性质,即索引的最左匹配特性。

            范围查询:
                范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引。
                同时,索引最多用于一个范围列,因此如果查询条件中有两个范围列则无法全用到索引。

                mysql会一直向右匹配直到遇到范围查询(><betweenlike)就停止匹配,
                比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,
                d是用不到索引的,
                如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

        14. 排序条件为索引,select的时候也是索引字段
            特别的:
                对于主键来说,还是会走索引

        15. 使用limit子句限制返回的数据行数


# 慢查询优化的基本步骤

    0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE
    1.where条件单表查,锁定最小返回记录表。
        这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,
        单表每个字段分别查询,看哪个字段的区分度最高
    2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)
    3.order by limit 形式的sql语句让排序的表优先查
    4.了解业务方使用场景
    5.加索引时参照建索引的几大原则
    6.命中索引
    7.观察结果,不符合预期继续从0分析


######################################################################################

# 执行计划:一个预估SQL语句执行的时间的操作
# 关键字:explain

# 虽然有MySQL优化的措施避免一些不能命中索引的方式,
# 但是最后还是要看SQL语句的运行时间,时间短就是好的。

# 执行计划是以最坏的打算进行预估SQL语句执行的时间,所以只能作为参考。
# 以后拿到一个SQL语句的时候,先进行执行计划。

# explain select * from userinfo;
id select_type table partitions type possible_keys key key_len ref rows filtered Extra

id: 查询顺序的标识
select_type: 查询的类型
            SIMPLE: 简单查询
            PRIMARY: 最外层查询
            SUBQUERY:映射的子查询
            DRIVED: 子查询
            UNION:联合
            UNION RESULT:使用联合的结果
table: 访问的表
partitions:
type: 查询的访问方式
        all < index < range < index_merge < ref_or_null < ref < eq_ref < const/system
        all: 从前往后,全表扫描
            select * from userinfo where uname = 'alex';
            特别的:select * from userinfo where uname = 'alex' limit 1;
            这句特别快,虽然explain后,type也是all,但这句是找第一个后就不会往后继续扫描了
        index: 全索引扫描
        range: 指定索引范围扫描
            PS: between and / in / >= < <= 特别注意!=>
        index_merge: 索引合并
        ref_or_null:
        ref: 使用索引查询一个或多个值
        eq_ref: 连表时条件onprimary key或者unique
        const: 常量,最多一个匹配行
        system: 仅一个匹配行
possible keys: 可能使用的索引
key: 真实使用的索引
key_len: 索引使用的长度
ref:
rows: 找到所需的行所读取的行数
filtered:
Extra:

###########################################################################################################

# 慢日志记录

# 让MySQL自动记录慢查询
# 配置方法一:
# 内存级别的修改
1,开启慢查询日志
slow_query_log
2,配置时间限制,超过此时间,则记录
long_query_time
3,开启记录没有使用索引的查询的选项
show variables like '%queries%';
log_queries_not_using_indexes
4,日志文件路径
slow_query_log_file

# 查看当前配置信息
show variables like '%query%';
# 修改当前配置
set global 变量名 =# 配置方法二:
# 给mysqld 指定配置文件
mysqld --defaults-file='D:\\....\.conf'
# 可以自行创建一个配置文件,然后把以上那些参数写在文件中即可。

# 配置方法三:
可以改默认的配置文件(我目前还没找到)

#####################################################################################################

# 分页性能相关方案

# 方案一:(O(∩_∩)O)
不让看,例如博客园

# 方案二:
# 运用覆盖索引的方式去索引表进行扫描:(这种方式并没有快多少)
select
    *
from
    userinfo
where
    userinfo.uid
in
(select uid from userinfo limit 700000, 10)# 方案三:(如果想实现跳转,那么基于数据库来实现不方便,可以基于其他形式例如缓存机制实现)
# 做记录,记录当前页的最大ID和最小ID
# max_id
# min_id
# 形式一:只有上一页、下一页
# 下一页:
select * from userinfo where uid > 700000 / max_id limit 10;
# 上一页:
select
    *
from
    (select * from userinfo where uid < 700001 / min_id order by uid desc limit 10) as ReversedLastPage
order by
    ReversedLastPage.uid asc;

# 注意:between and 如果在uid都是连续的情况下,那是最好的啦。但uid不一定连续。

# 形式二:上一页 192 193 ... [196] 197 198 199 下一页
# 假设,当前页在196, 想跳转到199, 一页只有10条数据
# 思路:计算199与196相差数据的数目,然后倒序,取最后10条的uid
199 - 196 = 3
select
    *
from
    userinfo
where
    userinfo.uid
in (
select
    UID.uid
from
(select AmongData.uid from (select uid from userinfo where uid > 当前页最大值 limit 每页数据 * [页码-当前页])
as AmongData order by AmongData.uid desc limit 10) as UID order by UID.uid asc)
order by userinfo.uid asc;

##################################################################################################

# MySQL的默认端口是 3306
# http头部与http请求体之间的分隔符是两个换行
#
# 函数编程:数据与逻辑分离
# 面向对象:数据与逻辑组合
# 面向对象的两大场景:
#       1,当一类函数共用同样的参数的时候,就可以使用类
#       2,创建模板:一类事物具有相同的属性和方法
# 面向对象的核心:分类以及提取共性

# ORM(Object Relational Mapper)框架
# 类:表;对象:行
# ORM有两类:
# 一,DB first: 手动创建数据库和表    =====> ORM框架 ===> 自动生成类
# 二,code first: 手动创建数据库和类 ====> ORM框架 ===> 自动生成表

# PS: Django 支持DB first 和 code first

# SQLAlchemy: code first
#   作用:提供简单的规则
#         自动转换成SQL语句
#         SQLAlchemy本身是无法操作数据库的,它只是将类和对象转换成SQL语句,
# (Dialect一般就是MySQL/SQL Server/Oracle等数据库,它与DBAPI交流)
# 然后再借助第三方工具DBAPI例如pymysql,将转换好了的SQL语句交给第三方工具去执行。
#
#

你可能感兴趣的:(MySQL,数据库,mysql,学习笔记)