什么是分组group

什么是分组group,第1张

由于GROUP BY 实际上也同样会进行排序 *** 作,而且与ORDER BY 相比,GROUP BY 主要只是多了排序之后的分组 *** 作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在GROUP BY 的实现过程中,与 ORDER BY 一样也可以利用到索引。 在MySQL 中,GROUP BY 的实现同样有多种(三种)方式,其中有两种方式会利用现有的索引信息来完成 GROUP BY,另外一种为完全无法使用索引的场景下使用。下面我们分别针对这三种实现方式做一个分析。 1、使用松散(Loose)索引扫描实现 GROUP BY 何谓松散索引扫描实现 GROUP BY 呢?实际上就是当 MySQL 完全利用索引扫描来实现 GROUP BY 的时候,并不需要扫描所有满足条件的索引键即可完成 *** 作得出结果。 下面我们通过一个示例来描述松散索引扫描实现 GROUP BY,在示例之前我们需要首先调整一下 group_message 表的索引,将 gmt_create 字段添加到 group_id 和 user_id 字段的索引中: sky@localhost: example 08:49:45>create index idx_gid_uid_gc ->on group_message(group_id,user_id,gmt_create) Query OK, rows affected (0.03 sec) Records: 96 Duplicates: 0 Warnings: 0 sky@localhost: example 09:07:30>drop index idx_group_message_gid_uid ->on group_message Query OK, 96 rows affected (0.02 sec) Records: 96 Duplicates: 0 Warnings: 0然后再看如下 Query 的执行计划: sky@localhost: example 09:26:15>EXPLAIN ->SELECT user_id,max(gmt_create) ->FROM group_message ->WHERE group_id <10 ->GROUP BY group_id,user_id\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: group_message type: range possible_keys: idx_gid_uid_gc key: idx_gid_uid_gc key_len: 8 ref: NULL rows: 4 Extra: Using whereUsing index for group-by我们看到在执行计划的 Extra 信息中有信息显示“Using index for group-by”,实际上这就是告诉我们,MySQL Query Optimizer 通过使用松散索引扫描来实现了我们所需要的 GROUP BY *** 作。 下面这张图片描绘了扫描过程的大概实现: 要利用到松散索引扫描实现 GROUP BY,需要至少满足以下几个条件: ◆GROUP BY 条件字段必须在同一个索引中最前面的连续位置 ◆在使用GROUP BY 的同时,只能使用 MAX 和 MIN 这两个聚合函数 ◆如果引用到了该索引中 GROUP BY 条件之外的字段条件的时候,必须以常量形式存在 为什么松散索引扫描的效率会很高? 因为在没有WHERE子句,也就是必须经过全索引扫描的时候, 松散索引扫描需要读取的键值数量与分组的组数量一样多,也就是说比实际存在的键值数目要少很多。而在WHERE子句包含范围判断式或者等值表达式的时候, 松散索引扫描查找满足范围条件的每个组的第1个关键字,并且再次读取尽可能最少数量的关键字。 2.使用紧凑(Tight)索引扫描实现 GROUP BY 紧凑索引扫描实现 GROUP BY 和松散索引扫描的区别主要在于他需要在扫描索引的时候,读取所有满足条件的索引键,然后再根据读取恶的数据来完成 GROUP BY *** 作得到相应结果。  sky@localhost : example 08:55:14>EXPLAIN ->SELECT max(gmt_create) ->FROM group_message ->WHERE group_id = 2 ->GROUP BY user_id\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: group_message type: ref possible_keys: idx_group_message_gid_uid,idx_gid_uid_gc key: idx_gid_uid_gc key_len: 4 ref: const rows: 4 Extra: Using whereUsing index 1 row in set (0.01 sec)这时候的执行计划的 Extra 信息中已经没有“Using index for group-by”了,但并不是说 MySQL 的 GROUP BY *** 作并不是通过索引完成的,只不过是需要访问 WHERE 条件所限定的所有索引键信息之后才能得出结果。这就是通过紧凑索引扫描来实现 GROUP BY 的执行计划输出信息。 下面这张图片展示了大概的整个执行过程: 在 MySQL 中,MySQL Query Optimizer 首先会选择尝试通过松散索引扫描来实现 GROUP BY *** 作,当发现某些情况无法满足松散索引扫描实现 GROUP BY 的要求之后,才会尝试通过紧凑索引扫描来实现。 当 GROUP BY 条件字段并不连续或者不是索引前缀部分的时候,MySQL Query Optimizer 无法使用松散索引扫描,设置无法直接通过索引完成 GROUP BY *** 作,因为缺失的索引键信息无法得到。但是,如果 Query 语句中存在一个常量值来引用缺失的索引键,则可以使用紧凑索引扫描完成 GROUP BY *** 作,因为常量填充了搜索关键字中的“差距”,可以形成完整的索引前缀。这些索引前缀可以用于索引查找。而如果需要排序GROUP BY结果,并且能够形成索引前缀的搜索关键字,MySQL还可以避免额外的排序 *** 作,因为使用有顺序的索引的前缀进行搜索已经按顺序检索到了所有关键字。 3.使用临时表实现 GROUP BY MySQL 在进行 GROUP BY *** 作的时候要想利用所有,必须满足 GROUP BY 的字段必须同时存放于同一个索引中,且该索引是一个有序索引(如 Hash 索引就不能满足要求)。而且,并不只是如此,是否能够利用索引来实现 GROUP BY 还与使用的聚合函数也有关系。 前面两种 GROUP BY 的实现方式都是在有可以利用的索引的时候使用的,当 MySQL Query Optimizer 无法找到合适的索引可以利用的时候,就不得不先读取需要的数据,然后通过临时表来完成 GROUP BY *** 作。 sky@localhost : example 09:02:40>EXPLAIN ->SELECT max(gmt_create) ->FROM group_message ->WHERE group_id >1 and group_id <10 ->GROUP BY user_id\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: group_message type: range possible_keys: idx_group_message_gid_uid,idx_gid_uid_gc key: idx_gid_uid_gc key_len: 4 ref: NULL rows: 32 Extra: Using whereUsing indexUsing temporaryUsing filesort这次的执行计划非常明显的告诉我们 MySQL 通过索引找到了我们需要的数据,然后创建了临时表,又进行了排序 *** 作,才得到我们需要的 GROUP BY 结果。整个执行过程大概如下图所展示: 当 MySQL Query Optimizer 发现仅仅通过索引扫描并不能直接得到 GROUP BY 的结果之后,他就不得不选择通过使用临时表然后再排序的方式来实现 GROUP BY了。 在这样示例中即是这样的情况。 group_id 并不是一个常量条件,而是一个范围,而且 GROUP BY 字段为 user_id。所以 MySQL 无法根据索引的顺序来帮助 GROUP BY 的实现,只能先通过索引范围扫描得到需要的数据,然后将数据存入临时表,然后再进行排序和分组 *** 作来完成 GROUP BY。

分隔符$

DROP PROCEDURE`t_girl``sp_split_table`$

。的CREATE PROCEDURE`t_girl``sp_split_table`()

开始

声明所做的诠释默认0

申报v_user_name VARCHAR(20)默认“,

申报v_table_name VARCHAR (64)默认“

-获取所有用户的名称。

user_name的选择USER_NAME t_group组申报cur1光标

-处理错误或警告。

宣布继续完成1329集= 1的处理程序

-打开游标。

开放cur1

而<>1

取到v_user_name cur1

如果没有这样做,那么

-获取表名。

设置v_table_name = CONCAT('t_group_',v_user_name),

-创建新的额外的表

集@ stmt的= CONCAT('创建表',v_table_name“像t_group')

S1 @ stmt的准备

执行S1

降准备S1

-数据加载到

stmt的CONCAT(“的插入',v_table_name,'SELECT *从t_group其中user_name =''',v_user_name,'')

准备S1 @ stmt的

执行S1

降准备S1

结束,如果

结束而

-关闭游标。

密切cur1

-从内存中自由变量的

设置@ stmt的= NULL

完$ $

界定符

2,试验表。

我们当前用一个有一千万条记录的表来做测试。

MySQL的教程 >SELECT COUNT(*)从t_group

+ --------- +

|计数(*)|

+ --------- +

| 10388608 |

+ ----- ----- +

1集行(0.00秒)

表结构

的MySQL>DESC t_group

+ ----------- + ------------------ + - + - ---- + ------ + ---------------- +

|场|类型| NULL |重点|中|额外|

+ ----------- + ------------------ + ------ + - - + ------ + ---------------- +

| ID | INT(10)无符号|无|的PRI | NULL | AUTO_INCREMENT |

|钱|十进制(10,2)| NO | | | |

| USER_NAME | VARCHAR(20)号的MUL | |

| | CREATE_TIME |时间戳| NO | | CURRENT_TIMESTAMP的| |

+ - --------- + ------------------ + ------ + ----- + ------- ------------ + ---------------- +

4行集(0.00秒)

索引情况。

mysql的显示指数从 表| Non_unique | Key_name | Seq_in_index | COLUMN_NAME |校勘基数Sub_part |盒装NULL | Index_type |评论 t_group | 0 |小学| 1 | ID | | 10388608 |空|空| | B树| | | t_group | | idx_user_name | 1 | USER_NAME | | |空|空| | B树| | | t_group | 1 | idx_combination1 | 1 | USER_NAME | | |空|空| | B树| | | t_group | | idx_combination1 | 2 |钱| | 3776 |空|空| | B树| 集行(0.00秒)

注:

idx_combination1这个索引什么必须的因为要对USER_NAME来集团此时属于松散索引扫描当然完了后你可以干掉她

idx_user_name

MYSQL>选择t_group USER_NAME 1 USER_NAME组

+ ----------- +

| USER_NAME |

+ ---- +

|大卫

| | 狮子座

| | 利维娅|

|露西|

|撒拉|

|西蒙|

|索尼

| | 晴天|

+ ---- +

8集行(0.00秒)

所以结果调表应该是这样的。

mysql的像“t_group_%>SHOW TABLES

+ ------------------------------ +

| Tables_in_t_girl(t_group_%)|

+ ---------- +

| t_group_david

| | t_group_leo

| | t_group_livia

| | t_group_lucy |

| t_group_sarah

| t_group_simon |

| t_group_sony

| | t_group_sunny时加入|

+ ------------------------------ +

8行集( 0.00秒)

3,对比结果。

MySQL的>SELECT COUNT(*)从t_group的USER_NAME ='国宝'

+ ---------- +

|计数(*)|

+ --------- +

| 1298576 |

+ ---------- +

1行集(1.71秒)

执行了将近2秒。

MySQL的>SELECT COUNT(*)从t_group_david的

+ --------- +

|计数(*)|

+ --------- +

| 1298576 |

+ --- ---- +

1集行(0.00秒)

几乎什么瞬间的

MySQL的>SELECT COUNT(*)从t_group其中user_name <>“国宝”

+ --------- +

|计数(*)|

+ --------- +

| 9090032 |

+ --------- +

1集行(9.26秒)

执行了将近10秒,可以想象,这个什么实际的项目大全-宜配网什么不能忍受的。

MySQL的选择(SELECT COUNT(*)从t_group) - (SELECT COUNT(*)来自t_group_david)总额

+ --------- +

|总时加入|

+ --------- +

| 9090032 |

+ --- --- +

1集行(0.00秒)

几乎什么瞬间的

我们来看看聚集函数。

对于原表的 *** 作。

MYSQL>选择分(钱),MAX(钱)从t_group其中user_name ='国宝'

+ ------------ + ------------ +

| MIN (钱)| MAX(钱)|

+ ------------ + ------------ +

| -6.41 | 500.59 |

+ ------- ----- + ------------ +

1集行(0.00秒)

最小,最大值都是全索引扫描。所以是瞬间的

MySQL的SELECT SUM(钱),平均(钱)从t_group其中user_name ='国宝'

+ -------------- + ------------ +

|总和(钱)平均(钱)|

+ -------------- + ------------ +

| 319992383.84 | 246.417910 |

+ ----------------------- - ------------ +

1集行(2.15秒)

其他聚集函数的结果就不是完整的索引扫描了耗时2.15秒。

对于小表的 *** 作

MYSQL>选择分(钱),最大从t_group_david(钱)

+ ------------ + ------------ +

|分(钱)| MAX(钱)|

+ ------------ + ------------ +

| -6.41 | 500.59 |

+ ------- ----- + ------------ +

1集行(1.50秒)

眼霜最小值完全什么全表扫描,耗时1.50秒,不划算。以此看来,

MySQL的>SELECT SUM(钱),AVG(钱)从t_group_david

+ -------------- + ------------ +

|总和(钱)| AVG (钱)|

+ -------------- + ------------ +

| 319992383.84 | 246.417910 |

+ ---- -------- + ------------ +

1行集(1.68秒)

取得这两个结果也是花了快2秒,快了一点。

我们来看看这个小表的结构。

MYSQL>DESC t_group_david

+ ----------- + ------------------ - ----- + ----- + ------ + ---------------- +

|领域| | NULL |键型|中|额外时加入|

+ ------------- + ------------------ + - - + ----- + ------ + ---------------- +

| ID | INT (10)无符号| |优先级| NULL | AUTO_INCREMENT

| | 钱|十进制(10,2)|号| | |

| | USER_NAME | VARCHAR(20)号的MUL | |

| | CREATE_TIME |时间戳| NO | | CURRENT_TIMESTAMP的| |

+ ----------- + ------------------ + ------ + ----- + ------ + ---------------- +

4行集(0.00秒)

明显的user_name的属性是多余的那么就干掉它。

MySQL的>ALTER TABLE t_group_david下降USER_NAME

查询确定,1298576行的影响(7.58秒)

记录:1298576重复:0警告:0

现在来重新对小表运行查询

MYSQL>选择分(钱),最大(钱)从t_group_david

+ ------------ + ------------ +

|分(钱)最大(钱)|

+ ------------ + ------------ +

| -6.41 | 500.59 |

+ ------------ + ------------ +

1行集(0.00秒)

此时是瞬间的。

MYSQL>SELECT SUM(钱),AVG(钱)从t_group_david

+ -------------- + ------------ +

总和(钱)AVG(钱)|

+ -------------- + ------------ +

| 319992383.84 | 246.417910 |

+ - ---------- + ------------ +

1行集(0.94秒)

这次算是控制在一秒以内了。

MySQL的>中止

1.应尽量避免在where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:selectidfromtwherenumisnullNULL对于大多数数据库都需要特殊处理,mysql也不例外,它需要的代码,的检查和特殊的索引逻辑,有些开发人员完全没有意识到,创建表时NULL是默认值,但大多数时候应该使用NOTNULL,或者使用一个特殊的值,如0,-1作为默认值。不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列这样的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能。任何在where子句中使用isnull或isnotnull的语句优化器是不允许使用索引的。此例可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:selectidfromtwherenum=02.应尽量避免在where子句中使用!=或 *** 作符,否则将引擎放弃使用索引而进行全表扫描。MySQL只有对以下 *** 作符才使用索引:,>=,BETWEEN,IN,以及某些时候的LIKE。可以在LIKE *** 作中使用索引的情形是指另一个 *** 作数不是以通配符(%或者_)开头的情形。例如,“SELECTidFROMtWHEREcolLIKE'Mich%'”这个查询将使用索引,但“SELECTidFROMtWHEREcolLIKE'%ike'”这个查询不会使用索引。3.应尽量避免在where子句中使用or来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:selectidfromtwherenum=10ornum=20可以这样查询:selectidfromtwherenum=10unionallselectidfromtwherenum=204.in和notin也要慎用,否则会导致全表扫描,如:selectidfromtwherenumin(1,2,3)对于连续的数值,能用between就不要用in了:selectidfromtwherenumbetween1and35.下面的查询也将导致全表扫描:selectidfromtwherenamelike'%abc%'或者selectidfromtwherenamelike'%abc'或者若要提高效率,可以考虑全文检索。而selectidfromtwherenamelike'abc%'才用到索引7.如果在where子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:selectidfromtwherenum=@num可以改为强制查询使用索引:selectidfromtwith(index(索引名))wherenum=@num8.应尽量避免在where子句中对字段进行表达式 *** 作,这将导致引擎放弃使用索引而进行全表扫描。如:selectidfromtwherenum/2=100应改为:selectidfromtwherenum=100*29.应尽量避免在where子句中对字段进行函数 *** 作,这将导致引擎放弃使用索引而进行全表扫描。如:selectidfromtwheresubstring(name,1,3)='abc'--nameselectidfromtwheredatediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id应改为:selectidfromtwherenamelike'abc%'selectidfromtwherecreatedate>='2005-11-30'andcreatedate<'2005-12-1'10.不要在where子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。12.不要写一些没有意义的查询,如需要生成一个空表结构:selectcol1,col2into#tfromtwhere1=0这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:createtable#t()13.很多时候用exists代替in是一个好的选择:selectnumfromawherenumin(selectnumfromb)用下面的语句替换:selectnumfromawhereexists(select1frombwherenum=a.num)14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。15.索引并不是越多越好,索引固然可以提高相应的select的效率,但同时也降低了insert及update的效率,因为insert或update时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。16.应尽可能的避免更新clustered索引数据列,因为clustered索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新clustered索引数据列,那么需要考虑是否应将该索引建为clustered索引。17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。18.尽可能的使用varchar/nvarchar代替char/nchar,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。19.任何地方都不要使用select*fromt,用具体的字段列表代替“*”,不要返回用不到的任何字段。20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。21.避免频繁创建和删除临时表,以减少系统表资源的消耗。22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。23.在新建临时表时,如果一次性插入数据量很大,那么可以使用selectinto代替createtable,避免造成大量log,以提高速度;如果数据量不大,为了缓和系统表的资源,应先createtable,然后insert。24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先truncatetable,然后droptable,这样可以避免系统表的较长时间锁定。25.尽量避免使用游标,因为游标的效率较差,如果游标 *** 作的数据超过1万行,那么就应该考虑改写。26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。27.与临时表一样,游标并不是不可使用。对小型数据集使用FAST_FORWARD游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。28.在所有的存储过程和触发器的开始处设置SETNOCOUNTON,在结束时设置SETNOCOUNTOFF。无需在执行存储过程和触发器的每个语句后向客户端发送DONE_IN_PROC消息。29.尽量避免大事务 *** 作,提高系统并发能力。30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/8427915.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-16
下一篇 2023-04-16

发表评论

登录后才能评论

评论列表(0条)

保存