2019年/11月/05日
一塌糊涂的SQL
SQL是一个声明式语言,足够抽象,理论上,学习声明式语言收益是巨大的,再加上SQL底层有着干净数学模型,可是现实中存在着各种丑陋肮脏的抽象泄露
所谓的泄露就是我们每写一个SQL都如履薄冰,看看下面我做的测试
explain基础:
- select_type:表示查询的类型。
- table:输出结果集的表
- partitions:匹配的分区
- type:表示表的连接类型
- possible_keys:表示查询时,可能使用的索引
- key:表示实际使用的索引
- key_len:索引字段的长度
- ref:列与索引的比较
- rows:扫描出的行数(估算的行数)
- filtered:按表条件过滤的行百分比
- Extra:执行情况的描述和说明
select_type
- SIMPLE(简单SELECT,不使用UNION或子查询等)
- PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
- UNION(UNION中的第二个或后面的SELECT语句)
- DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
- UNION RESULT(UNION的结果,union语句中第二个select开始后面所有select)
- SUBQUERY(子查询中的第一个SELECT,结果不依赖于外部查询)
- DEPENDENT SUBQUERY(子查询中的第一个SELECT,依赖于外部查询)
- DERIVED(派生表的SELECT, FROM子句的子查询)
- UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)
type
- ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
- index: Full Index Scan,index与ALL区别为index类型只遍历索引树
- range:只检索给定范围的行,使用一个索引来选择行
- ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
- eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
- const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
- NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
从最好到最差的排列 const、eq_reg、ref、range、index 和 ALL
possible_keys: 可能命中的索引
key: 实际使用的索引
Extra
- Using where
- Using Index Condition
- Using temporary
- Using filesort
- Using join buffer
- Impossible where
- Select tables optimized away
- No tables used
效率最低的是 Using temporary 和 Using filesort
测试表:
插入数据
注意必须要插入数据来测试,空表没有意义
单索引
explain列的定义:
select_type table type possible_keys key key_len ref rows Extra
等和不等
explain select * from test_table where id = 1
SIMPLE test_table const PRIMARY PRIMARY 4 const 1 NULL
explain select * from test_table where apply = 'hello'
SIMPLE test_table ref index_apply index_apply 302 const 1 Using index condition
explain select * from test_table where apply != 'hello'
SIMPLE test_table ALL index_apply NULL NULL NULL 1000 Using where
explain select * from test_table where apply is null
SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE
Like和Not Like
explain select * from test_table where apply like 'h%'
SIMPLE test_table range index_apply index_apply 302 NULL 1 Using index condition
explain select * from test_table where apply like '%h'
SIMPLE test_table ALL NULL NULL NULL NULL 1000 Using where
explain select * from test_table where apply not like 'h%'
SIMPLE test_table ALL NULL NULL NULL NULL 1000 Using where
类型不匹配
explain select * from test_table where apply > 1
SIMPLE test_table ALL index_apply NULL NULL NULL 1000 Using where
NULL
explain select * from test_table where apply2 is null
SIMPLE test_table ref index_apply2 index_apply2 303 const 1999 Using index condition
explain select * from test_table where apply2 is not null
SIMPLE test_table ALL index_apply2 NULL NULL NULL 3000 Using where
大于 小于
explain select * from test_table where age = 2
SIMPLE test_table ref index_age index_age 4 const 1 NULL
explain select * from test_table where age > 2
explain select * from test_table where age < 2
SIMPLE test_table ALL index_age NULL NULL NULL 6116 Using where //区分度不高,选择了权标扫描
SIMPLE test_table range index_age index_age 4 NULL 1 Using index condition //区分度高,所以命中索引
不等和运算
explain select * from test_table where age != 2
SIMPLE test_table ALL index_age NULL NULL NULL 3000 Using where
explain select * from test_table where age <> 2
SIMPLE test_table ALL index_age NULL NULL NULL 3000 Using where
explain select * from test_table where age/2 = 100
SIMPLE test_table ALL NULL NULL NULL NULL 3000 Using where
in between or
explain select * from test_table where age in (1,2,3)
SIMPLE test_table range index_age index_age 4 NULL 3 Using index condition
explain select * from test_table where age = 1 or age = 2 or age = 3
SIMPLE test_table range index_age index_age 4 NULL 3 Using index condition
explain select * from test_table where age not in (1,2,3)
SIMPLE test_table ALL index_age NULL NULL NULL 3000 Using where
explain select * from test_table where age between 1 and 1000
SIMPLE test_table range index_age index_age 4 NULL 1 Using index condition
NULL
explain select * from test_table where age2 is null
SIMPLE test_table ref index_age2 index_age2 5 const 2999 Using index condition
explain select * from test_table where age2 is not null
SIMPLE test_table ALL index_age2 NULL NULL NULL 2000 Using where
复合索引
必须按顺序
explain select * from test where a = 10
SIMPLE test ref index_abc index_abc 5 const 1 Using index
explain select * from test where b = 10
SIMPLE test ALL NULL NULL NULL NULL 2000 Using where
explain select * from test where a = 10 and b = 10
SIMPLE test ref index_abc index_abc 10 const,const 1 Using index
explain select * from test where a = 10 and d = 10
SIMPLE test ref index_abc index_abc 5 const 1 Using where
or里如果存在没有索引
explain select * from test where a = 10 or d = 10
SIMPLE test ALL index_abc NULL NULL NULL 2000 Using where
不按顺序又排序,还分组
explain select * from test where b = 10 and c = 10
SIMPLE test ALL NULL NULL NULL NULL 2000 Using where
explain select * from test where a = 10 order by d desc
SIMPLE test ref index_abc index_abc 5 const 1 Using where; Using filesort
explain select max(d) from test
SIMPLE test ALL NULL NULL NULL NULL 2000 NULL
explain select * from test group by d
SIMPLE test ALL NULL NULL NULL NULL 2000 Using temporary; Using filesort
套路 (部分参考了网络资料,请持有批判精神)
- 请注意你使用的mysql版本
- 选择是否走索引,优化器会干扰你,如果 MySQL 估计使用索引比全表扫描更慢,则不使用索引
- 类型要兼容
- 不要运算
- IS NULL 与 IS NOT NULL不能使用索引 ? 经过测试 is null可以用到索引
- Like %通配符如果出现在词首,不能使用索引, like这种东西就在关系数据库里不适用
- 当索引列有大量数据重复时,SQL查询可能不会去利用索引
- 一个表的索引最好不要超过6个
- 避免在有很多重复值(包括空值)的列上建索引
- 在where子句中最频繁用到的列上建索引
- 在有distinct、min、max、order by、group by操作的列和join连接列上建索引
- 避免创建冗余的类似索引,如存在a,b,c三列上的索引,那么a,b两列上的索引就是冗余的
- 尽量避免在一个索引中使用多于5个的列
- 不要在频繁更新的列、宽度较大的列上建索引
- 不要select * from
- Where子句中列的顺序与需使用的索引顺序保持一致
- 尽量不要使用非参数化的负向查询,这将导致无法使用索引,如<>, !=, !>, !<, not in, not like, not exists, not between, is not null, like ‘%
’ - 当需要验证是否有符合条件的记录时,使用exists,不要使用count(*)
- 不要单独索引,(a,b,c)免费获得(a)(a,b)
- mysql查询只使用一个索引
- 尽量不要包含多个列的排序
- 最左规则: mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配
- 对于 or 子句,如果要利用索引,则 or 之间的每个条件列都必须用到索引,因为一旦一个用不到那mysql就选择全表,反正要全表何必多一次索引查询
- 把 text 或 blob 列分离到单独的表中
- 将大字段、访问频率低的字段拆分到单独的表中存储, 分离冷热数据
- 在对表获取行锁的时候,要尽量的使用索引检索纪录,如果没有使用索引访问,那么即便你只是要更新其中的一行纪录,也是全表锁定的
- 由于 MySQL 的行锁是针对索引加的锁,不是针对纪录加的锁
- 除非确实要消除重复的行,否则建议使用 union all
- 使用 truncate 代替 delete
- 使用合理的分页方式以提高分页效率
- 避免使用存储过程、触发器、函数、UDF、events等,容易将业务逻辑和DB耦合在一起
- 避免使用JOIN。
- 在Join表的时候使用相同类型的列,并将其索引
- 永远为每张表设置一个ID和两个时间
结论
所以简单的写个sql会发现一堆的套路,而且同一个数据库的不同版本可能还有差异,更别提不同的数据库了,还有更严重的
- 现在SQL可以查询大数据了,其泄露程度你又得重新学习
- 分库了,分表了,完蛋了