纵论
对mysql优化时一个综合性的技术,主要包括
- 表的设计合理化(符合3NF)
- 添加适当索引(index) [ 四种: 普通索引、主键索引、唯一索引unique、全文索引 ]
- 分表技术( 水平分割、垂直分割 ) 水平分割根据一个标准重复定义几个字段值相同,表名称不同的表,表的结构相同
- 读( 将查询query分配到多个数据库中,子数据库);写(主数据库用来写)[ 写: update/delete/add ]分离
- 存储过程 [ 模块化编程,可以提高速度 ],存储过程其实是一个数据对象,从数据库的三层结构来说,分为 客户端java程序---dbms(数据库管理系统)通常说的数据库指的是数据库管理系统---db(本质就是一个文件),java程序一个sql语句发送到dbms的时候,dbms会将sql语句进行编译,然后执行,执行过后将数据暂时缓存一下,第二次操作的时候,数据就会快。Sql语句最致命的问题就是,sql编译很耗费时间,这时候有人就提出了将经常使用的语句在数据库里面编码,让它形成一个二进制文件,到时候直接调用,这样提高速度,存储过程,最常见的就是,分页存储过程,配合触发器的存储过程,存储过程是一个已经编译好,这时候就会直接执行,存储过程有利于模块化开发。存储过程的致命缺点:移植性不好
- 对mysql配置优化 [ 配置最大并发数my.ini max_connections=100,一般网站调整到1000左右即可,如果更大会把内存撑破; 调整缓存大小 ]
- mysql服务器硬件升级,服务器内存的提高,优化到位了,硬件不够要向上反馈提高硬件性能
- 定时的去清除不需要的数据,定时进行碎片整理( MyISAM引擎要定时清理,不然会越来越大 )
优化技术根据不同业务场景也不同,但是底层一些使用方式和原理是通用的,本篇博文从最基本的数据库基本字段类型展开优化技术的描述,详细如下
一、列类型的概念
整数类型:
从数学上来讨论tinyint
- 占据空间:指定的时候就已经分配好空间1字节
- 存储范围:就是能存储的现实世界的数据范围
数学推算
[0][0][0][0][0][0][0][0];
[1][1][1][1][1][1][1][1];
如果表示负数,可以用最高位来标志符号位,
思考:此时的表示范围
-128-->127 (只有后7位表示绝对值,最高位表示的是符号)至于为什么是-128,原因在于这里面在有符号的时候采用(补码)的形式推算,往前补一位,最后结果-1即可,所以这个是范围的由来。
类型 字节 位 无符号 有符号
Tinyint 1 8 0-->255 -128--->127
Smallint 2 16 0-->2^16-1 -2^15-->2^15-1
Mediumint 3 24 0-->2^23-1 -2^23-->2^23-1
Int 4 32 0-->2^32-1 -2^31-->2^31-1
Bigint 8 64 0-->2^64-1 -2^63->2^63-1
其中的范围就是现实世界中的数据范围
Tinyint默认是有符号的存储-128--> 127
Tinyint(M) unsigned zerofill
❶ M:宽度(在0填充的时候才有意义)也就是在zerofill的时候才有意义
❷ Unsigned: 无符号类型(非负)影响存储范围
❸ Zerofill:0 填充,默认无符号,说明无符号位,长度一致
❹ 一般tinyint(5) zerofill 就可以表示 tinyint unsigned zerofill,所以没有必要多写以个unsigned
❺ 比如插入的数据是34的时候,那么最后添加进去展示的效果是00034,这只是为了数据的显示效果,与数据的表示没有关系,没有zerofill就没有必要声明M了。
❻ 列的默认值,而且推荐声明默认值,往往这样配合使用,Not NULL default
比如表中的age字段就可以用 tinyint表示,因为tinyint在无符号的情况下可以存储0-255范围的数据。
比如绿毛龟则可以用smallint表示年龄
Int可以表示的数据类型的范围是40多亿,但是实际开发过程中,一旦数据的长度超过了某一范围,就不会用int来存储。
比如int(5)中这个5代表的就是宽度,当没有zerofill这个关键字的时候,存储的效果是没有变化的,放进去是什么样的,显示的就是什么样的,如果添加了zerofill,那么就可以对存储的数据进行显示方向的修改,比如存储的是123,那么查询出来之后的显示就是00123,如果存储的是99999999,宽度超过了5,那么存储进去的就是99999999,显示的还是99999999,这就是宽度的显示方面的作用了,影响展示效果
小数型/浮点型 定点型:
Float(M,D),M代表总位数,D代表小数位数,不包括小数点,也不含正负号(6,2)为例-9999.99-->9999.99
Float(4,2) (unsigned) -99.99-->99.99 (0.00-99.99)
那么float占几个字节呢,在声明float的时候,就已经指定了它所占的字节数,4个字节或者8个字节。因为在计算机里面表示float比较复杂,所以都用的比较大。
当M<=23时用4个字节,当M>23位数的时候,就用8个字节
字符型:
Char:定长字符串,char(M)M代表宽度,可容纳的字符数为M,0<=M<=255之间,char(10)表示能输入10个字符,但是前提不要忘记M在char中最多只能255个字符,char型,如果不够M个字符,末尾用空格补齐,取出来的时候,将尾部空格再去掉。所以数据后面本身就有空格存进去,但最后还是会被去掉
varchar:变长字符串,varchar(M)M代表宽度,可容纳的字符数为M,0<=M<=65535(以ascii字符为例,utf22000个汉子左右)
一个汉子就是一个字符,字符在不同字符集下所占的字节数是不一样的,gbk占两个字节,utf-8占3个字节,一个英文字母在任何编码集下都占用一个字节
每个字符都有一个ASCII码,一个ASCII码占一个字节,汉字在不同的编码集下占用的字符不同
区别在哪里:
char定长,那么就是不变,如果存的小于M个字符,实占M个字符
Varchar:M个字符,存的小于M个字符,设为N,N<M,实占N个字符
类型 宽度 可存字符 实存字符i<=M 占用空间 利用率
Char M M i M i/M<=100%
Varchar M M i i字符+(1-2)字节 i/(i+1-2)<100%
如何选择:定长速度快
考虑两个要素即可
选择原则:
- 空间利用效率,成语表则可以选char,个人简介或者微博(140)可以用varchar,这个可变
用户名:varchar 造成速度变慢,宁可让字节多浪费几个,而选择用char,没有定死的规则,根据实际变通。
- 速度
Text的不好的地方就是不能全文索引,搜索速度慢,不知道现在是否可以。
结论:
如果不是特别大的内容建议用char,varchar来代替
Text不用就加默认值(加了也没用)
日期时间类型:
Year类型:1个字节,表示 1901-2155,少了一个位数是为了防止出错的情况下做的操作,出错用的是0000表示
比如:如果输入两位,范围在00-69之间表示2000-2069
70-99表示1970-1999年,所以嫌弃麻烦的话,直接用4位输入即可
Date类型: 典型格式1992-08-12
日期类型: 1000-01-01---9999-12-31
Time 典型格式: HH:mm:ss
时间范围: -838:59:59 ---838:59:59
Datetime类型 典型格式: 1989-05-05 14:32:03
日期时间类型和范围: 1000-01-01 00:00:00 ----9999:12-31 23:59:59
注意:在开发中,很少使用日期事件类型来表示一个需要精确到秒的列
原因:虽然日期事件类型能精确到秒,而且查看方便,但是计算不方便,比如一个用户注册时间在某一个时间点,要求统计出距离这个时间点30分钟的注册用户查出来,那么就不方便了,到这里我就明白了之前看项目过程中,为什么创建时间要用timestamp来做了,而更新时间用datetime来存储了
Timestamp 时间戳用int来存储,方便计算,对于显示来说也可以方便的格式化,可以格式化成想要的格式
Java中用long表示毫秒数,变量用long保存,与之对应的MySQL的数据类型就是bigint,占用8个字节
Enum和set枚举型和集合类型:
关系型数据库中,不要出现一个列可以有多个值的现象,这样列的值就不唯一,还不如用一张表来表示各种字段
建表案例:
现在设计一张表:
姓名: char(3)
年龄: tinyint unsigned
Email: varchar(30),
Telephone: char(11)
Introduction: varchar(1000)
Salary: decimal(7,2)
报道日期: date
Engine: myisam,
Charset: utf-8
date是1970-01-01 00:00:00到当前的秒数,一般存注册时间,商品发布时间等,并不是datetime存储,而是用时间戳,因为datetime虽然直观,但是计算不便
Limit 【offset】 ,N
offset:偏移量
N:取出的总条数
二、五种子句:
Where子句:条件查询,运用在各种场合
Group 子句:
Having 子句:
Order by 子句:
Limit 子句:
五种子句的先后顺序要记住
三、三种子查询
1.什么是子查询?
当一个查询是另一个查询的条件时,称之为子查询。
2.子查询有什么好处?
子查询可以使用几个简单命令构造功能强大的复合命令。
Where 子查询:指把内层查询的结果作为外层查询的比价条件
Exist 子查询:把外层的查询结果拿到内层,看内层的查询结果是否成立
语法:SELECT ... FROM table WHERE EXISTS (subquery)
该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(TRUE 或 FALSE)来决定主查询的数据结果是否得以保留。
article 文章表:
user 用户表:
我们要查出 article 表中的数据,但要求 uid 必须在 user 表中存在。SQL 语句如下:
SELECT * FROM article WHERE EXISTS (SELECT * FROM user WHERE article.uid = user.uid)
定义:exists子查询就是对外层表进行循环,再对内表进行内层查询。和in ()差不多,但是它们还是有区别的。主要是看两个张表大小差的程度。
若子查询表大则用exists(内层索引),子查询表小则用in(外层索引);
效率的区别就在于使用的索引(where后面的变量就是我们使用的索引)不同摆了,我们可以对大表使用索引提高搜索速度。
From 子查询:指把内层的查询结果当成临时表,供外层sql再次查询
语法:SELECT ... FROM (subquery) AS name ...
例子:FROM 子查询 SQL 如下:
SELECT s1,s2 FROM (SELECT s1, s2*2 AS s2 FROM table1) AS temp WHERE s1 > 1
总结:
5种子句的使用顺序 where , group , having, order by , limit
DML:对应CRUD
四、Union的用法:
合并查询的结果(取select结果的并集)
❶ 对于重复的行,去掉
❷ 如果不去重复,可以用union all
Union的要求:
❶ 各select查出的列数一致
❷ 如果子句中使用功能了 order by limit ,那么子句要用“()”包裹起来
❸ 如果子句只用order by 没有limit ,Order by 会被优化掉,不起作用
五、左连接,右连接,内连接,MySQL没有外链接
左连接与右连接可以相互转换,join后的为主表
内连接:左右连接的交集
两张表能相互匹配上的行
六、DDL语句,列的增删改
增加列:
❶ Alter table 表名 add 列声明
❷ 增加的列默认是在表的最后一列
❸ 可以用after来声明新增的列在哪一些列后面
❹ Alter table 表名 add 列声明 after username
❺ 如果新增的列在最前面怎么做
❻ Alter table 表名 add 列声明 first
修改列名:
Alter table 表名 change 被改变的列名 新列名 列声明
删除列名:
Alter table 表名 drop 列名
七、视图:
create [algorithm=merge[temptable|undefined]] view viewName as select ....
如果某个查询结果出现的非常频繁,也就是,拿这个结果进行子查询出现的非常频繁
视图的定义:
视图是由查询结果形成的一张虚拟表
创建语句:
Create view 视图名称 as select 语句;
删除视图:
Drop view
使用的时候,直接将其当做表来使用就可以了
为什么要视图:
- 可以简化查询
- 可以进行权限控制:把表的权限封闭,但是开放相应的视图权限,视图里只开放部分数据
- 大数据分表的时候可以用到:比如,表的行数超过200万的时候,就会变慢,可以把一张表的数据拆成4张表来存放,存放的方法最常用的方法可以用ID进行取摸计算,拆成几张表就是摸几
视图与表的关系:
视图是表的查询结果,那么表的数据改变了,自然影响视图的结果
视图增删改也会影响表,但是视图并不是总是能增删改的,当视图的数据与表的数据一一对应的时候,就可以改变,否则不能,对于视图的insert还应该注意,视图必须包含表中没有默认值的列,也就是视图中的列有个列没有默认值,那么插入的时候就要给他赋值
视图的算法algorithm
Algorithm = merge/temptable/undefined
Merge :(合并)当引用视图时,引用视图的语句与定义视图的语句合并
Temptable:当引用视图的时候,根据视图的创建语句建立一个临时表
Undefined:未定义,自动,系统自动帮你选
Merge
意味着视图知识一个规则,语句规则,当查询视图的时候,把查询视图的语句(比如where那些)与创建时的语句where子句等合并,分析形成一条select语句
例如:
创建视图的语句:
MySQL> create view g2 as select goods_id, cat_id, goods_name, shop_price from goods order by cat_id asc, shop_price desc;
查询视图的语句
Select * from g2 group by cat_id;
而整句话的效果看起来就像是上面两条语句做了拼接,完整的执行了下面这行代码
select goods_id, cat_id, goods_name, shop_price from goods group by cat_id order by cat_id asc, shop_price desc;
通过merge算法将sql语句进行前后叠加
temptable
是根据创建语句瞬间创建一张临时表,然后查询视图的时候从该临时表查询数据
创建视图的语句:
比如:create algorithm=temptable view g2 as select goods_id, cat_id, goods_name, shop_price from goods order by cat_id asc, shop_price desc;
查询视图的语句:
Select * from g2 group by cat_id
最终执行的是2句话,取数据并放在临时表,然后去查询临时表
结论:
以上两次查询视图的对象不一样,一个是查询goods表,第二次查询查询的是临时表,第一个也就是默认的给你多展示一条创建语句罢了,并没有起到视图的效果
八、MySQL的字符集和校对集设置
字符集的设置非常灵活
❶ 可以设置服务器默认字符集
❷ 数据库默认字符集
❸ 表默认字符集
❹ 列字符集
如果某一个级别没有指定字符集,则继承上一级
以表声明utf-8为例
存储的数据在表中,最终是utf-8字符集来存储的
set character_set_client=gbk;这个是设置客户端的编码
Set character_set_connection=gbk;这个是字符转换器的设置,告诉字符转换器将客户端传来的值转换成gbk
Set character_set_result=gbk;这个是告诉字符转换器,最终返回的结果是gbk字符集
- 告诉服务器,我给你发送的数据是什么编码的?Character_set_client;
- 告诉转换器,转换成什么编码?Character_set_connection;
- 查询的结果用什么编码?Character_set_results;
- 如果以上三种都为字符集N,则可以简写为 set names N,
- 什么时候出现乱码:1.client声明与事实不符 2:results与客户面不符
- 什么时候丢失数据呢?Connection和服务器的字符集所占的字符数比client小的时候,就会造成数据丢失
- 如何解决UTF-8客户端,服务器是GBK的乱码问题,首先告诉转换器我的字符集是utf-8,connection转换器是什么字符集倒无所谓,最后results是UTF-8就可以解决了,这个时候不用考虑字节的变化,他们在转换的时候自然会有码表进行对应
排序规则就是校对集
比如根据assic码表,a97B68,B应该在a前面,但是排序有校对集这个规则,来源于现实的理解,所以应用这种校对集
一个字符集有多种校对集
查看校对集:show collation; 195种
查看字符集:show character; 35个
改变校对集 charset utf8 collate utf8_bin;
默认使用的是utf8_general_ci规则,也可以用二进制来排序utf8_bin
声明校对集:在建表的时候末尾声明校对集,还有就是字符集和校对集也有对应关系,utf8就不能用Latin的校对集
触发器
作用:监视某种情况并触发某种操作
观察以下场景,trigger的一个场景
一个电子商城
商品表,g
主键 商品名 库存
1 电脑 28
2 自行车 12
订单表,o
订单主键 商品外键 购买数量
1 2 3
2 1 5
完成下单与减少库存的逻辑
Insert into o(gid, num) values(2,3); //插入语句
Update g set goods_num = good_num - 3 where id = 2 //更新过程
这两个逻辑可以看成一个整体或者说 insert ---->引来update
用触发器可以解决上述问题:我们可以监视某表的变化,当发生某种变化的时候,触发某个操作
能监视的操作: 增删改
触发操作: 增删改
触发器的语法
After / before
监视地点:o表
监视操作:insert
触发时间:after
触发操作:update
语法:
create trigger triggerName After / before insert/update/delete on 表名 for each row
Begin
Sql 语句
End;
Delimiter $$;
所以上面的语句就可以写成这样一个触发器
Create trigger tg1
After insert into o
For each row //固定写法,oracle和sql server 是可选的
Begin
Update g set num = num -3 where id = 2;
End$$
删除触发器的语法:
Drop trigger triggerName;
接下来完成动态赋值
首先:如何在触发器中引用行的值
❶ 对于insert而言,新增的行,用new来表示,行中的每一列的值,用new.列名来表示
❷ 对于delete而言,原本有一行,后来被删除,想引用被删除的这一行,用old.列名来表示
❸ 对于update而言,被修改的行,修改前的数据用old.列名表示,修改后的数据用new.列名表示
Create trigger tg2
After insert into on o
For each row
Begin
Update g set num = num - new.much where id = new.gid;
End$$
以上是下订单的时候用的
接下来可以是删除一个订单的时候,相应库存增加
监视的表:o表
监视的事件:delete
监视的时间:after
触发的操作:update
Create trigger tg3
After delete on o
For each row
Begin
Update g set num = num + old.much where id = old.gid;
End$$
修改订单数量的时候,库存相应改变
Create trigger tg4
After update on o
For each row
Begin
Update g set num = num + old.much- new.much where id = old.gid;
End$$
触发器里的After与before 的区别:
❶ After是先完成数据的增删改再触发
❷ 触发中的语句晚于增删改,不能对前面的增删改产生影响
Before是先完成触发,再增删改,触发的语句先于监视的增删改,也就是说我们有机会审查判断即将要发生的操作
典型案例:
对于所下订单进行判断,如果订单的数量大于5,就认为是恶意订单,强制把所订的商品数量改成5
监视地点:o表
监视事件:insert
触发事件:update
触发时间:before
目的,触发事件咸鱼监听事件,并判断监听事件的数据
Create trigger tg5
Before insert into on o
For each row
Begin
If new.much > 5 then
Set new.much = 5;
End if;
Update g set num = num - new.much where id = new.gid;
End$
查看触发器
Show triggers
常用的表引擎
Myisam:批量插入速度快,不支持事务,锁表
Innodb:批量插入相对较慢,支持事务,行锁
全文索引:以5.5版本为例,myisam和innodb都已经支持
事务
事务的四个特性
❶ 通俗的说事务,一组操作要么都成功执行,要么都不执行成功--->原子性
❷ 在所有的操作没有执行完毕之前,其他会话不能够看到中间改变的过程-->隔离性
❸ 事务发生前和发生后,数据的总额依然匹配--->一致性
❹ 事务产生的影响是不能够撤销的--->持久性
如果出现了错误,只能通过补偿性事务进行弥补
如何开启一个事务:
Start transaction
Sql 语句
提交事务commit 或者 rollback回滚事务
有一些语句会造成事务的隐式的提交,但是为了安全性,建议手动提交
事务的基本原理:
不用事务的时候,直接作用在表,所以双方能够马上看到表的记录改变;用了事务之后,先是在事务的日志文件上写上语句的影响,commit的时候,直接通过日志文件作用于表文件。同理,在没有commit之前,因为语句的影响在事务日志文件之中,所以表的改变没有及时改变,只有当commit的时候才可以
备份与恢复:
导出数据库下面的表
MySQLdump -u用户名 -p密码 库名 表1 表2 表3... >地址/备份文件名称
导出的是建表语句以及insert语句
Oracle则是二进制文件
导出数据库下的所有表
Mysqldump -u用户名 -p密码 库名 > 地址/备份文件名
导出一个库
Mysqldump -u用户名 -p密码 -B 库名1 库名2 > 地址/备份文件名
导出所有库
Mysqldump -u用户名 -p密码 -A > 地址/备份文件名
恢复库
❶ 登录到MySQL命令行
库为单位导出的sql文件
MySQL>source 备份文件地址
对于表级的备份文件
MySQL>use 库名
Mysql>source 备份文件地址
❷ 不登录到MySQL命令行
针对库级的备份文件
MySQl -u用户名 -p密码 < 库级备份文件地址
针对表级的备份文件
MySQL -u用户名 -p密码 库名 < 表级备份文件地址
索引:
Hash索引:
根据hash算法算出散列值,根据散列值分配空间。
理论上还是会有重复,也就是碰撞性,碰撞性高,那么算法就不好;第二点就是最大区间,如果散列的空间很大,那么服务器的区间不能分配这么多空间,则会报错,而二叉树则可以把1万以内的数量在14次内遍历出结果
索引是针对数据建立的目录
作用:加快查询速度
负面影响:降低了增删改的速度
案例:
设有新闻列表15列, 10列上有索引,公500万行数据,如何快速导入?
- 把空表的索引全部删除
- 导入数据
- 数据导入完毕后,集中建立索引
索引的创建原则:
- 不要过渡索引
- 在where条件最频繁的列加上索引
- 尽量索引散列值,过于集中的值加索引的意义不大
查看一张表上所有索引
Show index from 表名 \G
建立索引:
Alter table 表名 add index /unique/fulltext[索引名](列名)
Alter table 表名 add primary key (列名)//不用添加索引名,因为主键只有一个
删除索引名:
普通非主键索引:Alter table 表名 drop index 索引名
全文索引 中文对MySQL没有作用
删除主键:alter table 表名 drop primary key
存储过程:
概念:概念类似于函数,就是把一段代码封装起来,当要执行这一段代码的时候,可以通过调用这个存储过程来实现,在封装的语句体里面,可以用if else case while 等控制,可以进行sql编程
存储过程没有返回值,函数有