第一层,客户端/服务器。负责连接,授权,安全等。每个客户端连接都会在服务器拥有一个线程。解析器解析查询并创建解析树,然后优化(重写查询,选择索引等)节奏执行,select语句在解析之前先会先查询缓存若存在,直接返回结果。
第二层,核心服务。如查询解析,优化,缓存,内置函数,存储过程,触发器,视图…
第三层,存储引擎。负责数据存储和提取。
ACID
优化数据类型
1.尽量使用可以正确存储数据的最小数据类型。原因:占用更少磁盘,内存,cup。
2.使用简单的。整型比字符操作代价低,原因:字符集和校队规则。
3.避免null。原因:可为null的列索引统计更复杂,更多存储空间,如果确实需要才使用。
4.时间类型。int 可以记录大范围的时间,datetime类型(范围1001-9999)适合用来记录数据的原始的创建时间,timestamp(范围1970-2038)类型适合用来记录数据的最后修改时间,只要修改记录,timestamp字段的值都会被自动更新。
5.小数类型。float和double近似小数,decimal精确小数,尽量只在对小数精确计算时使用,数据量大时使用bigint替代。
6.字符型。varchar可变长字符串适合长的字符串(需要额外的1或2个字节记录长度),char定长的,长度不够用空格填充,适合短的字符串及定值的如MD5值,或者经常变更的。
7.存储很大的字符串数据。使用blob(二进制方式)和text(字符方式,有排序规则和字符集),性能低下,尽量避免。
8.存储IPv4使用整型而不是varchar(15),因为它实际就是32位无符号整数,加小数点只是方便阅读。
数据库设计注意
1.设计表非常宽,如果只有一小部分用到,转换代价就非常高,尽量避免。
2.太多关联。
在MySQL中索引在存储引擎层,所以不同的存储引擎有不同的工作方式。一般情况都是指B-Tree索引,索引的优点:减少服务器需要扫描的数据量;帮助服务器避免排序和临时表;将随机I/O变为顺序I/O。
- Tree索引
意味着索引的值都是按顺序存储的,之所以加快访问数据的速度,因为存储引擎不再需要全表扫描,而是从索引的根节点开始搜索。
- 哈希索引
基于哈希表实现,只有精确匹配索引所有列的查询才有效。冲突越多代价越大,只适用于特定场合,如url等。
//重写前
select * from ta1 where a='**' and b like '**%';
//重写后
select * from tal
join(
select id from tal where a='**' b like '**%'
) as t2 on (t2.id=tal.id);
使用延迟关联,这样就能覆盖第一阶段的查询
create table test(
id int not null primary key,
a int not null,
unique(id),
index(id)
)
这样id上就有三个索引了,因为MySQL唯一和主键限制都是通过索引来实现的。
select * from A where id in(select id from B)
。性能很糟,不建议使用 //错误写法
UPDATE tab set col=(
select count(*) from lawyer_mediation
)
where id=4
//正确方法
update tab INNER JOIN(
select count(*) cnt from tab
) as der
set tab .col=der.cnt
where id =4
优化count()
//优化前
select count(*)from city where id>5;
//优化后,先查询小于等于5,再相减
select (select count(*) from city) - count(*) from city where id<=5
2.同一个查询中统计同一列的不同值的数量
//统计tab表中color为蓝和红的数量
select count(color='blue' or null)as blue,count(color='red' or null) as red from tab;
3.更复杂的应该考虑增加汇总表
优化关联查询
1.确保on或者using字句中列上有索引,ON子句的语法格式为:table1.column_name = table2.column_name。
当当两个表采用了相同的命名列时,就可以使用 USING 来简化,格式为:USING(column_name)。
2.确保任何的group by 和order by的表达式只涉及到一个表中的列。
3.如果需要对关联查询做分组,并且按照表中的某个列分组,那么通常采用查找表的标识发列分组效率更高。select a ,count(*) from tab ... group by id
优化limit分页
需求:对于偏移量很大的查询如limit 1000,10。会抛弃前面的大量记录会被抛弃,就需要优化.
-- 需要优化的sql
select id ,name from user order by phone limit 50,5
方案一:延迟关联
select lim.id ,name
from user INNER JOIN(
select id from user order by phone limit 50,5
)as lim using(id)
分析:这里的延迟关联将大大提升查询效率,让MySQL扫描尽可能少的页面,获取需要访问的记录后再根据关联列回原表查询需要的所有列,也可以用于优化关联查询中的分页。
方案二:转换为已知位置查询
select id,name from user where position between 50 and 54 order by position;
分析:前提能够转换,该列上有索引,并且计算边界值,扫描更少的记录。
方案三:向前翻页
select id,name from user where id<10040 order by id desc limit 5
分析:前提是id主键是单调增长的。好处就是无论怎样往后翻页,性能都很好。
实际问题优化
需求:计算两点之间的距离,如附近的人,附近的服务等功能。现有表tab和属性name,lat纬度,lon经度。
-- 计算公式
ACOS(
COS(latA)*COS(latB)*COS(lonA-lonB)
+SIN(latA)*SIN(latB)
)
这算是一个比较精确的计算公式了,但实际上没有必要,不仅消耗cup而且无法使用索引
优化:在表中添加两个列,lat_floor和lon_floor作为范围的近似值,并且在程序中计算出指定范围内的所有点的范围(经度,纬度的最大值和最小值)
如计算结果为
--------------------------------------------------
fl_lat | ce_lat | fl_lon | ce_lon
--------------------------------------------------
36 | 40 | -80 | -77
--------------------------------------------------
通过范围来生成in()列表,作为where的字句
--不精确的查询
select * from tab
where lat between 38.03 - degrees(0.02) and 38.03 + degrees(0.02)
and lon between 78.48 - degrees(0.02) and 78.48 + degrees(0.02)
and lat_floor in(36,37,38,39,40) and lon_floor in(-80,-79,-78,-77)
当然,也可以使用前面的圆周公式精确计算,因为过滤了大量的数据,所有速度会很快。
3959是地球半径,radians是弧度
-- 根据毕达哥拉斯定理计算
select * from tab
where lat_floor in(36,37,38,39,40) and lon_floor in(-80,-79,-78,-77)
and 3959*ACOS(
COS(RADIANS(lat)) * COS(RADIANS(38.03) * COS(RADIANS(lon)-RADIANS(-78.48))
+SIN(RADIANS(lat)) * SIN(RADIANS(38.03))
)<=100
优化建议
1.限制结果集(行和列)
2.避免全表扫描,主要有在where子句中使用!= > <操作符,使用xx is null语句(使用默认值代替),使用or链接如...where a=1 or a=2 替换为...where a=1 union all ...where a=2;前置%,如...like '%a%';in 和not in(如果是连续的数值,可以用between and代替);在where中使用表达式操作,..where num/2=100,可替换为num=100*2;在where子句中使用函数操作;
3.使用exists代替in
select n from a where n in(select n from b)替换为select n from a where exists(select 1 from b where n=a.n)
4.尽量使用数字型字段,在比较的时候只比较一次。