<转> MySQL中采用类型varchar(20)和varchar(255)对性能上的影响

<转> MySQL中采用类型varchar(20)和varchar(255)对性能上的影响,第1张

从MySQL4.1开始,varchar (N)中的N指的是该字段最多能存储多少个字符(characters),不是字节数。

不管是一个中英文字符或者数字、或者一个汉字,都当做一个字符。在4.1之前,N表示的是最大存储的字节数(bytes)。

也就是说varchar(20)和varchar(255)对应的索引长度分别为20 3(utf-8)(+2+1),255 3(utf-8)(+2+1),其中"+2"用来存储长度信息,“+1”用来标记是否为空,加载索引信息时用varchar(255)类型会占用更多的内存; (备注:当字段定义为非空的时候,是否为空的标记将不占用字节)例如,测试sql(InnoDB引擎)如下:

varchr(10)变长字段且允许NULL:10*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL)+2(变长字段)

varchr(10)变长字段且不允许NULL:10*(Character Set:utf8=3,gbk=2,latin1=1)+2(变长字段)

char(10)固定字段且允许NULL:10*(Character Set:utf8=3,gbk=2,latin1=1)+1(NULL) char(10)固定

字段且允许NULL:10*(Character Set:utf8=3,gbk=2,latin1=1)根据这个值,就可以判断索引使用情况,特别是在组合索引的时候,判断所有的索引字段都被查询用到。

varchar(20)与varchar(255)都是保持可变的字符串,当使用ROW_FORMAT=FIXED创建MyISAM表时,会为每行使用固定的长度空间,这样设置不同的varchar长度值时,存储相同数据所占用的空间是不一样。

另外,我们还遇到过有人以为varchar不能设置超过255的人。其实varchar没有明确最大长度,然后有人说那65535字节(bytes)不是吗?

但是事实上如果你用的 utf8 编码的话按理最大可以设置到 varchar(21845),但是其实一般都会说你超出。其实 MySQL要求一个行定义长度不能超过 65535 bytes (所有字符串类型字段包括其字段名称占用空间都计算在内, text、blob等大字段类型除外)。

首先我们要知道一个概念: InnoDB存储引擎的表索引的前缀长度最长是767字节(bytes) (前缀长度适用于mysql5.6及以前,后续版本有变化),你如果需要建索引,就不能超过 767 bytes(mysql5.6及以前,后续版本有变化)。

utf8编码时 255 3=765bytes ,恰恰是能建索引情况下的最大值.*

如果像lavavel5.3往后 使用的是utf8mb4编码,默认字符长度则应该是 767除以4向下取整,也就是191。

MySQL 5.5的发布带来了许多增强的功能,虽然已经报道了很多增强功能,如半同步复制,但大家却忽略了分区方面的增强,有时甚至还对其真正意义产生了误解,在这篇文章中,我们希望解释一下这些很酷的增强,特别是我们大多数人还没有完全理解的地方。51CTO向您推荐《MySQL数据库入门与精通教程》。

非整数列分区

任何使用过分区的人应该都遇到过不少问题,特别是面对非整数列分区时,MySQL 5.1只能处理整数列分区,如果你想在日期或字符串列上进行分区,你不得不使用函数对其进行转换。

MySQL 5.5中新增了两类分区方法,RANG和LIST分区法,同时在新的函数中增加了一个COLUMNS关键词。我们假设有这样一个表:

1.  CREATE TABLE expenses (  

2.    expense_date DATE NOT NULL,  

3.    category VARCHAR(30),  

4.    amount DECIMAL (10,3)  

5.  ) 

如果你想使用MySQL 5.1中的分区类型,那你必须将类型转换成整数,需要使用一个额外的查找表,到了MySQL 5.5中,你可以不用再进行类型转换了,如:

1.  ALTER TABLE expenses  

2.  PARTITION BY LIST COLUMNS (category)  

3.  (  

4.    PARTITION p01 VALUES IN ( 'lodging', 'food'),  

5.    PARTITION p02 VALUES IN ( 'flights', 'ground transportation'),  

6.    PARTITION p03 VALUES IN ( 'leisure', 'customer entertainment'),  

7.    PARTITION p04 VALUES IN ( 'communications'),  

8.    PARTITION p05 VALUES IN ( 'fees')  

9.  ) 

这样的分区语句除了更加易读外,对数据的组织和管理也非常清晰,上面的例子只对category列进行分区。

在MySQL 5.1中使用分区另一个让人头痛的问题是date类型(即日期列),你不能直接使用它们,必须使用YEAR或TO_DAYS转换这些列,如:

1.  /* 在MySQL 5.1中*/  

2.  CREATE TABLE t2  

3.  (  

4.    dt DATE 

5.  )  

6.  PARTITION BY RANGE (TO_DAYS(dt))  

7.  (  

8.    PARTITION p01 VALUES LESS THAN (TO_DAYS('2007-01-01')),  

9.    PARTITION p02 VALUES LESS THAN (TO_DAYS('2008-01-01')),  

10.   PARTITION p03 VALUES LESS THAN (TO_DAYS('2009-01-01')),  

11.   PARTITION p04 VALUES LESS THAN (MAXVALUE)) 

12.  

13. SHOW CREATE TABLE t2 \G  

14. *************************** 1. row ***************************  

15.        Table: t2  

16. Create Table: CREATE TABLE `t2` (  

17.   `dt` date DEFAULT NULL 

18. ) ENGINE=MyISAM DEFAULT CHARSET=latin1  

19. /*!50100 PARTITION BY RANGE (TO_DAYS(dt))  

20. (PARTITION p01 VALUES LESS THAN (733042) ENGINE = MyISAM,  

21.  PARTITION p02 VALUES LESS THAN (733407) ENGINE = MyISAM,  

22.  PARTITION p03 VALUES LESS THAN (733773) ENGINE = MyISAM,  

23.  PARTITION p04 VALUES LESS THAN MAXVALUE ENGINE = MyISAM) */ 

看上去非常糟糕,当然也有变通办法,但麻烦确实不少。使用YEAR或TO_DAYS定义一个分区的确让人费解,查询时不得不使用赤裸列,因为加了函数的查询不能识别分区。

但在MySQL 5.5中情况发生了很大的变化,现在在日期列上可以直接分区,并且方法也很简单。

1.  /*在MySQL 5.5中*/  

2.  CREATE TABLE t2  

3.  (  

4.    dt DATE 

5.  )  

6.  PARTITION BY RANGE COLUMNS (dt)  

7.  (  

8.    PARTITION p01 VALUES LESS THAN ('2007-01-01'),  

9.    PARTITION p02 VALUES LESS THAN ('2008-01-01'),  

10.   PARTITION p03 VALUES LESS THAN ('2009-01-01'),  

11.   PARTITION p04 VALUES LESS THAN (MAXVALUE)) 

12.  

13. SHOW CREATE TABLE t2 \G  

14. *************************** 1. row ***************************  

15.        Table: t2  

16. Create Table: CREATE TABLE `t2` (  

17.   `dt` date DEFAULT NULL 

18. ) ENGINE=MyISAM DEFAULT CHARSET=latin1  

19. /*!50500 PARTITION BY RANGE  COLUMNS(dt)  

20. (PARTITION p01 VALUES LESS THAN ('2007-01-01') ENGINE = MyISAM,  

21.  PARTITION p02 VALUES LESS THAN ('2008-01-01') ENGINE = MyISAM,  

22.  PARTITION p03 VALUES LESS THAN ('2009-01-01') ENGINE = MyISAM,  

23.  PARTITION p04 VALUES LESS THAN (MAXVALUE) ENGINE = MyISAM) */ 

在这里,通过函数定义和通过列查询之间没有冲突,因为是按列定义的,我们在定义中插入的值是保留的。

多列分区

COLUMNS关键字现在允许字符串和日期列作为分区定义列,同时还允许使用多个列定义一个分区,你可能在官方文档中已经看到了一些例子,如: 

1.  CREATE TABLE p1 (  

2.    a INT,  

3.    b INT,  

4.    c INT 

5.  )  

6.  PARTITION BY RANGE COLUMNS (a,b)  

7.  (  

8.    PARTITION p01 VALUES LESS THAN (10,20),  

9.    PARTITION p02 VALUES LESS THAN (20,30),  

10.   PARTITION p03 VALUES LESS THAN (30,40),  

11.   PARTITION p04 VALUES LESS THAN (40,MAXVALUE),  

12.   PARTITION p05 VALUES LESS THAN (MAXVALUE,MAXVALUE)  

13. ) 

14.  

15. CREATE TABLE p2 (  

16.   a INT,  

17.   b INT,  

18.   c INT 

19. )  

20. PARTITION BY RANGE COLUMNS (a,b)  

21. (  

22.   PARTITION p01 VALUES LESS THAN (10,10),  

23.   PARTITION p02 VALUES LESS THAN (10,20),  

24.   PARTITION p03 VALUES LESS THAN (10,30),  

25.   PARTITION p04 VALUES LESS THAN (10,MAXVALUE),  

26.   PARTITION p05 VALUES LESS THAN (MAXVALUE,MAXVALUE)  

27. ) 

同样还有PARTITION BY RANGE COLUMNS (a,b,c)等其它例子。由于我很长时间都在使用MySQL 5.1的分区,我对多列分区的含义不太了解,LESS THAN (10,10)是什么意思?如果下一个分区是LESS THAN (10,20)会发生什么?相反,如果是(20,30)又会如何?

所有这些问题都需要一个答案,在回答之前,他们需要更好地理解我们在做什么。

开始时可能有些混乱,当所有分区有一个不同范围的值时,实际上,它只是在表的一个列上进行了分区,但事实并非如此,在下面的例子中:

1.  CREATE TABLE p1_single (  

2.    a INT,  

3.    b INT,  

4.    c INT 

5.  )  

6.  PARTITION BY RANGE COLUMNS (a)  

7.  (  

8.    PARTITION p01 VALUES LESS THAN (10),  

9.    PARTITION p02 VALUES LESS THAN (20),  

10.   PARTITION p03 VALUES LESS THAN (30),  

11.   PARTITION p04 VALUES LESS THAN (40),  

12.   PARTITION p05 VALUES LESS THAN (MAXVALUE)  

13. ) 

它和前面的表p1不一样,如果你在表p1中插入(10,1,1),它将会进入第一个分区,相反,在表p1_single中,它将会进入第二个分区,其原因是(10,1)小于(10,10),如果你仅仅关注第一个值,你还没有意识到你在比较一个元组,而不是一个单一的值。

现在我们来分析一下最难懂的地方,当你需要确定某一行应该放在哪里时会发生什么?你是如何确定类似(10,9) <(10,10)这种运算的值的?答案其实很简单,当你对它们进行排序时,使用相同的方法计算两条记录的值。

1.  a=10  

2.  b=9  

3.  (a,b) < (10,10) ?  

4.   

5.  # evaluates to:  

6.   

7.  (a < 10)  

8.  OR 

9.  ((a = 10) AND ( b < 10))  

10.  

11. # which translates to:  

12.  

13. (10 < 10)  

14. OR 

15. ((10 = 10) AND ( 9 < 10)) 

如果有三列,表达式会更长,但不会更复杂。你首先在第一个项目上测试小于运算,如果有两个或更多的分区与之匹配,接着就测试第二个项目,如果不止一个候选分区,那还需要测试第三个项目。

下图所显示的内容表示将遍历三条记录插入到使用以下代码定义的分区中:

(10,10),

(10,20),

(10,30),

(10, MAXVALUE)

 

 

图 2 元组比较。当第一个值小于分区定义的第一个范围时,那么该行将属于这里了。

 

 

图 3 元组比较。当第一个值等于分区定义的第一个范围,我们需要比较第二个项目,如果它小于第二个范围,那么该行将属于这里了。

 

 

图 4 元组比较。当第一个值和第二个值等于他们对应的范围时,如果元组不小于定义的范围,那么它就不属于这里,继续下一步。

 

 

图 5 元组比较。在下一个范围时,第一个项目是等于,第二个项目是小于,因此元组更小,那么该行就属于这里了。

在这些图的帮助下,我们对插入一条记录到多列分区表的步骤有了更深的了解,这些都是理论上的,为了帮助你更好地掌握新功能,我们再来看一个更高级一点的例子,对于比较务实的读者更有意义,下面是表的定义脚本:

1.  CREATE TABLE employees (  

2.    emp_no int(11) NOT NULL,  

3.    birth_date date NOT NULL,  

4.    first_name varchar(14) NOT NULL,  

5.    last_name varchar(16) NOT NULL,  

6.    gender char(1) DEFAULT NULL,  

7.    hire_date date NOT NULL 

8.  ) ENGINE=MyISAM  

9.  PARTITION BY RANGE  COLUMNS(gender,hire_date)  

10. (PARTITION p01 VALUES LESS THAN ('F','1990-01-01') ,  

11.  PARTITION p02 VALUES LESS THAN ('F','2000-01-01') ,  

12.  PARTITION p03 VALUES LESS THAN ('F',MAXVALUE) ,  

13.  PARTITION p04 VALUES LESS THAN ('M','1990-01-01') ,  

14.  PARTITION p05 VALUES LESS THAN ('M','2000-01-01') ,  

15.  PARTITION p06 VALUES LESS THAN ('M',MAXVALUE) ,  

16.  PARTITION p07 VALUES LESS THAN (MAXVALUE,MAXVALUE) 

和上面的例子不同,这个例子更好理解,第一个分区用来存储雇佣于1990年以前的女职员,第二个分区存储股用于1990-2000年之间的女职员,第三个分区存储所有剩下的女职员。对于分区p04到p06,我们策略是一样的,只不过存储的是男职员。最后一个分区是控制情况。

看完后你可能要问,我怎么知道某一行存储在那个分区中的?有两个办法,第一个办法是使用与分区定义相同的条件作为查询条件进行查询。


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

原文地址:https://54852.com/zaji/8589864.html

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

发表评论

登录后才能评论

评论列表(0条)

    保存