Mysql基础课七:语句操作分析上

count(*)

  1. count(*) 在不同 Mysql 引擎中,实现不一样,如,MyISAM 引擎是将表的总行数存在了磁盘上,查询就直接返回;而 InnoDB 引擎,是需要将数据一行行从引擎中读出来,然后累积计数的;所以 InnoDB 对于大表的 count 有时候很慢;

  2. InnoDB 之所以要这么做,是因为它的事务隔离级别,是通过 MVCC 实现的,每行记录都需要判断是否对改会话可见,所以只能一行行读出依次判断;

  3. 其实 InnoDB 也做了优化,因为任何索引树都包含了全部数据,所以 InnoDB 会选择一个最小的索引树进行遍历;注意 show table status 显示的行数是不能直接使用的,因为不太准确;

  4. 真实应用中,可以通过将 count(*) 计数值存起来,每次有对表有增删操作,就修改该计数值;注意 表计数值存在数据库中,并将其和表增删操作维护成一个事务;

  5. count(*),count(1),count(主键id) 都是返回满足条件的结果集的总行数,而 count(字段) 会返回满足条件的,且字段值不为 null 的总个数,建议统计表中所有数据,使用 count (*)

order by

  1. 以 select city,name,age from user where city = ‘杭州’ order by name limit 1000 为例,city 上创建有索引,explain 发现,extra 字段中 Using filesort 表示需要排序,Mysql 会给每个线程分配一块 sort_buffer 用于排序;

  2. 排序过程,Mysql 会首先从索引树中找到第一个 city = ‘杭州’ 的主键id,然后回表得到 name,age,city 字段值,并存入 sort_buffer中,然后依次重复,直到找到 city != ‘杭州’ 的记录为止,然后对 sort_buffer 中的数据按照 name 进行快速排序,将排序结果前 1000 行返回;

  3. 如果要排序的数据量大于 sort_buffer_size,排序就会利用磁盘空间辅助完成,并且如果查询需要的字段比较多,Mysql 可能选择将 排序字段和主键 id 放入 sort_buffer,排好序后,回表得到查询结果,这种排序称为 rowid 排序,而全部查询字段放入 sort_buffer 称为全字段排序;

  4. 其中这个例子中,更优的一种解决办法,是在 city 和 name 上建立联合索引,这样 order by 就不用单独排序了,从 (city,name) 的索引树上取出 city 相等的结果,name就是有序的,并且如果建立 (city,name,age) 的覆盖索引,查询就会更快了;

  5. 如果 SQL 语句是 select * from user where city in (‘杭州’,‘苏州’) order by name limit 100,这时候直接查询,是需要重新排序的,性能很差,可以通过拆成两条语句,得到两个 100 条数据的有序集合,然后利用归并排序或者优先级队列排序,得到最终的100条结果;

  6. 如果在5的基础上,改为 limit 10000, 100,表示从结果中丢掉前 10000 个,然后依次取 100 个返回,这种情况,拆分的语句每条查询就需要 10100 个,并且如果返回 10100 数据量大,就可以先查询 id,得到 100 个结果后,再按照 id 查询;

随机排序

  1. 假设要求,每次从单词表中随机取出三个单词,一种方案是,进行随机排序,然后取出三个值,即 select word from words order by rand() limit 3;

  2. order by rand() 通过 explain 发现,extra 字段是 Using temporary,Using filesort,表示使用了临时表和执行了排序操作,注意临时内存表,会优先使用 rowid排序,因为回表过程是在内存中,非常快;

  3. 上面语句执行逻辑:首先创建一个内存临时表,存储引擎是 memory,表中两个字段 R,W;然后从 words 表中,按主键顺序取出所有 word 值,并对每一个值通过 rand() 生成一个随机小数,存放到临时表中,R 是随机小数,W 是 word值;最后初始化 sort_buffer,一个字段是 R 值,一个是位置信息 pos,根据 R 值进行排序,排序完成后,取出前三个结果的 pos 值,依次到临时表中取出 word 值,返回给客户端;
    Mysql基础课七:语句操作分析上_第1张图片

  4. InnoDB 表中如果没有主键,就会自动生成一个 rowid 作为主键,对于有主键的 InnoDB 表,rowid 即为主键,在 memory 临时表来说,rowid 就是数组下标;

  5. 总结,order by rand() 的方式,使得执行过程复杂,需要大量的扫描行数,不建议使用;

  6. 第二种解决方案,是使用一个随机算法,如 Y = floor(行总数 * rand()),limit Y,1 这样来取得一个随机值,然后同样的办法,得到三个随机数 Y1,Y2,Y3,分别 limit Y1(Y2 / Y3),1 这样来取出三个值即可,这种方案推荐使用;

条件字段函数操作

  1. 对字段使用函数,会导致无法使用该字段上的索引快速查找功能,而使用全索引扫描或者主键索引扫描,这是因为,使用函数后,就不是有序的,优化器就无法使用索引快速查找了;

  2. 举例,select count(*) from tradelog where month(trade_time) = 7;查询所有交易 7 月份的记录总数,就不会用的索引的快速查找,而是走索引全扫描,对每一个值进行判断,性能是比较低的,可以通过改造,使用如图代替;

	select count(*) from tradelog where (trade_time >= '2016-7-1' and trade_time <'2016-8-1') or 
	(trade_time >= '2017-7-1' and trade_time <'2017-8-1') 
  1. 注意只要字段上使用了函数,无论是否使用后是否仍有序,Mysql 都不会使用索引的快速查找,例如,select * from tradelog where id + 1 = 1000;需要改成 id = 1000 -1 才会使用索引;

隐式类型转换

  1. 在 Mysql 中,字符串和数字进行比较,是将字符串转换成数字,所以操作 select * from tradelog where trade_id = 11010,trade_id 在数据库中是 varchar 类型的,就相当于 cast(trade_id as signed int) = 11010,就是在索引字段上使用了函数,就不会走索引树搜索功能;

  2. 如果两个字符集不同的表做连接操作,很可能因为隐式类型转换,导致无法使用索引;此时一种方案是统一字符集,另一种是在连接语句上,显示指定字符转换,并且放在索引字符赋值中;

  3. 如,select * from trade_log l, trade_detail d where l.trade_id = d.trade_id and l.id =2 中,表 l 和表 d 使用的不同字符集,执行器会先获取 l 中 id = 2 的记录,使用其 trade_id 的值去 d 表查询,如果 d 表的字符集 utf-8 是 l 表字符集 utf8mb4 的子集,就变成了 convert(traideid USING utf8mb4) = trade_id(id = 2),就不会使用索引,可以改成 where d.tradeid=CONVERT(l.tradeid USING utf8) and l.id=2;

你可能感兴趣的:(Mysql)