Mysql日常使用经验

159 阅读11分钟

Mysql

where 后的比较运算符(=、<、>、<>)不能对null使用,如下什么也查不到,应该使用is null来判断

select distinct product_category_id+1 as a from pms_product where price= null;

where 后的逻辑运算符(and、or、not)中

and时如下查不到任何东西

select  product_category_id from pms_product where price<300 and price = null;

or时如下查的到东西

select  product_category_id from pms_product where price<300 or price = null;

where后不能使用聚合函数

select product_category_id from pms_product where count(*)>1

去重使用distinct,如何获取商品的种类总数可以如下

select  count(distinct product_category_id) from pms_product

group by 和 having

使用group by的sql中,表示按某个字段将表分组,select后只能为group by后的值、聚合函数、常量,

having中也是只能使用group by后的值、聚合函数、常量

order by

order by后可以有一个或多个的列,多个列时表示先按第一个排序、排完后第一个列相等的按第二个列排序,若列的值为null,将null的放最上面

insert into ....select .....

insert into 'tablename' ('column1','column2'.....) select 'column1','column2'..... from 'tablename'

insert into ....select ..... 语法一般用来备份表的数据

视图

本质就是一条sql语句,创建视图时不可使用order by

更新、插入、删除时,视图必须满足以下条件才行:

  1. select语句中没使用distinct
  2. from后只使用了一张表
  3. 没有使用group by和having

标量子查询

查询结果只有一行一列的子查询,如

select * from pms_product where id = (select id from pms_product where id=10)
                                       -- 只有一行一列的结果

主要做的事如下

select *,count(select id from pms_product where id<10) from pms_product where id < 10
select *,count(*) from pms_product   -- 这样写会报错

关联子查询

如下这种查询可以理解为连接查询,只不过子查询先执行,可得到p1,而外面得不到p2,一些条件要写到子查询中

select *,CONCAT(name,pic),CURRENT_date from pms_product as p1 where p1.price>10 
(select 1 from pms_product as p2 where id<10 and p1.id=p2.id);

exists

表示包含,比in要强大,in左侧必须有列,而exists左侧没有列,如下

select *,CONCAT(name,pic),CURRENT_date from pms_product as p1 where exists 
(select 1 from pms_product as p2 where id<10 and p1.id=p2.id);

主要用于检查子查询是否至少会返回一行数据,它返回的具体是True、False

case表达式

可以理解为if else,如下,else可以不写,但是end一定要有

select *,
case when product_category_id = 7 then '1号商品'
     when product_category_id = 8 then '8号商品'
	 when product_category_id = 19 then '19号商品'
	 when product_category_id = 29 then '29号商品'
	 when product_category_id = 35 then '35号商品'
	 else null
end  as categoty 
from pms_product;

case可以用作行转列

select 
 sum(case when price<100 then price end) as '小于100',
 sum(case when price<200 and price>=100 then price end) as '100-200之间',
 sum(case when price>=200 then price end) as '大于200'
from pms_product;

索引

一张表里只能有一个主键索引,其他的可以有多个,innoDB除了全文索引,其他索引的数据结构类型默认都是BTREE,全文索引的数据结构类型为fulltext

  • 主键索引(primary key)
  • 唯一索引 (unique key)
  • 普通索引 (key/index)
  • 全文索引(fulltext)

使用explain进行sql分析,一般只看type、key、rows 这三个属性就够了

EXPLAIN select * from ums_admin where id = 6

一般来说,type 得保证查询至少达到 range 级别,最好能达到 ref

+----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+ | 1 | SIMPLE | ums_admin | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL | +----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+

  • id:列的编号就是 select 的序列号,也可以理解为 SQL 执行顺序的标识

  • select_type:查询的类型,主要用于区分普通查询、联合查询、子查询等复杂的查询

    ​ simple:表示查询中不包括 union 操作或者子查询

    ​ primary:需要 union 操作或者含有子查询的 select

​ derived:from 列表中出现的子查询

​ subquery:除了 from 子句中包含的子查询外

​ union:若第二个 select 出现在 union 之后,则被标记为 union;若 union 包含在 from 子句的子查询中,外层 select 将被标记为 derived

​ dependent union:与 union 一样,出现在 union 或 union all 语句中,但是这个查询要受到外部查询的影响

​ dependent subquery:与 dependent union 类似,子查询中的第一个 SELECT,这个 subquery 的查询要受到外部表查询的影响

  • table:表示 explain 的一行正在访问哪个表
  • type:访问类型,即 MySQL 决定如何查找表中的行依次从好到差:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL,除了 all 之外,其他的 type 都可以使用到索引,除了 index_merge 之外,其他的 type 只可以用到一个索引。一般来说,得保证查询至少达到 range 级别,最好能达到 ref
    1. system:表中只有一行数据(等于系统表)这是 const 类型的特例平时不会出现
    2. const:使用唯一索引或者主键,表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引。因为只需匹配一行数据,所有很快。
    3. eq_ref:唯一性索引扫描,对于每个索引键,表中只有一行数据与之匹配。常见于主键或唯一索引扫描。
    4. ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质也是一种索引
    5. fulltext:全文索引检索,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql 不管代价,优先选择使用全文索引。
    6. ref_or_null:与 ref 方法类似,只是增加了 null 值的比较。
    7. index_merge:表示查询使用了两个以上的索引,索引合并的优化方法,最后取交集或者并集,常见 and ,or 的条件使用了不同的索引。
    8. unique_subquery:用于 where 中的 in 形式子查询,子查询返回不重复值唯一值;
    9. index_subquery:用于 in 形式子查询使用到了辅助索引或者 in 常数列表,子查询可能返回重复值,可以使用索引将子查询去重。
    10. range:索引范围扫描,常见于使用>,<,between ,in ,like等运算符的查询中。
    11. index:索引全表扫描,把索引树从头到尾扫一遍
    12. all:遍历全表以找到匹配的行(Index 与 ALL 虽然都是读全表,但 index 是从索引中读取,而 ALL 是从硬盘读取)
    13. NULL: MySQL 在优化过程中分解语句,执行时甚至不用访问表或索引
  • possible_keys:显示查询可能使用到的索引
  • key:显示查询实际使用哪个索引来优化对该表的访问
  • key_len:用于处理查询的索引长度,表示索引中使用的字节数
  • ref:显示哪个字段或者常数与 key 一起被使用
  • rows:表示 MySQL 根据表统计信息及索引选用情况,大致估算的找到所需的目标记录所需要读取的行数,不是精确值
  • extra:不适合在其他列中显示但十分重要的额外信息
  • filtered:这个字段表示存储引擎返回的数据在 server 层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数

索引创建原则

  • 索引不是越多越好
  • 不要对经常变得数据加索引
  • 不要对例如性别的列加索引
  • 小数据量的表不需要索引
  • 索引一般加在经常查询的表上

使用以下技巧来优化索引的查询速度和sql的查询速度

  1. 尽量使用主键查询,而非其他索引,因为主键查询不会触发回表查询;
  2. 查询语句尽可能简单,大语句拆小语句,减少锁时间;
  3. 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型;
  4. 金额的字段使用decimal或者varchar存储
  5. 用 exists 替代 in 查询;
  6. 避免在索引列上使用 is null 和 is not null。
  7. *禁止使用select ,获取什么字段就去什么值,select * 会增加内存、带宽
  8. 组合索引字段数量不建议超过5个,按最左原则(x,y,z)相当于建立了(x),(x,y),(x,y,z),不要建立重复的索引
  9. 隐式类型转换会使索引失效,如a=1写为a='1',会导致全表扫描
  10. 禁止在where后面使用内置表达函数
  11. 禁止负向查询(!=,not like,not in)
  12. 禁止使用以%开头的模糊查询
  13. 尽量在order by ,group by的列上创建索引
  14. 如果条件中有 or 即使其中有条件带索引也不会正常使用索引,要想使用 or 又想让索引生效,只能将 or 条件中的每个列都加上索引才能正常使用;
  15. 禁止大表join查询和子查询,进行join查询时关联字段建立索引
  16. insert必须指定字段,紧张使用insert into Table values(),指定字段插入
  17. 单张表索引数量建议控制在5个,索引过多会导致插入更新性能下降,还可能导致mysql性能下降
  18. 在 where 子句中对字段进行表达式操作。

数据拆分

当表中数据量太大时 SQL 的查询会比较慢,你可以考虑拆分表,让每张表的数据量变小,从而提高查询效率。

1.垂直拆分

指的是将表进行拆分,把一张列比较多的表拆分为多张表。比如,用户表中一些字段经常被访问,将这些字段放在一张表中,另外一些不常用的字段放在另一张表中,插入数据时,使用事务确保两张表的数据一致性。垂直拆分的原则:

  • 把不常用的字段单独放在一张表;
  • 把 text,blob 等大字段拆分出来放在附表中;
  • 经常组合查询的列放在一张表中。
2.水平拆分

指的是将数据表行进行拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。通常情况下,我们使用取模的方式来进行表的拆分,比如,一张有 400W 的用户表 users,为提高其查询效率我们把其分成 4 张表 users1,users2,users3,users4,然后通过用户 ID 取模的方法,同时查询、更新、删除也是通过取模的方法来操作。

表的其他优化方案:
  1. 使用可以存下数据最小的数据类型;
  2. 使用简单的数据类型,int 要比 varchar 类型在 MySQL 处理简单;
  3. 尽量使用 tinyint、smallint、mediumint 作为整数类型而非 int;
  4. 尽可能使用 not null 定义字段,因为 null 占用 4 字节空间;
  5. 尽量少用 text 类型,非用不可时最好考虑分表;
  6. 尽量使用 timestamp,而非 datetime;
  7. 单表不要有太多字段,建议在 20 个字段以内。

慢查询分析

MySQL 中自带了慢查询日志的功能,开启它就可以用来记录在 MySQL 中响应时间超过阀值的语句,具体指运行时间超过 long_query_time 值的 SQL,则会被记录到慢查询日志中。long_query_time 的默认值为 10,意思是运行 10S 以上的语句。默认情况下,MySQL 数据库并不启动慢查询日志,需要我们手动来设置这个参数,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会给 MySQL 服务器带来一定的性能影响。慢查询日志支持将日志记录写入文件,也支持将日志记录写入数据库表。使用 mysql> show variables like '%slow_query_log%'; 来查询慢查询日志是否开启,执行效果如下图所示:imgslow_query_log 的值为 OFF 时,表示未开启慢查询日志。

开启慢查询日志

开启慢查询日志,可以使用如下 MySQL 命令:

mysql> set global slow_query_log=1

不过这种设置方式,只对当前数据库生效,如果 MySQL 重启也会失效,如果要永久生效,就必须修改 MySQL 的配置文件 my.cnf,配置如下:

slow_query_log =1 slow_query_log_file=/tmp/mysql_slow.log

当你开启慢查询日志之后,所有的慢查询 SQL 都会被记录在 slow_query_log_file 参数配置的文件内,默认是 /tmp/mysql_slow.log 文件,此时我们就可以打开日志查询到所有慢 SQL 进行逐个优化。

读写分离

一般情况下对数据库而言都是“读多写少”,换言之,数据库的压力多数是因为大量的读取数据的操作造成的,我们可以采用数据库集群的方案,使用一个库作为主库,负责写入数据;其他库为从库,负责读取数据。这样可以缓解对数据库的访问压力。

MySQL 常见的读写分离方案有以下两种:

1.应用层解决方案

可以通过应用层对数据源做路由来实现读写分离,比如,使用 SpringMVC + MyBatis,可以将 SQL 路由交给 Spring,通过 AOP 或者 Annotation 由代码显示的控制数据源。优点:路由策略的扩展性和可控性较强。缺点:需要在 Spring 中添加耦合控制代码。

2.中间件解决方案

通过 MySQL 的中间件做主从集群,比如:Mysql Proxy、Amoeba、Atlas 等中间件都能符合需求。优点:与应用层解耦。缺点:增加一个服务维护的风险点,性能及稳定性待测试,需要支持代码强制主从和事务。

三大范式

关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式

通常我们就使用第一范式(1NF)、第二范式(2NF)、第三范式(3NF),主要是规范数据库设计,阿里规约要求一般关联不超过三张表,为了性能我们可以不完全按照三大范式,大体上按三大范式

第一范式(1NF): 要求数据库表的每一列都是不可分割的原子数据项,保证每一列都不可再分

第二范式(2NF):前提满足第一范式, 确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关,每张表只描述一件事情

第三范式(3NF):前提满足第二范式, 确保数据表中的每一列数据都和主键直接相关,而不能间接相关

shardingJDBC

一个读写分离、分库分表的插件,它和主从复制不一样,主从复制是数据库层面的,shardingJDBC是用在业务端读写分离的插件。使用读写分离,数据库必须开启主从复制。

为什么要分库分表

单库的连接数和事务数也就是并发超过了2k,系统基本就完蛋了,最好是并发量控制在1k左右,所以我们就要分库分表

**水平拆分:**一个表的数据拆到不同库的不同表中(是指表中的数据拆分),也就是分表

**垂直拆分:**库的拆分是将不同业务的表拆为几个不同的库,表的拆分是将一张表拆为几张(是将表的字段拆分,比如:一张表常用的查询的为一张,不常查询的为一张)