MySQL系列---实践总结

目录

  • 基础术语
  • 命令分类
  • DDl命令详解
  • DML命令详解
  • DQL命令详解
  • DCL命令详解
  • TCL命令详解
  • 视图及索引
  • 索引
  • 数据库设计
  • sql语句优化
  • 分布式和集群
  • 读写分离-并行度
  • 分库分表-存储量
    • 分库产生的问题
    • 分表产生的问题
  • 分区表
  • 分区表与分表的区别
  • 分区表与分表的联系
  • 心得

基础术语

数据库:DB(简称),database(全称)

数据库管理系统:DBMSb(简称),database management system(全称)

DBMS大致分类:

  1. 关系型:Oracle/Mysql/Sql server/Db2—服务按核收费/开源免费/微软产品/ibm产品
  2. 非关型:redis/hbase/mongdb/neo4j—内存缓存/列存储/文档存储/图存储

客户端软件:Navicat Premium 最好

命令分类

  1. 登录
    登录:mysql -uname -p Enter password:xxxxxx
    展示数据库:show databases;
    进入数据库:use dbname;
    展示数据表:show tables;
    查看表结构:desc tname;
  2. 数据定义语言
    DDL:Data Definition Language,创建和维护数据库对象结构、创建数据库、表、视图、索引等,总之就是不涉及删除修改具体信息影响结构的行为。
    create / alter / drop 相当于搞房子时打地基
  3. 数据操纵语言
    DML:Data Manipulation Language,数据的增删改查
    insert / delete / update / select 行业术语CRUD(select-retrieve)
  4. 数据控制语言
    DCL:Data Control Language,授权以及取消授权
    grant / revoke
  5. 事务控制语言
    TCL:transaction Control Language,执行成功全部保存、执行失败时全部删除,与安装软件的策略相同,只有进度条完全成功才会全部安装,中途出错全部删除!
    Commit / rollback
  6. 数据查询语言
    DQL:Data Query Language,DML中增删改语法相对固定且较少,最重要的便是查,基于其重要性因此查也被单独的定义成一种行为
    select!

以下示例中“[ ]”代表可加可不加,但加与不加其对应含义不同,执行效果也会不同,仅仅只代表语法规则内两者均可而已!

DDl命令详解

需要带上前缀(database/table )

  1. 数据类型
    整型数据:
    int(tinyint、smallint、bigint) 4个字节
    浮点数:
    fioat(m,n),double(m,n),decimal(m,n),m代表长度n代表小数点位数,只有decimal不会产生精度丢失,因此常用语存储价格或金额
    日期类型:
    date:日期,Time:时间,Datetime:时间和日期,Timestamp:时间戳 ,Year:年份
    字符串:
    char(n):(定长字符串) 不足长用前缀空格填满,Varchar(n):(可变字符串) 指定的只是上限,Text:大文本(比如存一本书,特别多的)
    其他类型:
    Bit(1):性别的常用类型,因为性别的存储方式不是男女,而是0 1
    Blob:存储二进制数据(图片、音频、视频)、很少用,数据库存这些不效率也浪费空间,这些一般存在文件库中,数据库对应存其地址
    Enum:枚举类型
    Set:集合类型
    Json:json类型,数据传输格式、后台传到前台(本质是一个字符换),它在传输时会保持一定的格式,集合转化成json、再从json转化成集合,简化效率

  2. 基本语句:creat/drop/alert
    创建/删除数据库: create/drop database [if not exists] dbname;
    创建数据表: create table [if not exists] tname(name varchar(10),sex bit);
    删除数据表:drop table tname;
    修改数据表:alter table tname rename/add/drop/change/modify
    复制数据表结构/结构加数据:creat table new_tname select * from tname where False/True

  3. 修改语句
    1. 添加字段(add) 追加
    alter table student add tel varchar(11); // sql语言默认添加到最后一位
    #添加字段到首列
    alter table student add email varchar(20) first;
    #放置字段到第二列
    alter table student add height double(4,1) after email;
    2.删除字段(drop)
    alter table student drop height;
    3.修改字段(modify/change)
    #modify: modify 字段名称 新类型 新约束
    #change: change 旧字段 新字段 新类型 新约束
    #修改字段名称
    alter table student change name sname varchar(20);
    #只修改字段类型和长度
    alter table student change sname sname varchar(50);
    alter table student modify sname varchar(50);
    #修改字段位置
    alter table student modify age int after sname;
    4.表名称修改
    alter table student rename stu;

  4. 标准建表语句,真正的数据表绝对不会只有字段即可,还有限制条件
    实体完整即行完整:primary key/unique/auto_increment
    #创建数据表时随字段声明,优少写一行代码缺无法创建联合主键
    create table stu(sid int primary key,sname varchar(20),age int);
    #不随字段单独声明,可创建联合主键
    create table stu(sid int,sname VARCHAR(20),age int,primary key(sid,age));
    #不在建表时指定,其实已经归属于修改表结构范畴,因此用alert
    alert table stu add constraint primary key(sid,age);
    #不为空,建表时设置,此约束只能随字段
    create table stu(sid int primary key,sname varchar(20) unique,age int);
    #不为空,建表后设置
    alert table stu add constraint unique(sname);
    #主键自增,建表时指定
    create table stu(sid int primary key auto_increment,sname varchar(20),age int);
    ##主键自增比较复杂:
    首先,他只有主键才可以设置,也就是说随便一个字段就设置auto_increment会报错的;
    其次,设置主键自增以后,insert语句时则不需要再指定此字段,也不需要指定此字段对应值;
    最后,主键自增如果建表时没有指定,不可以用建表后设置的通用语句(alert table stu add constraint xxx),语法不允许,因为这个约束是有前提的,只能使用alert table stu modify xxx;
    另外,其实所有的约束都可以用一个统一语句表述,并且很好记,如下
    ALTER TABLE stu MODIFY sid int primary key;
    ALTER TABLE stu MODIFY sid int primary key auto_increment;
    ALTER TABLE stu MODIFY sid int unique;
    域完整性即字段完整:not null/default
    #下面是一个完整的建表语句
    create table stu(sid int primary key auto_increment,sname varchar(20) not null default ‘like’,age int default 1);
    #建表后修改
    ALTER TABLE stu MODIFY sid int not null DEFAULT 22;
    ##域完整有个瑕疵:
    那就是not null 和default可以同时指定,按理说没有意义啊,只要指定not null,就不会有机会用到defaut的值
    应用完整性即外键约束:foreign key
    alter table stu add constraint foreign key(cid) references classroom(cid);
    ##外键约束说明:
    首先,外键的取值范围必须在参照表主键范围内;
    其次,主外键的名称可以不一致,但是约束必须一致。
    最后,只能用add constraint foreign key添加外键约束,无法用modify
    另外,举例说明什么是外键约束,一个表中的某个字段参考另一个表中的某个字段,(比如学生表和班级表,两个表中都含有班级字段,)保证表与表中参考数据的完整性,
    MySQL系列---实践总结_第1张图片
    总结:只有建表语句中可以在字段后指定的限制才可以用modify修改表结构,无法随字段添加的注定只能用 add constraint :这就是外键约束无法用 modify的原因,是有迹可循的。

DML命令详解

不需要带前缀,带前缀会无法执行(确实也没必要带前缀,因为DML命令只能操作table,无需赘述)

  1. 插入语句:insert into
    语法:insert into tname [(feild1,field2,…)] values(value1,value2,…)
    #插入一条完整语句,字段可省略,但所有字段对应值不可缺少且顺序正确
    insert into stu values(‘[email protected]’,’zs’,18,’男’,’123445555555’);
    #插入非完整字段,字段顺序随意,但值必须和语句中字段声明顺序一致
    insert into stu(age,sname,sex)values(20,’ls’,’女‘);
    #插入多条语句,插入多条语句的效率远高于逐条插入
    insert into stu(age,sname,sex)values(21,’lz’,’女‘); (22,‘lc’,‘男’);
  2. 修改语句:update
    语法:update tname set fie1=val1,fie2=val2 [where condition] //不加条件限制,则代表此字段全表修改
    updata stu set age=22; //所有人年龄均变为22
    updata stu set age=33 where sname=‘lz’; //只有lz的年龄变化为33
  3. 删除语句:delete from
    语法:delect from tname [where condition]; []含义同修改,以下不再赘述
    delect from stu; //全表删除,效果同于清空表
    delect from stu where sname = ‘haha’; //删除一条数据

DQL命令详解

语法顺序是语法顺序,执行顺序是执行顺序,两者不同,都了然于胸才写的对写的出查询语句!!!

  1. 语法顺序:select-distinct/func-from-join-on-where-group by-with-having-order by-asc/desc-limit
    以下是一个伪sql语句示例:
    MySQL系列---实践总结_第2张图片

  2. 执行顺序:from-on-join-where-group by-func-with-having-select-distinct-order by-desc/asc-limit
    MySQL系列---实践总结_第3张图片

  3. sql调优:调优实际上就隐藏在sql的执行原理中,任何调优都是洞悉其执行原理,进而投其所好选取最优解。
    从上述伪sql语句的顺序中我们即可以发现,所有的查询语句都是从 FROM 开始执行的。在实际执行过程中,每个步骤都会为下一个步骤生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入。 以下便是每个步骤的具体执行过程,理解这些过程,你便同时学会了sql调优
    1 )FROM 执行笛卡尔积
    FROM 才是 SQL 语句执行的第一步,并非 SELECT 。对FROM子句中的前两个表执行笛卡尔积(交叉联接),生成虚拟表VT1,获取不同数据源的数据集。
    FROM子句执行顺序为从后往前、从右到左,FROM 子句中写在最后的表(基础表 driving table)将被最先处理,即最后的表为驱动表,当FROM 子句中包含多个表的情况下,我们需要选择数据最少的表作为基础表。
    2 )ON 应用ON过滤器
    对虚拟表VT1 应用ON筛选器,ON 中的逻辑表达式将应用到虚拟表 VT1中的各个行,筛选出满足ON 逻辑表达式的行,生成虚拟表 VT2 。
    3 )JOIN 添加外部行
    如果指定了OUTER JOIN保留表中未找到匹配的行将作为外部行添加到虚拟表 VT2,生成虚拟表 VT3。保留表如下:
    LEFT OUTER JOIN把左表记为保留表
    RIGHT OUTER JOIN把右表记为保留表
    FULL OUTER JOIN把左右表都作为保留表
    在虚拟表 VT2表的基础上添加保留表中被过滤条件过滤掉的数据,非保留表中的数据被赋予NULL值,最后生成虚拟表 VT3。
    4 )WHERE 应用WEHRE过滤器
    对虚拟表 VT3应用WHERE筛选器。根据指定的条件对数据进行筛选,并把满足的数据插入虚拟表 VT4。
    由于数据还没有分组,因此现在还不能在WHERE过滤器中使用聚合函数对分组统计的过滤。
    同时,由于还没有进行列的选取操作,因此在SELECT中使用列的别名也是不被允许的。
    如果FROM子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1~3,直到处理完所有的表为止。
    5 ) GROUP BY 分组
    按GROUP BY子句中的列/列表将虚拟表 VT4中的行唯一的值组合成为一组,生成虚拟表VT5。如果应用了GROUP BY,那么后面的所有步骤都只能得到的虚拟表VT5的列或者是聚合函数(count、sum、avg等)。原因在于最终的结果集中只为每个组包含一行。
    同时,从这一步开始,后面的语句中都可以使用SELECT中的别名。
    6 )AGG_FUNC 计算聚合函数
    计算 max 等聚合函数。SQL Aggregate 函数计算从列中取得的值,返回一个单一的值。常用的 Aggregate 函数包涵以下几种:
    AVG:返回平均值
    COUNT:返回行数
    FIRST:返回第一个记录的值
    LAST:返回最后一个记录的值
    MAX: 返回最大值
    MIN:返回最小值
    SUM: 返回总和
    7 )WITH 应用ROLLUP或CUBE
    对虚拟表 VT5应用ROLLUP或CUBE选项,生成虚拟表 VT6。
    CUBE 和 ROLLUP 区别如下:
    CUBE 生成的结果数据集显示了所选列中值的所有组合的聚合。
    ROLLUP 生成的结果数据集显示了所选列中值的某一层次结构的聚合。
    8 )HAVING 应用HAVING过滤器
    对虚拟表VT6应用HAVING筛选器。根据指定的条件对数据进行筛选,并把满足的数据插入虚拟表VT7。
    HAVING 语句在SQL中的主要作用与WHERE语句作用是相同的,但是HAVING是过滤聚合值,在 SQL 中增加 HAVING 子句原因就是,WHERE 关键字无法与聚合函数一起使用,HAVING子句主要和GROUP BY子句配合使用。
    9 )SELECT 选出指定列
    将虚拟表 VT7中的在SELECT中出现的列筛选出来,并对字段进行处理,计算SELECT子句中的表达式,产生虚拟表 VT8。
    10 )DISTINCT 行去重
    将重复的行从虚拟表 VT8中移除,产生虚拟表 VT9。DISTINCT用来删除重复行,只保留唯一的。
    11 )ORDER BY 排列
    将虚拟表 VT9中的行按ORDER BY 子句中的列/列表排序,生成游标 VC10 ,注意不是虚拟表。因此使用 ORDER BY 子句查询不能应用于表达式。同时,ORDER BY子句的执行顺序为从左到右排序,是非常消耗资源的。
    12 )LIMIT/OFFSET 指定返回行
    从VC10的开始处选择指定数量行,生成虚拟表 VT11,并返回调用者。

  4. 基础查询:
    1)#查询emp表的所有信息
    select * from emp;
    2 ) #查询emp表中所有员工的姓名个职位信息
    selsct ename,job from emp;
    3 )#查询emp表中员工部门为20的全部信息
    select *from emp where deptno = 20;
    4 ) #查询emp表种员工工资大于2000的员工信息
    select ename, sal form emp where sal >2000;
    5 ) #查询emp表中员工工资在1000到2000之间的员工信息
    select * from emp where sal>1000&&sal<2000;
    select * from emp where sal>1000 and sal<2000;
    select * from emp where sal between 1000 and 2000;
    6 ) #查询emp表中员工编号为7788,7369,7521的所有员工信息。(集合查询)
    select * from emp where empno=7788,empno=7369,empno=7521; // 用逗号是不对的
    select * from emp where empno=7788|empno=7369|empno=7521; // |是位运算符,||才对
    select * from emp where empno=7788||empno=7369||empno=7521; //这样才可以。
    select * from emp where empno in (7788,7521,7369); //标准语法
    7)#查询emp表中所有的职位信息(直接写是有重复信息的,所以需要去重)
    select distinct job from emp;
    8 ) #distinct与函数连用,需要将distinct写在函数里面,只有这样才能在执行函数前执行distinct。因为distinct执行顺序在函数之后!
    select count(distinct job)from emp;
    9)#查询emp表中工资提升5%后的员工姓名及其工资。[as] 别名
    select ename,sal * 1.05 [as] nsal from emp;
    select e.ename, e.sal from emp e;
    10 ) #查询emp表中所有(没)有奖金的员工信息。
    select * from emp where comm = null; 等号不能判断为空,无输出
    select * from emp where comm is null;
    select * from emp where comm is not null;
    11 ) #查询emp表中工资最高的员工信息。
    select * from emp order by sal desc limit 1; //一个条件
    select * from emp order by sal desc, empno desc limit 1; //多个条件,逗号分隔
    select * from emp limit 0,5;//等同于limit 5,limit [开始索引(不包含开始)]长度)(不写开始索引,默认从第一位开始
    12) #查询名称中包含s的员工信息。(模糊查询:like;%:代表n个字符;_:代表一个字符)
    select * from emp where ename like ‘%s%’;
    select * from emp where ename like ‘s%’;(姓名首字母是s的员工)
    select * from emp where ename like ‘%s’;(姓名末尾是s的员工)
    #查询名称第三个字符为L的员工信息;
    select *from emp where ename like ‘__L%’;
    13 ) #输出平均工资大于2000的部门
    select deptno,avg(sal) asal from emp group by deptno having asal >2000; // 执行机制决定group by、聚合函数、having一般连用。

  5. 高级查询:逗号表示字段和表并列,and表示约束条件并列
    ###内连接查询:内外键互换不会影响结果集,哪个表在前面哪个表的内容就在前面,显示两者共有的
    //输出笛卡尔积,也就是直积,首先,输出行数问题,表1行数 * 表2行数!其次,哪个表在前面其字段就在前面;最后,即使两表有重复字段也会输出,并且就应该输出,因为是直积,重复字段并不同值!
    select * from emp,dept;
    #一般来说,直接输出直积是没什么意义的,肯定要根据关联输出,但是,使用where这种输出方法同样会输出重复字段,实际这个重复字段第二次输出是没意义的!因此两次输出值完全相同
    select * from emp,dept where emp.deptno=dept.deptno;
    #标准语法,inner join … on …; 其实和逗号连接关联表是同样效果,重复字段依旧会输出
    select * from emp inner join dept on emp.deptno=dept.deptno;
    #这种语法输出比较规范,建议多用,不过只有关联字段名称相同才可以,但是若是准备关联,肯定是要设置成相同的吧?! inner join … using(),这种方法不会输出重复字段,并且关联字段会在第一列输出
    select * from emp inner join dept using(deptno);
    ###外连接查询:内外键互换会影响结果集,显示基准表中有的内容,若被关联表中没有值以null填充
    select * from emp left join dept on emp.deptno=dept.deptno; //emp为基准表,deptno字段重复输出
    select * from emp right join dept on emp.deptno=dept.deptno; //dept为基准表,deptno字段重复输出
    select * from emp left join dept using(deptno); //emp为基准表,deptno字段不重复输出
    select * from emp right join dept using(deptno); //dept为基准表,deptno字段不重复输出
    ###自然连接:NATURAL JOIN,相当于省略了using(),关联字段第一列显示,且不重复输出
    #以下两种输出,关联字段始终第一列输出,后续依旧有基准表和被关联表区别的
    SELECT * FROM law_user NATURAL JOIN forum_article;
    SELECT * FROM forum_article NATURAL JOIN law_user;
    ###应用示例
    #查询20号部门所有员工及其所在部门信息
    select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 20;
    select * from (select * from emp where deptno = 20) e,dept where e.deptno = dept.deptno
    #查询所有员工及其上级领导的名称,非同字段关联
    select * from emp e1 left join emp e2 on e1.mgr = e2.empno;
    总结:using()和NATURAL JOIN会不输出重复字段,其它都会输出重复字段

  6. 嵌套查询(子查询):将一个查询结果作为另一个查询的条件或者组成继续进行检索
    ###单行子查询:子查询返回结果是一条记录,用 =
    #查询编号为7788的员工所在的部门信息
    select dept.* from dept,(select deptno from emp where empno = 7788 ) e where e.deptno = dept,deptno; //关联查询,其实跟上述的高级查询类似
    select * from dept where deptno = (select deptno from emp where empno = 7788) //进阶至嵌套查询
    ###多行子查询:子查询返回多条记录 使用 in 或者any/all;=any:相当于in; >any: 大于最小值;all: 大于最大值; #查询工资大于2000的员工的部门信息
    select * from dept where deptno in (select distinct deptno from emp where sal>2000)
    #查询工资大于所在部门平均工资的员工信息
    select * from emp,(select deptno, avg(sal) asal from emp group by deptno) e where emp.deptno = e.deptno and emp.sal>e.asal; //关联查询
    select * from emp e1 where sal > (select avg(sal) from emp e2 where e2.deptno = e1.deptno) //查询都是逐条进行的,主查询和子查询是可以相互传递参数的,主查询需要什么子查询就需要给我什么,这是此题第二种方法嵌套查询理解的关键。
    ###多列子查询:用exists,先执行主查询,不需要关心两者字段是否匹配,将主查询的记录依次交给子查询,在子查询中匹配记录
    #查询工资大于2000的员工所在部门信息
    select * from dept where deptno in (select deptno from emp where sal >2000);
    select * from dept where exists (select * from emp where sal > 2000 and emp.deptno = dept.deptno)
    #总结in和exists区别:in先执行子查询,exists限制性主查询;in需要关注子查询返回值的字段信息,exists不需要关注返回字段;exists将主查询的记录依次在子查询匹配,如果匹配返回则true并展示。

  7. 联合查询:又名合并结果集;关键字: union(自动去重) union all(不去重,故会有重复),条件是多个结果集都在同一个表中出现
    #查询20号部门以及工资大于2000的员工信息
    select * from emp where deptno = 20 or sal > 2000;
    select * from emp where deptno =20 union all all select * from emp where sal >2000;
    总结
    嵌套查询是将子查询查出来放在限制条件中,关联查询是将子查询当做一个关联表;
    关联查询需要将多个表连接在一起,缺点是占用内存稍大,子查询缺点是可能会有重复;
    感觉复杂的sql语句都是混合使用,其实也没那么大区别。

DCL命令详解

  1. grant授权

    #0.授权语句
    grant all on *.* to root@'172.16.1.%' identified by '123';
    
    #1.查看用户权限
    mysql> show grants for root@'localhost';
    | Grants for root@localhost |
    | GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' WITH GRANT OPTION |
    | GRANT PROXY ON ''@'' TO 'root'@'localhost' WITH GRANT OPTION   |
    
    
    #2.全库全表授权
    mysql> grant all on *.* to root@'172.16.1.%' identified by '123';
    Query OK, 0 rows affected (0.00 sec)
    
    #3.单库授权
    mysql> grant all on mysql.* to root@'172.16.1.%' identified by '123';
    Query OK, 0 rows affected (0.00 sec)
    
    #4.单表授权
    mysql> grant all on mysql.user to root@'172.16.1.%' identified by '123';
    Query OK, 0 rows affected (0.00 sec)
    
    #5.单列授权(脱敏)
    mysql> grant select(user,host) on mysql.user to root@'172.16.1.%' identified by '123';
    Query OK, 0 rows affected (0.00 sec)
    
    #6.扩展参数
    max_queries_per_hour:一个用户每小时可发出的查询数量
    mysql> grant all on *.* to root@'172.16.1.%' identified by '123' with max_queries_per_hour 2;
    Query OK, 0 rows affected (0.00 sec)
    
    max_updates_per_hour:一个用户每小时可发出的更新数量
    mysql> grant all on *.* to root@'172.16.1.%' identified by '123' with max_updates_per_hour 2;
    Query OK, 0 rows affected (0.00 sec)
    
    max_connetions_per_hour:一个用户每小时可连接到服务器的次数
    mysql> grant all on *.* to lhd@'172.16.1.%' identified by '123' with max_connections_per_hour 2;
    Query OK, 0 rows affected (0.00 sec)
    
    max_user_connetions:允许同时连接数量
    mysql> grant all on *.* to lhd@'172.16.1.%' identified by '123' with max_user_connections 1;
    Query OK, 0 rows affected (0.00 sec)
    
  2. revoke回收权限

    mysql> revoke drop on *.* from lhd@'172.16.1.%';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> show grants for lhd@'172.16.1.%';
    | Grants for lhd@172.16.1.%                                                                           
    | GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE ON *.* TO 'lhd'@'172.16.1.%' IDENTIFIED BY PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' WITH MAX_CONNECTIONS_PER_HOUR 2 MAX_USER_CONNECTIONS 1
    
    #所有权限
    SELECT, INSERT, UPDATE, DELETE, CREATE, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER, CREATE TABLESPACE, DROP, GRANT
    
  3. 授权超级管理员

    grant all on *.* to root@'172.16.1.%' identified by '123' with grant option;
    

TCL命令详解

  1. 存储引擎(show engines):存储机制决定查询效率
    查看当前存储引擎: show engines;
    其分类:
    Innodb:事务型数据库首选存储引擎,执行安全性数据库,行锁定和外键。mysql5.5之后默认使用
    Myisam:不支持事务,但是查询效率较高,5.5之前使用
    memory:数据存储在内存中,查询效率最高
  2. 事务定义:一组DML操作,要么同时成功,要么同时失败,比如转账,必须进行事务操作,否则一旦出现非正常操作引起中断就会出现钱没了;其实质为先在内存操作,全部成功再写入数据库,并提供回滚操作
    转账示例:
    #开启事务
    start transaction;
    #业务
    updata account set money = money -100 where name = ‘like’;
    updata account set money = money +100 where name = ‘lucy’;
    #提交或者回滚
    commit;/ rollback;
  3. ACID特性(事务的四大特性)
    原子性:事务内的DML操作必须作为一个整体,不可分割,同时成功或失败(原子是最小单位嘛,比喻)
    一致性:事务执行前后整体状态不变(钱总数不变)
    隔离性:并发事务之间不能产生干扰(同时存钱和消费也不该出现错误),并发事务容易产生的问题:脏读(读到未提交事务)、不可重复读(数据前后不一致)、虚读(行数前后不一致)
    持久性:事务提交之后将持久化到数据库。

视图及索引

  1. 定义:
    视图是一张虚拟表(它没有真实的物理存在。 没有结构,可以把基表的设计表和
    视图的设计视图打开看看,表的结构是框框,虚拟表是代码; 也没有存储在文件或内存中。 但它相当于表,使用方法与表相同,不过要尽量避免数据的增删改),在引用过程中动态根据基表生成。
  2. 使用场景:
    比如我想只查看一个表中的某些字段,而非全部,如果创建一个新表遍历会占用内存并且难以维护,因此引出虚拟表,不占内存,无需维护;
    安全:有些数据需要保密,创建视图可以展示非密字段
    高效:复杂的查询语句,每次查询会很耗费性能,存储到视图,每次从视图获取会提高效率
  3. 语法:
    创建视图:create view view_emp as select * from emp;
    删除视图:drop view view_emp;
  4. 注意:
    通过视图可以修改基表(因为视图的底层就是基表,操作视图实质就是基表),但是要尽量避免修改,视图只是让你看的
    在这里插入图片描述
    因此,请注意,以上三个问题都是肯定的,请勿随意更改视图!

索引

  1. 定义:
    提升查询效率创建的数据结构。默认是B-Tree索引(位图、反向….);
    遍历是有效率之分的,也就是速度不同;
    主键字段,外键字段,唯一约束修饰的字段都会自动创建索引。
  2. 查询的效率:在这里插入图片描述
    #查看扫描效率: explain指令 主要是看type和rows的区别
    #使用非主键查询 慢,整表遍历
    explain select * from emp where enaem = ‘scott’;
    在这里插入图片描述
    #使用主键查询 快,遍历一行
    explain select * from emp where empno = 7788;
    在这里插入图片描述
  3. 语法:
    ###普通索引
    #创建索引:create index index_name on tname(field1….);
    create index index_emp on emp(ename);
    #删除索引: drop index index_name on tname;
    drop index index_emp on emp;
    ###唯一索引
    #创建索引:create unique index index_name on tname(field1….);
  4. 使用场景:
    表数据量足够大(索引也是需要维护的,增删改都会对索引进行维护)
    增删改较少的表
    高基数列(基数指的就是不重复性,基数少的设置也没什么意义)
  5. 注意:
    索引需要单独维护,因此索引不宜添加过多;
    尽量将条件列设置索引;
    有些查询是不走索引的,即索引失效状况(有时sql语句不合理即使有索引也不会走索引,效率依旧低下):比如模糊查询、不等于查询就会失效
    #举例不走索引的查询,也许貌似只有等值查询才会走
    explain select * from emp where ename like ‘s%’;
    explian select * from emp where sal > 2000;

数据库设计

sql写的再好,若是数据表设计的不合理,执行效率依旧低下!

  1. 数据库设计三大范式
    1NF:所有的域必须是原子性的。即列不能再分割
    MySQL系列---实践总结_第4张图片
    2NF:所有的字段必须与主键相关,而不是部分相关(即不可只与联合主键的一部分相关),一张表中只描述一类事物
    MySQL系列---实践总结_第5张图片
    3NF:所有字段必须与主键直接相关,而不是间接相关。
    MySQL系列---实践总结_第6张图片
  2. 注意:
    其实规范的目的只是提供一个建议,希望其字段不要冗余,但其最终目的终究是为了应用,所以最终设计还是要与实际相结合,不一定三个范式必须符合(实际小票中就有sum,但确实违反第三范式),有时候就是只能违反,需要根据实际情况进行权衡
  3. 数据库设计步骤:
    A:需求分析阶段
    准确了解与分析用户的需求(困难且耗时)
    B:概念结构设计阶段
    设计数据库的E-R模型图(Entity-Relationship),确认需求信息的正确与完整
    C:逻辑设计阶段
    将E-R模型图转化为逻辑模型,一般体现为多个数据表
    D:物理设计阶段
    确定存取方法(索引的设定),确定存储结构(文件存放位置等),编写CRUD实现前端应用需求
    E:数据库实施阶段
    将数据载入,并对数据库进行调试
    F:运行及维护阶段
    使用数据库,并进行日常维护。
  4. E—R图
    定义:
    提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型
    软件:
    PowerDesigner15.1
    使用:
    百度即可。

sql语句优化

  1. select 字句中不要使用*,*是通配符,系统会一次查询所有数据,耗时
  2. 尽量不要让索引失效,如下情况会让索引失效
    MySQL系列---实践总结_第7张图片

分布式和集群

其实分布式和集群这两个概念,与mysql并没有什么联系,主要是自己深受分布式和集群这两个词的茶毒。(进入互联网缘起hadoop)

集群是个物理形态,分布式是个工作方式。

只要是一堆机器,就可以叫集群,他们是不是一起协作着干活,这个谁也不知道;一个程序或系统,只要运行在不同的机器上,就可以叫分布式,嗯,C/S架构也可以叫分布式。

集群一般是物理集中、统一管理的,而分布式系统则不强调这一点。

所以,集群可能运行着一个或多个分布式系统,也可能根本没有运行分布式系统;分布式系统可能运行在一个集群上,也可能运行在不属于一个集群的多台(2台也算多台)机器上。

分布式是相对中心式而来,强调的是任务在多个物理隔离的节点上进行。中心化带来的主要问题是可靠性,若中心节点宕机则整个系统不可用,分布式除了解决部分中心化问题,也倾向于分散负载,但分布式会带来很多的其他问题,最主要的就是一致性。
集群就是逻辑上处理同一任务的机器集合,可以属于同一机房,也可分属不同的机房。分布式这个概念可以运行在某个集群里面,某个集群也可作为分布式概念的一个节点。

一句话,就是:“分头做事”与“一堆人”的区别

所以,还有一句话—一堆人分头做事,就像一个公司的员工不就是这种状态吗,因此经常会有分布式集群这个词语。

分布式:不同的业务模块部署在不同的服务器上或者同一个业务模块分拆多个子业务,部署在不同的服务器上,解决高并发的问题

集群:同一个业务部署在多台机器上,提高系统可用性

  1. 举例一
    小饭店原来只有一个厨师,切菜洗菜备料炒菜全干。后来客人多了,厨房一个厨师忙不过来,又请了个厨师,两个厨师都能炒一样的菜,这两个厨师的关系是集群。为了让厨师专心炒菜,把菜做到极致,又请了个配菜师负责切菜,备菜,备料,厨师和配菜师的关系是分布式,一个配菜师也忙不过来了,又请了个配菜师,两个配菜师关系是集群。
  2. 举例二
    举例一是很浅显的区分,更深入一点就是,其实分布式不一定就是不同的组件,同一个组件也可以,关键在于是否通过交换信息的方式进行协作。比如说Zookeeper的节点都是对等的,但它自己就构成一个分布式系统。也就是说,分布式是指通过网络连接的多个组件,通过交换信息协作而形成的系统。而集群,是指同一种组件的多个实例,形成的逻辑上的整体。可以看出这两个概念并不完全冲突,分布式系统也可以是一个集群,例子就是前面说的zookeeper等,它的特征是服务之间会互相通信协作。是分布式系统不是集群的情况,就是多个不同组件构成的系统;是集群不是分布式系统的情况,比如多个经过负载均衡的HTTP服务器,它们之间不会互相通信,如果不带上负载均衡的部分的话,一般不叫做分布式系统。

读写分离-并行度

感觉读写分离更像是集群。其实他就是个集群!

  1. 什么是读写分离?为什么要有读写分离?

    数据库一般 请求 2000/s,大于该请求就会出现磁盘高,性能瓶颈。随着用户的增多,数据的增多,单机的数据库往往支撑不住快速发展的业务,所以数据库集群就产生了!

    且大部分情景是读大于写

    因此就有了读写分离,读写分离顾名思义就是读和写分离了,对应到数据库集群一般都是一主一从(一个主库,一个从库)或者一主多从(一个主库,多个从库),业务服务器把需要写的操作都写到主数据库中,读的操作都去从库查询。主库会同步数据到从库保证数据的一致性。这样可以支持更高的并发。

    这种集群方式的本质就是把访问的压力从主库转移到从库,适合读的请求较多的情况下。

    在单机的情况下,一般我们做数据库优化都会加索引,但是加了索引对查询有优化,但是会影响写入,因为写入数据会更新索引。所以做了主从之后,我们可以单独的针对从库(读库)做索引上的优化,而主库(写库)可以减少索引而提高写的效率。
    MySQL系列---实践总结_第8张图片
    看起来还是很简单的,但是有两点要注意:主从同步延迟、分配机制的考虑;

  2. 如何实现读写分离?

    主库将变更写到binlog日志,然后从库连接到主库之后,从库有一个IO线程,将主库的binlog日志拷贝到自己的relay log中,从库的SQL线程从relay log中读取binlog,然后执行binlog日志中的内容,在自己本地在执行一遍SQL,这样就完成了从库和主库的数据是一样的。

  3. 主从复制原理
    MySQL系列---实践总结_第9张图片
    1.主库对所有DDL和DML产生的日志写进binlog;
    2.主库生成一个 log dump 线程,用来给从库I/O线程读取binlog;
    3.从库的I/O Thread去请求主库的binlog,并将得到的binlog日志写到relay log文件中;
    4.从库的SQL Thread会读取relay log文件中的日志解析成具体操作,将主库的DDL和DML操作事件重放。

  4. Mysql主从同步的延时问题?

    现象:从库同步主库数据的过程是串行化的,主库是并行的操作,在从库上会串行(顺序)执行。由于从库从主库拷贝日志然后串行执行SQL的特点,在高并发场景下,从库的数据一定会比主库慢一些,有延时,写入的主库数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。

    1.MySQL的主从复制都是单线程的操作,主库对所有DDL和DML产生的日志写进binlog,由于binlog是顺序写,所以效率很高。Slave的SQL Thread线程将主库的DDL和DML操作事件在slave中重放。DML和DDL的IO操作是随即的,不是顺序的,成本高很多。

    2.由于SQL Thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL Thread所能处理的速度,或者当slave中有大型query语句产生了锁等待那么延时就产生了。

    常见原因:Master负载过高、Slave负载过高、网络延迟、机器性能太低、MySQL配置不合理。

  5. 主从延迟排查方法?

    通过监控 show slave status 命令输出的Seconds_Behind_Master参数的值来判断:

    NULL:表示io_thread或是sql_thread有任何一个发生故障;
    0:该值为零,表示主从复制良好;
    正值:表示主从已经出现延时,数字越大表示从库延迟越严重。
    MySQL系列---实践总结_第10张图片

  6. 解决从库复制延迟的问题?
    极可能减少延迟时间:
    1.优化网络
    2.升级Slave硬件配置
    3.Slave调整参数,关闭binlog,修改innodb_flush_log_at_trx_commit参数值
    4.升级MySQL版本到5.7,使用并行复制:从库开启多个线程,并行读取relay log中不同库的日志,然后并行重放不同库的日志,这就是库级别(基于库的)的并行
    无论如何都会有延迟,因此还有一个方向是面向业务:
    1.二次读取:意思就是读从库没读到之后再去主库读一下
    2.写之后的马上的读操作访问主库
    3.关键业务读写都由主库承担,非关键业务读写分离

  7. 主库突然宕机日志没有写到binlog(数据丢失问题)?

    主库突然宕机日志没有写到binlog,那么主库就丢失了这条数据如何解决?
    半同步复制(semi-sync复制):主库写入binlog日志之后,强制立即将数据同步到从库,从库将日志写入本地的relay log之后,返回一个ack给主库,主库收到从库ack请求(需要等待至少一个从库接收到并写到relay log中才返回结果给客户端),才认为是写操作完成了。 半同步复制提高了数据的安全性,同时它也造成了一个TCP/IP往返耗时的延迟。(所以写库失败,需要重试解决丢失数据问题)

  8. relay-log(中继日志)
    存储所有主库TP过来的binlog事件主库binlog,记录主库发生过的修改事件。

分库分表-存储量

主从集群也就是读写分离,其实只是分担了访问的压力,但是存储的压力没有解决。分库分表就是用来解决存储压力的。

  1. 为什么分离分表?
    表数据量达到上千万级别,数据库磁盘消耗高,QPS响应慢,需要通过分库分表来优化

  2. 分库分表中间件
    中间件有client类型,proxy类型
    MySQL系列---实践总结_第11张图片
    MySQL系列---实践总结_第12张图片

  3. 什么是垂直分和水平分?
    MySQL系列---实践总结_第13张图片
    MySQL系列---实践总结_第14张图片
    MySQL系列---实践总结_第15张图片
    MySQL系列---实践总结_第16张图片

  4. 如何平滑的执行分库分表?
    1.启动多个线程读取原始数据到数据中间件中
    2.双写迁移方案:
    MySQL系列---实践总结_第17张图片
    参考:mysql分表的3种方法

  5. 分库分表之后全局唯一ID如何生成(主键自增ID)?
    1.全局表:生成主键ID的全局数据库A,每次都去数据库A里面得到主键ID,然后再去其他数据库insert 数据。瓶颈:单库,适用于 并发量小,数据大的情况,每秒最高并发最大为几百的合适。

    2.uuid :本地生成,不基于数据库,存储空间大,字段太长,做为主键性能差

    3.利用redis的incr原子性操作自增,可以一次增长incrby order 100,一次申请100个自增ID,然后自己在内存通过自增实现,减少占用内存请求资源

    4.MySQL多实例主键自增 :优点:解决了单点问题,缺点:确定了步长 无法再次扩容
    MySQL系列---实践总结_第18张图片
    5.雪花算法(snowflake) :整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。缺点:强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

  6. 雪花算法
    SnowFlake算法生成id的结果是一个64bit大小的整数
    ①ID能够按照时间有序生成
    ②生成id的结果是一个64bit大小的整数,为一个Long型(转换成字符串后长度最多19)
    ③分布式系统内不会产生ID碰撞(由datacenter和workId作区分)并且效率较高
    可以手动指定每个区间的占用位置的大小,工作机器ID为serverID,java自带算法,无需依赖外部jar
    根据业务场景可以灵活调整Bit位划分,灵活读高
    缺点:
    依赖于机器时钟ID,如果时钟回拨会导致生成重复,基于的算法,发现时钟回拨会报异常,导致服务不可用
    MySQL系列---实践总结_第19张图片
    1.1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。

    2.41bit-时间戳,用来记录时间戳,毫秒级。

    • 41位可以表示2^{41}-1个数字,
    • 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 2^{41}-1,减1是因为可表示的数值范围是从0开始算的,而不是1。
    • 也就是说41位可以表示2{41}-1个毫秒的值,转化成单位年则是(2{41}-1) / (1000 * 60 * 60 * 24 *365) = 69年

    3.10bit-工作机器id,用来记录工作机器id。

    • 可以部署在2^{10} = 1024个节点,包括5位datacenterId和5位workerId
    • 5位(bit)可以表示的最大正整数是2^{5}-1 = 31,即可以用0、1、2、3、…31这32个数字,来表示不同的datecenterId或workerId

    4.12bit-序列号,序列号,用来记录同毫秒内产生的不同id。

    • 12位(bit)可以表示的最大正整数是2^{12}-1 = 4095,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

分库产生的问题

假设数据库中有两张表分别是用户表和订单表。如果要分库的话现在你需要买两台机子,搞两个数据库分别放在两台机子上,并且一个数据库放用户表,一个数据库放订单表。
MySQL系列---实践总结_第20张图片
这样存储压力就分担到两个服务器上了,但是会带来新的问题,所以东西变复杂了都会有新的问题产生。。

1.联表查询

也就是join了,之前在一个数据库里面可以用上join用一条sql语句就可以联表查询得到想要的结果,但是现在分为多个数据库了,所以join用不上了。就比如现在要查注册时间在2019年之后用户的订单信息,你就需要先去数据库A中用户表查询注册在2019年之后的信息,然后得到用户id,再拿这些id去数据库B订单表中查找订单信息,然后再拼接这些信息返回。所以等于得多写一些代码了。

在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上,这时,表的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表,结果原本一次查询能够完成的业务,可能需要多次查询才能完成。

2.事务问题

搞数据库基本上都离不开事务,但是现在不同的数据库事务就不是以前那个简单的本地事务了,而是分布式事务了,而引入分布式事务也提高了系统的复杂性。

在执行分库分表之后,由于数据存储到了不同的库上,数据库事务管理出现了困难。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。

3 额外的数据管理负担和数据运算压力。

额外的数据管理负担,最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些都可以通过应用程序解决,但必然引起额外的逻辑运算,例如,对于一个记录用户成绩的用户数据表userTable,业务要求查出成绩最好的100位,在进行分表之前,只需一个order by语句就可以搞定,但是在进行分表之后,将需要n个order by语句,分别查出每一个分表的前100名用户数据,然后再对这些数据进行合并计算,才能得出结果。

分表产生的问题

1.垂直分表

比如我们表有10列,现在一刀切下去,分成了两张表,其中一张表3列,另一张表7列。

这个一刀切下去让两个表分别有几列不是固定的,垂直分表适合表中存在不常用并且占用了大量空间的字段拆分出去。

就拿头条的用户信息,比如用户表只有用户id、昵称、手机号、个人简介这4个字段。但是手机号和个人简介这种信息就属于不太常用的,占用的空间也不小,个人简介有些人写了一坨。所以就把手机号和个人简介这两列拆分出去。

那垂直分表影响就是之前只要一个查询的,现在需要两次查询才能拿到分表之前的完整用户表信息。

2.水平分表

比如现在用户表有5000万行数据,我们切5刀,分成5个表,每个表1000万行数据。

水平分表就适合用户表行数很多的情况下,一般单表行数超过5000万就得分表,如果单表的数据比较复杂那可能2000万甚至1000万就得分了,这个得看实际情况有些表很简单可能一亿行都不用分。所以当一个表行数超过千万级别的时候关注一下,如果没有性能问题就可以再等等看,不要急着分表,因为分表会是带来很多问题。

水平分表的问题比垂直分表就更烦了。

要考虑怎么切,讲的高级点就叫路由:

  1. 按id(也就是范围路由)

    比如id 值1-999万的放一张表,1000万-1999万放一张表,一次类推(也就是一段一段)。这个得试的,因为范围分的大了,可能性能还有问题,范围分的小了。。那表不得多死。

    这种分法的好处就是容易切啊,简单粗暴,以后新增的数据分表都不会影响到之前的数据,之前的数据都不需要移动。

  2. 哈希路由

    就是取几列哈希一下看看数据哪个库,比如拿id来做哈希,1500取余8等于4,所以这条记录就放在user_4这个表中,2011取余8等于3,所以这条记录就放在user_3中。这种分法好处就是分的很均匀,基本上每个表的数据都差不多,但是以后新增数据又得分表了咋办,以前的数据都得动,比较烦!

  3. 搞一张表来存储路由关系

    还是拿用户表来说,就是弄一个路由表,里面存userId和表编号,表示这个userId是这张user表的的。这种方式也简单,之后又要分表了之后改改路由表,迁移一部分数据。但是这种方法导致每次查询都得查两次,并且如果路由表太大了,那路由表又成为瓶颈了!

分区表

  1. 什么是分区表?
    mysql数据库中的数据是以文件的形势存在磁盘上的,默认放在/mysql/data下面(可以通过my.cnf中的datadir来查看),一张表主要对应着三个文件,一个是frm存放表结构的,一个是myd存放表数据的,一个是myi存表索引的。如果一张表的数据量太大的话,那么myd,myi就会变的很大,查找数据就会变的很慢,这个时候我们可以利用mysql的分区功能,在物理上将这一张表对应的三个文件,分割成许多个小块,这样呢,我们查找一条数据时,就不用全部查找了,只要知道这条数据在哪一块,然后在那一块找就行了。如果表的数据太大,可能一个磁盘放不下,这个时候,我们可以把数据分配到不同的磁盘里面去。

    分区表,是指根据一定规则,将数据库中的一张表分解成多个更小的,容易管理的部分。从逻辑上看,只有一张表,但是底层却是由多个物理分区组成。且把一张表的数据分成N多个区块,这些区块可以在同一个磁盘上,也可以在不同的磁盘上。

    其实,在MySQL中,InnoDB存储引擎长期支持表空间的概念,并且MySQL服务器甚至在分区引入之前,就能配置为存储不同的数据库使用不同的物理路径,而分区表又把这个概念推进了一步,它允许根据可以设置为任意大小的规则,跨文件系统分配单个表的多个部分。

  2. 表分区与分表的区别

    分表:指的是通过一定规则,将一张表分解成多张不同的表。比如将用户订单记录根据时间成多个表。 分表与分区的区别在于:分区从逻辑上来讲只有一张表,而分表则是将一张表分解成多张表。

  3. 表分区有什么好处?

    (1)、与单个磁盘或文件系统分区相比,可以存储更多的数据。

    (2)、对于那些已经失去保存意义的数据,通常可以通过删除与那些数据有关的分区,很容易地删除那些数据。相反地,在某些情况下,添加新数据的过程又可以通过为那些新数据专门增加一个新的分区,来很方便地实现。

    (3)、一些查询可以得到极大的优化,这主要是借助于满足一个给定WHERE语句的数据可以只保存在一个或多个分区内,这样在查找时就不用查找其他剩余的分区。因为分区可以在创建了分区表后进行修改,所以在第一次配置分区方案时还不曾这么做时,可以重新组织数据,来提高那些常用查询的效率。

    (4)、涉及到例如SUM()和COUNT()这样聚合函数的查询,可以很容易地进行并行处理。这种查询的一个简单例子如 “SELECT salesperson_id, COUNT (orders) as order_total FROM sales GROUP BY salesperson_id;”。通过“并行”,这意味着该查询可以在每个分区上同时进行,最终结果只需通过总计所有分区得到的结果。

    (5)、通过跨多个磁盘来分散数据查询,来获得更大的查询吞吐量。

  4. 分区表的限制因素

    (1)、一个表最多只能有1024个分区。

    (2)、 MySQL5.1中,分区表达式必须是整数,或者返回整数的表达式。在MySQL5.5中提供了非整数表达式分区的支持。

    (3)、如果分区字段中有主键或者唯一索引的列,那么多有主键列和唯一索引列都必须包含进来。即:分区字段要么不包含主键或者索引列,要么包含全部主键和索引列。

    (4)、分区表中无法使用外键约束。

    (5)、MySQL的分区适用于一个表的所有数据和索引,不能只对表数据分区而不对索引分区,也不能只对索引分区而不对表分区,也不能只对表的一部分数据分区。

  5. 如何判断当前MySQL是否支持分区

    mysql> show variables like '%partition%';
    +-------------------+-------+
    | Variable_name     | Value |
    +-------------------+-------+
    | have_partitioning | YES   |
    +-------------------+-------+
    1 row in set (0.00 sec)
    

    have_partintioning 的值为YES,表示支持分区。

  6. MySQL支持的分区类型有哪些?

    (1)、RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区。

    (2)、LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。

    (3)、HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。

    (4)、KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。

    说明:在MySQL5.1版本中,RANGE,LIST,HASH分区要求分区键必须是INT类型,或者通过表达式返回INT类型。但KEY分区的时候,可以使用其他类型的列(BLOB,TEXT类型除外)作为分区键。

  7. RANGE分区

    根据范围分区,范围应该连续但是不重叠,使用PARTITION BY RANGE, VALUES LESS THAN关键字。不使用COLUMNS关键字时RANGE括号内必须为整数字段名或返回确定整数的函数。

    使用RANGE分区时必须注意,每个分区都是按顺序进行定义,从最低到最高。这是PARTITION BY RANGE 语法的要求;在这点上,它类似于C或Java中的“switch … case”语句。所以这就是为什么明明所有语句都可以满足却仍然分区有序,因为range分区有它自己的逻辑。

    7.1. 根据数值范围

    drop table if exists employees;
    create table employees(
        id int not null,
        fname varchar(30),
        lname varchar(30),
        hired date not null default '1970-01-01',
        separated date not null default '9999-12-31',
        job_code int not null default 0,
        store_id int not null default 0
    )engine=myisam default charset=utf8
    partition by range(store_id)(
        partition p0 values less than (6),
        partition p1 values less than (11),
        partition p2 values less than (16),
        partition p3 values less than (21)
    );
    
    insert into employees (id,fname,lname,hired,store_id) values(1,'张三','张','2015-05-04',1);
    insert into employees (id,fname,lname,hired,store_id) values(2,'李四','李','2016-10-01',5);
    insert into employees (id,fname,lname,hired,store_id) values(3,'王五','王','2016-11-14',10);
    insert into employees (id,fname,lname,hired,store_id) values(4,'赵六','赵','2017-08-24',15);
    insert into employees (id,fname,lname,hired,store_id) values(5,'田七','田','2018-05-20',20);
    

    MySQL系列---实践总结_第21张图片
    按照这种分区方案,在商店1到5工作的雇员相对应的所有行被保存在分区P0中,商店6到10的雇员保存在P1中,依次类推。注意,每个分区都是按顺序进行定义,从最低到最高。这是PARTITION BY RANGE 语法的要求。

    对于包含数据(6,‘亢八’,‘亢’,‘2018-06-24’,13)的一个新行,可以很容易地确定它将插入到p2分区中。

    insert into employees (id,fname,lname,hired,store_id) values(6,'亢八','亢','2018-06-24',13);
    

    MySQL系列---实践总结_第22张图片

    但是如果增加了一个编号为第21的商店(7,‘周九’,‘周’,‘2018-07-24’,21),将会发生什么呢?在这种方案下,由于没有规则把store_id大于20的商店包含在内,服务器将不知道把该行保存在何处,将会导致错误。

    insert into employees (id,fname,lname,hired,store_id) values(7,'周九','周','2018-07-24',21);
    
    ERROR 1526 (HY000): Table has no partition for value 21
    

    要避免这种错误,可以通过在CREATE TABLE语句中使用一个“catchall” VALUES LESS THAN子句,该子句提供给所有大于明确指定的最高值的值:

    create table employees(
        id int not null,
        fname varchar(30),
        lname varchar(30),
        hired date not null default '1970-01-01',
        separated date not null default '9999-12-31',
        job_code int not null default 0,
        store_id int not null default 0
    )engine=myisam default charset=utf8
    partition by range(store_id)(
        partition p0 values less than (6),
        partition p1 values less than (11),
        partition p2 values less than (16),
        partition p3 values less than (21),
      partition p4 values less than MAXVALUE 
    );
    

    7.2. 根据TIMESTAMP范围

    drop table if exists quarterly_report_status;
    create table quarterly_report_status(
      report_id int not null,
      report_status varchar(20) not null,
      report_updated timestamp not null default current_timestamp on update current_timestamp
    )
    partition by range(unix_timestamp(report_updated))(
      partition p0 values less than (unix_timestamp('2008-01-01 00:00:00')),
      partition p1 values less than (unix_timestamp('2008-04-01 00:00:00')),
      partition p2 values less than (unix_timestamp('2008-07-01 00:00:00')),
      partition p3 values less than (unix_timestamp('2008-10-01 00:00:00')),
      partition p4 values less than (unix_timestamp('2009-01-01 00:00:00')),
      partition p5 values less than (unix_timestamp('2009-04-01 00:00:00')),
      partition p6 values less than (unix_timestamp('2009-07-01 00:00:00')),
      partition p7 values less than (unix_timestamp('2009-10-01 00:00:00')),
      partition p8 values less than (unix_timestamp('2010-01-01 00:00:00')),
      partition p9 values less than maxvalue
    );
    

    7.3. 根据DATE、DATETIME范围

    添加COLUMNS关键字可定义非integer范围及多列范围,不过需要注意COLUMNS括号内只能是列名,不支持函数;多列范围时,多列范围必须呈递增趋势:

    drop table if exists member;
    create table member(
      firstname varchar(25) not null,
      lastname varchar(25) not null,
      username varchar(16) not null,
      email varchar(35),
      joined date not null
    )
    partition by range columns(joined)(
      partition p0 values less than ('1960-01-01'),
      partition p1 values less than ('1970-01-01'),
      partition p2 values less than ('1980-01-01'),
      partition p3 values less than ('1990-01-01'),
      partition p4 values less than maxvalue
    )
    

    7.4. 根据多列范围

    drop table if exists rc3;
    create table rc3(
      a int,
      b int
    )
    partition by range columns(a,b)(
      partition p0 values less than (0,10),
      partition p1 values less than (10,20),
      partition p2 values less than (20,30),
      partition p3 values less than (30,40),
      partition p4 values less than (40,50),
      partition p5 values less than (maxvalue,maxvalue)
    )
    

    7.5. RANGE分区在以下场合特别有用

    drop table if exists staff;
    create table staff(
      id int not null,
      fname varchar(30),
      lname varchar(30),
      hired date not null default '1970-01-01',
      separated date not null default '9999-12-31',
      job_code int not null default 0,
      store_id int not null default 0
    )engine=myisam default charset=utf8
    partition by range(year(separated))(
      partition p0 values less than (1991),
      partition p1 values less than (1996),
      partition p2 values less than (2001),
      partition p4 values less than MAXVALUE
    );
    

    (1)、当需要删除一个分区上的“旧的”数据时,只删除分区即可。如果你使用上面最近的那个例子给出的分区方案,你只需简单地使用”alter table staff drop partition p0;”来删除所有在1991年前就已经停止工作的雇员相对应的所有行。对于有大量行的表,这比运行一个如”delete from staff WHERE year(separated) <= 1990;”这样的一个DELETE查询要有效得多。

    (2)、想要使用一个包含有日期或时间值,或包含有从一些其他级数开始增长的值的列。

    (3)、经常运行直接依赖于用于分割表的列的查询。例如,当执行一个如”select count(*) from staff where year(separated) = 200 group by store_id;”这样的查询时,MySQL可以很迅速地确定只有分区p2需要扫描,这是因为余下的分区不可能包含有符合该WHERE子句的任何记录。

  8. LIST分区

    LIST分区在很多方面类似于RANGE分区。和按照RANGE分区一样,每个分区必须明确定义。它们的主要区别在于,LIST分区中每个分区的定义和选择是基于某列的值从属于一个值列表集中的一个值,而RANGE分区是从属于一个连续区间值的集合。LIST分区通过使用“PARTITION BY LIST(expr)”来实现,其中“expr” 是某列值或一个基于某个列值、并返回一个整数值的表达式,然后通过“VALUES IN (value_list)”的方式来定义每个分区,其中“value_list”是一个通过逗号分隔的整数列表。

    现在看来 兑换系统的flow表应该使用list分区,而不是range分区。但是很多事情,并不是错了就要懊悔,认识错误只是为了后续不再犯同样的错误,而非懊悔

    不使用COLUMNS关键字时List括号内必须为整数字段名或返回确定整数的函数。

    假定有20个音像店,分布在4个有经销权的地区,如下表所示:

    ====================

    地区 商店ID 号

    北区 3, 5, 6, 9, 17

    东区 1, 2, 10, 11, 19, 20

    西区 4, 12, 13, 14, 18

    中心区 7, 8, 15, 16

    drop table if exists staff;
    create table staff(
      id int not null,
      fname varchar(30),
      lname varchar(30),
      hired date not null default '1970-01-01',
      separated date not null default '9999-12-31',
      job_code int not null default 0,
      store_id int not null default 0
    )
    partition by list(store_id)(
      partition pNorth values in (3,5,6,9,17),
      partition pEast values in (1,2,10,11,19,20),
      partition pWest values in (4,12,13,14,18),
      partition pCentral values in (7,8,15,16)
    );
    

    这使得在表中增加或删除指定地区的雇员记录变得容易起来。例如,假定西区的所有音像店都卖给了其他公司。那么与在西区音像店工作雇员相关的所有记录(行)可以使用查询“ALTER TABLE staff DROP PARTITION pWest;”来进行删除,它与具有同样作用的DELETE(删除)“DELETE FROM staff WHERE store_id IN (4,12,13,14,18);”比起来,要有效得多。

    如果试图插入列值(或分区表达式的返回值)不在分区值列表中的一行时,那么“INSERT”查询将失败并报错。

    当插入多条数据出错时,如果表的引擎支持事务(Innodb),则不会插入任何数据;如果不支持事务,则出错前的数据会插入,后面的不会执行。

    与Range分区相同,添加COLUMNS关键字可支持非整数和多列。LIST分区除了能和RANGE分区结合起来生成一个复合的子分区,与HASH和KEY分区结合起来生成复合的子分区也是可能的

  9. HASH分区

    HASH分区主要用来确保数据在预先确定数目的分区中平均分布。在RANGE和LIST分区中,必须明确指定一个给定的列值或列值集合应该保存在哪个分区中;而在HASH分区中,MySQL 自动完成这些工作,你所要做的只是基于将要被哈希的列值指定一个列值或表达式,以及指定被分区的表将要被分割成的分区数量。

    要使用HASH分区来分割一个表,要在CREATE TABLE 语句上添加一个“PARTITION BY HASH (expr)”子句,其中“expr”是一个返回一个整数的表达式。它可以仅仅是字段类型为MySQL 整型的一列的名字。此外,你很可能需要在后面再添加一个“PARTITIONS num”子句,其中num 是一个非负的整数,它表示表将要被分割成分区的数量。
    Hash括号内只能是整数列或返回确定整数的函数,实际上就是使用返回的整数对分区数取模。
    如果没有包括一个PARTITIONS子句,那么分区的数量将默认为1。 只有一种例外: 对于NDB Cluster(簇)表,默认的分区数量将与簇数据节点的数量相同,这种修正可能是考虑任何MAX_ROWS 设置,以便确保所有的行都能合适地插入到分区中。

    drop table if exists staff;
    create table staff(
      id int not null,
      fname varchar(30),
      lname varchar(30),
      hired date not null default '1970-01-01',
      separated date not null default '9999-12-31',
      job_code int not null default 0,
      store_id int not null default 0
    )
    partition by hash(store_id)
    partitions 4;
    
    drop table if exists staff;
    create table staff(
      id int not null,
      fname varchar(30),
      lname varchar(30),
      hired date not null default '1970-01-01',
      separated date not null default '9999-12-31',
      job_code int not null default 0,
      store_id int not null default 0
    )
    partition by hash(year(hired))
    partitions 4;
    

    “expr”还可以是MySQL 中有效的任何函数或其他表达式,只要它们返回一个既非常数、也非随机数的整数。(换句话说,它既是变化的但又是确定的)。但是应当记住,每当插入或更新(或者可能删除)一行,这个表达式都要计算一次;这意味着非常复杂的表达式可能会引起性能问题,尤其是在执行同时影响大量行的运算(例如批量插入)的时候。

    最有效率的哈希函数是只对单个表列进行计算,并且它的值随列值进行一致地增大或减小,因为这考虑了在分区范围上的“修剪”。也就是说,表达式值和它所基于的列的值变化越接近,MySQL就可以越有效地使用该表达式来进行HASH分区。

    例如,“date_col” 是一个DATE(日期)类型的列,那么表达式TO_DAYS(date_col)就可以说是随列“date_col”值的变化而发生直接的变化,因为列“date_col”值的每个变化,表达式的值也将发生与之一致的变化。而表达式YEAR(date_col)的变化就没有表达式TO_DAYS(date_col)那么直接,因为不是列“date_col”每次可能的改变都能使表达式YEAR(date_col)发生同等的改变。即便如此,表达式YEAR(date_col)也还是一个用于 哈希函数的、好的候选表达式,因为它随列date_col的一部分发生直接变化,并且列date_col的变化不可能引起表达式YEAR(date_col)不成比例的变化。

    作为对照,假定有一个类型为整型(INT)的、列名为“int_col”的列。现在考虑表达式“POW(5-int_col,3) + 6”。这对于哈希函数就是一个不好的选择,因为“int_col”值的变化并不能保证表达式产生成比例的变化。列 “int_col”的值发生一个给定数目的变化,可能会引起表达式的值产生一个很大不同的变化。例如,把列“int_col”的值从5变为6,表达式的值将产生“-1”的改变,但是把列“int_col”的值从6变为7时,表达式的值将产生“-7”的变化。

    换句话说,如果列值与表达式值之比的曲线图越接近由等式“y=nx(其中n为非零的常数)描绘出的直线,则该表达式越适合于 哈希。这是因为,表达式的非线性越严重,分区中数据产生非均衡分布的趋势也将越严重。

  10. LINEAR HASH分区
    Hash分区也存在与传统Hash分表一样的问题,可扩展性差。MySQL也提供了一个类似于一致Hash的分区方法-线性Hash分区,只需要在定义分区时添加LINEAR关键字。

    drop table if exists staff;
    create table staff(
      id int not null,
      fname varchar(30),
      lname varchar(30),
      hired date not null default '1970-01-01',
      separated date not null default '9999-12-31',
      job_code int not null default 0,
      store_id int not null default 0
    )
    partition by linear hash(year(hired))
    partitions 4;
    

    线性哈希分区和常规哈希分区在语法上的唯一区别在于,在“PARTITION BY” 子句中添加“LINEAR”关键字,它与常规哈希的区别在于,线性哈希功能使用的一个线性的2的幂(powers-of-two)运算法则,而常规哈希使用的是求哈希函数值的模数。

    MySQL系列---实践总结_第23张图片
    按照线性哈希分区的优点在于增加、删除、合并和拆分分区将变得更加快捷,有利于处理含有极其大量(1000吉)数据的表。它的缺点在于,与使用常规HASH分区得到的数据分布相比,各个分区间数据的分布不大可能均衡。

  11. KEY分区

    按照KEY进行分区类似于按照HASH分区,除了HASH分区使用的用户定义的表达式,而KEY分区的 哈希函数是由MySQL 服务器提供。MySQL 簇(Cluster)使用函数MD5()来实现KEY分区;对于使用其他存储引擎的表,服务器使用其自己内部的 哈希函数,这些函数是基于与PASSWORD()一样的运算法则。

    drop table if exists staff;
    create table staff(
      id int not null,
      fname varchar(30),
      lname varchar(30),
      hired date not null default '1970-01-01',
      separated date not null default '9999-12-31',
      job_code int not null default 0,
      store_id int not null default 0
    )
    partition by key(store_id)
    partitions 4;
    

    在KEY分区中使用关键字LINEAR和在HASH分区中使用具有同样的作用,分区的编号是通过2的幂(powers-of-two)算法得到,而不是通过模数算法。

    另外,当表存在主键或唯一索引时可省略Key括号内的列名,Mysql将按照主键-唯一索引的顺序选择,当找不到唯一索引时报错。

  12. 子分区
    子分区是分区表中每个分区的再次分割。例如,考虑下面的CREATE TABLE 语句:

    CREATE TABLE ts (id INT, purchased DATE)
        PARTITION BY RANGE(YEAR(purchased))
        SUBPARTITION BY HASH(TO_DAYS(purchased))
        SUBPARTITIONS 2
        (
            PARTITION p0 VALUES LESS THAN (1990),
            PARTITION p1 VALUES LESS THAN (2000),
            PARTITION p2 VALUES LESS THAN MAXVALUE
        )// 表ts 有3个RANGE分区。这3个分区中的每一个分区——p0, p1, 和 p2 ——又被进一步分成了2个子分区。实际上,整个表被分成了3 * 2 = 6个分区。但是,由于PARTITION BY RANGE子句的作用,这些分区的头2个只保存“purchased”列中值小于1990的那些记录。
    

    子分区可以用于特别大的表,在多个磁盘间分配数据和索引。假设有6个磁盘,分别为/disk0, /disk1, /disk2等。现在考虑下面的例子:

    CREATE TABLE ts (id INT, purchased DATE)
        PARTITION BY RANGE(YEAR(purchased))
        SUBPARTITION BY HASH(TO_DAYS(purchased))
        (
            PARTITION p0 VALUES LESS THAN (1990)
            (
                SUBPARTITION s0 
                    DATA DIRECTORY = '/disk0/data' 
                    INDEX DIRECTORY = '/disk0/idx',
                SUBPARTITION s1 
                    DATA DIRECTORY = '/disk1/data' 
                    INDEX DIRECTORY = '/disk1/idx'
            ),
            PARTITION p1 VALUES LESS THAN (2000)
            (
                SUBPARTITION s0 
                    DATA DIRECTORY = '/disk2/data' 
                    INDEX DIRECTORY = '/disk2/idx',
                SUBPARTITION s1 
                    DATA DIRECTORY = '/disk3/data' 
                    INDEX DIRECTORY = '/disk3/idx'
            ),
            PARTITION p2 VALUES LESS THAN MAXVALUE
            (
                SUBPARTITION s0 
                    DATA DIRECTORY = '/disk4/data' 
                    INDEX DIRECTORY = '/disk4/idx',
                SUBPARTITION s1 
                    DATA DIRECTORY = '/disk5/data' 
                    INDEX DIRECTORY = '/disk5/idx'
            )
        )
  13. 分表处理NULL值的方式

    MySQL 中的分区在禁止空值(NULL)上没有进行处理,无论它是一个列值还是一个用户定义表达式的值。一般而言,在这种情况下MySQL 把NULL视为0。如果你希望回避这种做法,你应该在设计表时不允许空值;最可能的方法是,通过声明列“NOT NULL”来实现这一点。

    如果插入一行到按照RANGE或LIST分区的表,该行用来确定分区的列值为NULL,分区将把该NULL值视为0。

分区表与分表的区别

1.实现方式上

  1. mysql的分表是真正的分表,一张表分成很多表后,每一个小表都是完正的一张表,都对应三个文件,一个.MYD数据文件,.MYI索引文件,.frm表结构文件。

    [root@BlackGhost test]# ls |grep user
    alluser.MRG
    alluser.frm
    user1.MYD
    user1.MYI
    user1.frm
    user2.MYD
    user2.MYI
    user2.frm
    

    简单说明一下,上面的分表呢是利用了merge存储引擎(分表的一种),alluser是总表,下面有二个分表,user1,user2。他们二个都是独立 的表,取数据的时候,我们可以通过总表来取。这里总表是没有.MYD,.MYI这二个文件的,也就是说,总表他不是一张表,没有数据,数据都放在分表里面。我们来看看.MRG到底是什么东西

    [root@BlackGhost test]# cat alluser.MRG |more
    user1
    user2
    #INSERT_METHOD=LAST 
    

    从上面我们可以看出,alluser.MRG里面就存了一些分表的关系,以及插入数据的方式。可以把总表理解成一个外壳,或者是联接池。

  2. 分区不一样,一张大表进行分区后,他还是一张表,不会变成二张表,但是他存放数据的区块变多了。

    Sql代码
    [root@BlackGhost test]# ls |grep aa
    aa#P#p1.MYD
    aa#P#p1.MYI
    aa#P#p3.MYD
    aa#P#p3.MYI
    aa.frm
    aa.par
    

    从 上面我们可以看出,aa这张表,分为二个区,p1和p3,本来是三个区,被我删了一个区。我们都知道一张表对应三个文件.MYD,.MYI,.frm。分 区呢根据一定的规则把数据文件和索引文件进行了分割,还多出了一个.par文件,打开.par文件后你可以看出他记录了,这张表的分区信息,根分表中 的.MRG有点像。分区后,还是一张,而不是多张表。
    如orderid,userid,ordertime,…
    ordertime<2015-01-01 #p0
    ordertime<2015-04-01 #p1
    ordertime<2015-07-01 #p2
    ordertime<2015-10-01 #p3
    ordertime<2016-01-01 #p4
    按照时间分区。大部分只查询最近的订单数据,那么大部分只访问一个分区,比整个表小多了,数据库可以更加好的缓存,性能也提高了。这个是数据库分的,应用程序透明,无需修改。

2.数据处理上

  1. 分表后,数据都是存放在分表里,总表只是一个外壳,存取数据发生在一个一个的分表里面。看下面的例子:

    select * from alluser where id='12’表面上看,是对表alluser进行操作的,其实不是的。是对alluser里面的分表进行了操作。

  2. 分区后,不存在分表的概念,分区只不过把存放数据的文件分成了许多小块,分区后的表呢,还是一张表。数据处理还是由自己来完成。

3.提高性能上

  1. 分表后,单表的并发能力提高了,磁盘I/O性能也提高了。并发能力为什么提高了呢,因为查寻一次所花的时间变短了,如果出现高并发的话,总表可以根据不同 的查询,将并发压力分到不同的小表里面。磁盘I/O性能怎么搞高了呢,本来一个非常大的.MYD文件现在也分摊到各个小表的.MYD中去了。

  2. mysql提出了分区的概念,我觉得就想突破磁盘I/O瓶颈,想提高磁盘的读写能力,来增加mysql性能。
    在这一点上,分区和分表的测重点不同,分表重点是存取数据时,如何提高mysql并发能力上;而分区呢,如何突破磁盘的读写能力,从而达到提高mysql性能的目的。

4.实现的难易度上

  1. 分表的方法有很多,用merge来分表,是最简单的一种方式。这种方式根分区难易度差不多,并且对程序代码来说可以做到透明的。如果是用其他分表方式就比分区麻烦了。

  2. 分区实现是比较简单的,建立分区表,根建平常的表没什么区别,并且对开代码端来说是透明的。

分区表与分表的联系

  1. 都能提高mysql的性高,在高并发状态下都有一个良好的表面。

  2. 分表和分区不矛盾,可以相互配合的,对于那些大访问量,并且表数据比较多的表,我们可以采取分表和分区结合的方式(如果merge这种分表方式,不能和分区配合的话,可以用其他的分表试),访问量不大,但是表数据很多的表,我们可以采取分区的方式等。

心得

技术没有贵贱,不是用了分布式、分库分表、分区表等等就牛逼,越复杂的系统维护的成本和难度越高,出现问题的几率越大。这种架构的演化往往都是被用户所驱动的,可以说是"不得已而为之"。

基本上单机数据库可以支撑10万用户量级别。所以一般情况下像数据库吃不消就升级硬件,优化数据库配置、优化代码、引入redis等。只有在真的不行了才上这些更复杂的东西。

你可能感兴趣的:(复习专栏,mysql)