有一桩事不可避免,那就是可能要使用更多的空间,由于较串来讲,数的存储更为有效。咱们可能已经注意到,因为数和串处理方式的不一样,查询结果也有所不一样。例如,数的排序与串的排序就有所不一样。数2
小于数11,但串“2”按字典顺序大于“ 11”。可用以下数值内容的列来搞清这个问题: mysql
将零加到该列强制得出一个数值,可是这样合理吗?通常可能不合理。将该列做为数而不是串具备几个重要的含义。它对每一个列值实施串到数的转换,这是低效的。并且将该列的值转换为计算结果妨碍MySQL使用该列上的索引,下降了之后的查询速度。若是这些值一开始就是做为数值存储的,那么这些性能上的下降都不会出现。采用一种表示而不用另外一种的简单选择实际上并不简单,它在存储需求、查询效率以及处理性能等方面都会产生重要的影响。 sql
前面的例子说明,在选择列类型时,有如下几个问题须要考虑: 数据库
■ 列中存储何种类型的值?这是一个显而易见的问题,但必须肯定。可将任何类型的值表示为串,尤为当对数值使用更为合适的类型可能获得更好的性能时(日期和时间值也是这样)。可见,对要处理的值的类型进行评估不必定是件微不足道的事,特别在数据是别人的数据时更是如此。若是正在为其余人创建一个表,搞清列中要存储的值的类型极为重要,必须提足够多的问题以便获得做出决定的充足的信息。 数据库设计
■ 列值有特定的取值范围吗?若是它们是整数,它们老是非负值吗?若是这样,可采用UNSIGNED类型。若是它们是串,总能从定长值集中选出它们吗?若是这样,ENUM或SET是很合适的类型。在类型的取值范围与所用的存储量之间存在折衷。需有一个多“大”的类型?对于数,若是其取值范围有限,能够选择较小的类型,对取值范围几乎无限的数,应该选择较大的类型。对于串,可使它们短也可使它们长,但若是但愿存储的值只含不到10个字符,就不该该选用CHAR(255)。 函数
■ 性能与效率 问题是什么?有些类型比另一些类型的处理效率高。数值运算通常比串的运算快。短串比长串运行更快,并且磁盘消耗更小。定长类型比可变长类型的性能更好。■ 但愿对值进行什么样的比较?对于串,其比较能够是区分大小写的,也能够不区分大小写。其选择也会影响排序,由于它是基于比较的。 性能
■ 计划对列进行索引吗?若是计划对列进行索引,那么将会影响您对列类型的选择,由于有的MySQL版本不容许对某些类型进行索引,例如不能对BLOB 和TEXT类型进行索引。并且有的MySQL版本要求定义索引列为NOT NULL 的,这使您不能使用NULL 值。 测试
如今让咱们来更详细地考虑这些问题。这里要指出的是:在建立表时,但愿做出尽量好的列类型选择,但若是所做的选择其实际并非最佳的,这也不会带来多大的问题。可用ALTER TABLE 将原来选择的类型转换为更好的类型。在发现数据所含的值比原设想的大时,可像将SMALLINT 更换成MEDIUMINT那样简单地对类型进行更换。有时这种更换也可能很复杂,例如将CHAR 类型更换成具备特定值集的ENUM 类型。在MySQL3.23 及之后的版本中,可以使用PROCEDURE ANALYSE( ) 来得到表列的信息,诸如最小值和最大值以及推荐的覆盖列中值的取值范围的最佳类型。这有助于肯定使用更小的类型,从而改进涉及该表的查询的性能,并减小存储该表所需的空间量。 编码
2.3.1列中存储何种类型的值 spa
在决定列的类型时,首先应该考虑该列的值类型,由于这对于所选择的类型来讲具备最为明显的意义。一般,在数值列中存储数,在串列中存储串,在日期和时间列中存储日期和时间。若是数值有小数部分,那么应该用浮点列类型而不是整数类型,如此等等。有时也存在例外,不可一律而论。主要是为了有意义地选择类型,应该理解所用数据的特性。若是您打算存储本身的数据,大概对如何存储它们会有本身很好的想法。可是,若是其余人请您为他们建一个表,决定列类型有时会很困难。这不像处理本身的数据那么容易。应该充分地提问,搞清表实际应该包含何种类型的值。 设计
若是有人告诉您,某列须要记录“降雨量”。那是一个数吗?或者它“主要”是一个数值,即通常是但不老是编码成一个数吗?例如,在看电视新闻时,气象预报通常包括降雨量。有时是一个数(如“0.25”英寸的雨量),可是有时是“微量(trace)”降雨,意思是“雨根本就不大”。这对气象预报很合适,但在数据库中怎样存储?有可能须要将“微量”量化为一个数,以便能用数值列类型来记录降雨量,或许须要使用串,以即可以记录“微量”这个词。或者能够提出某种更为复杂的安排,使用一个数值列和一个串列,若是填充一个列就让另外一个列为NULL。很明显,可能的话,应该避免最后这种选择;最后这种选择使表难于理解,使查询更为困难。咱们通常尽可能以数值形式存储全部的行,并且只为了显示的须要才对它们进行转换。例如,若是小于0.01英寸的非零降雨量被视为微量,那么能够以下选择列值:
对于金钱的计算,须要处理元和分部分。这彷佛像浮点值,但FLOAT和DOUBLE 容易出现舍入错误,除了只须要大体精确的记录外,这些类型可能不适合。由于人们对本身的钱都是很敏感的,最好是用一种能提供完善的精确性的类型,例如:
■ 将钱表示为DECIMAL(M, 2) 类型,选择M为适合于所需取值范围的最大宽度。这给出具备两位小数精度的浮点值。DECIMAL的优势是将值表示为一个串,并且不容易出现舍入错误。不利之处是串运算比内部存储为数的值上的运算效率差。
■ 可在内部用整数类型来表示全部的钱值。其优势是内部用整数来计算,这样会很是快。不利之处是在输入或输出时须要利用乘或除100对值进行转换。有些数据显然是数值的,但必须决定是使用浮点类型仍是使用整数类型。应该搞清楚所用的单位是什么以及须要什么样的精度。整个单元的精度都够吗?或者须要表示小数的单元吗?这将有助于您在整数列和浮点数列之间进行区分。例如,若是您正表示权重,那么若是记录的值为英磅,可使用一个整形列。若是但愿记录小数部分,就应该使用浮点列。在有的状况下,甚至会使用多个字段,例如:若是但愿根据磅和盎司记录权重,则可使用多个列。
高度(height)是另一种数值类型,有以下几种表示方法:
■ 诸如“6 英尺2 英寸”可表示为“ 6-2”这样一个串。这种形式具备容易察看和理解的优势(固然比“74
英寸更好理解”),可是这种值很难用于数学运算,如求和或取平均值。
■ 一个数值字段表示英尺,另外一个数值字段表示英寸。这样的表示进行数值运算相对容易,但两个字段比一个字段难于使用。
■ 只用一个表示英寸的数值段。这是数据库最容易处理的方式,可是这种方式意义最不明确。不过要记住,不必定要用与您惯常使用的那种格式来表示值。能够用MySQL的函数将值转换为看上去意义明显的值。所以,最后这种表示方法多是表示高度的最好方法。
若是须要存储日期信息,须要包括时间吗?即,它们永远都须要包括时间吗? MySQL不提供具备可选时间部分的日期类型: DATE 可不包含时间,而DATETIME 必须包含时间。若是时间确实是可选的,那么可用一个DATE 列记录日期,一个TIME 列记录时间。容许TIME 列为NULL 并解释为“无时间”:
在这种状况下必须回答的问题是,是否能够只用日期,或者是否须要既使用日期又使用时间。这个问题依赖于一个题目是否能够在同一天投入测试过程不止一次。若是是这样,那么应该记录时间(比方说,记录测试过程开始的时间),或者用DATETIME 列,或者分别用DATE 和TIME 列(二者都必须填写)。若是一个题目一天测试了两次,没有时间值就不能将该题目的细目记录与适当的主记录进行关联。
我曾经听过有人声称“我不须要时间;我从不在同一天把一道题测试两次”。有时他们是对的,可是我也看到过这些人后来在录入同一天测试屡次的题目的数据后,反过来考虑怎样防止细目记录与错误的主记录相混。很抱歉,这时已经太迟了!有时能够在表中增长TIME 列来处理这个问题,不幸的是,除非有某些独立的数据源,如原书面记录,不然很难整理现有记录。此外,没办法消除细目记录的歧义,以便将它们关联到合适的主记录上。即便有独立的信息源,这样作也是很是乱的,极可能使已经编写来利用表的应用程序出问题。最好是向表的拥有者说明问题并保证在建立他们的表以前进行很好的描述。
有时具备一些不完整的数据,这会干扰列类型的选择。若是进行家谱研究,须要记录出生日期和死亡日期,有时会发现所能搜集到的数据中只是某人出生或死亡的年份,但没有确切的日期。若是使用DATE 列,除非有完整的日期值,不然不能输入日期。若是但愿可以记录所具备的任何信息,即便不完整也保存,那么可能必须保存独立的年、月、日字段。这样就能够输入所具备的日期成员并将没有的部分设为NULL。在MySQL3.23 及之后的版本中,还容许DATE 的日为0 或者月和日部分为0。这样“模糊”的日期可用来表示不完整的日期值。
2.3.2 列值有特定的取值范围吗
若是已经决定从通用类别上选择一种列类型,那么考虑想要表示的值的取值范围会有助于将您的选择缩减到该类别中特定的类型上。假如但愿存储整数值。这些整数值的取值范围为0 到1000,那么可使用从SMALLINT 到BIGINT 的全部类型。若是这些整数值的取值范围最多为2 000 000,则不能使用SMALLINT,其选择范围从MEDIUMINT 到BIGINT。须要从这个可能的选择范围中选取一种类型。固然,能够简单地为想要存储的值选择最大的类型(如上述例子中选择BIGINT)。可是,通常应该为所要存储的值选择足以存储它的最小的类型。这样作,能够最小化表占用的存储量,获得最好的性能,由于一般较小列的处理比较大列的快。若是不知道所要表示的值的取值范围,那么必须进行猜想或使用BIGINT以应付最坏的状况。(请注意,若是进行猜想时使用了一个过小的类型,工做不会白作;之后能够利用ALTER TABLE 来将此列改成更大一些的类型。)
在第1章中,咱们为学分保存方案建立了一个score 表,它有一个记录测验和测试学分的score 列。为了讨论简单起见,建立该表时使用了INT类型,但如今能够看出,若是学分在0到100 的取值范围内,更好的选择应该是TINYINT UNSIGNED,由于所用的存储空间较小。数据的取值范围还影响列类型的属性。若是该数据从不为负,可以使用UNSIGNED 属性;不然就不能用它。
串类型没有数值列那样的“取值范围”,但它们有长度,须要知道该串可以使用的列最大长度。若是串短于2 56个字符,可以使用CHAR、VARCHAR、TINYTEXT 或TINYBLOB 等类型。若是想要更长的串,可以使用TEXT 或BLOB 类型,而CHAR 和VARCHAR 再也不是选项。对于用来表示某个固定值集合的串列,能够考虑使用ENUM 或SET 列类型。它们多是很好的选项,由于它们在内部是用数来表示的。这两个类型上的运算是数值化的,所以,比其余的串类型效率更高。它们还比其余串类型紧凑、节省空间。在描述必须处理的值的范围时,最好的术语是“老是”和“决不”(如“老是小于1000”或“决不为负”),由于它们能更准确地约束列类型的选择。但在未确证以前,要慎用这两个术语。特别是与其余人谈他们的数据,而他们开始乱用这两个术语时要注意。在有人说“老是”或“决不”时,必定要搞清他们说的确实是这个含义。有时人们说本身的数据老是有某种特定的性质,而其真正的含义是“几乎老是”。
例如,假如您为某些人设计一个表,而他们告诉您,“咱们的测试学分老是0 到100”。根据这个描述,您选择了TINYINT 类型并使它为UNSIGNED的,由于值老是非负的。然而,您发现编码录入数据库的人有时用- 1来表示“学生因病缺席”。呀,他们没告诉您这事。可能能够用NULL 来表示-1,但若是不能,必须记录-
1,这样就不能用UNSIGNED 列了(只好用ALTER TABLE 来补救!)。有时关于这些情形的讨论可经过提一些简单的问题来简化,如问:曾经有过例外吗?若是曾经有过例外状况,即便是只有一次,也必须考虑。您会发现,和您讨论数据库设计的人老是认为,若是例外不常常发生,那么就没什么关系。然而在建立数据库时,就不能这样想了。须要提的问题并非例外出现有多频繁,而是有没有例外?若是有,必须考虑进去。
2.3.3 性能与效率问题
列类型的选择会在几个方面影响查询性能。若是记住下几节讨论的通常准则,将可以选出有助于MySQL有效处理表的列类型。
1. 数值与串的运算
数值运算通常比串运算更快。例如比较运算,可在单一运算中对数进行比较。而串运算涉及几个逐字节的比较,若是串更长的话,这种比较还要多。若是串列的值数目有限,应该利用ENUM或SET类型来得到数值运算的优越性。这两种类型在内部是用数表示的,可更为有效地进行处理。例如替换串的表示。有时可用数来表示串值以改进其性能。例如,为了用点分四位数(dotted-quad)表示法来表示IP 号,如192.168.0.4,可使用串。可是也能够经过用四字节的UNSIGNED类型的每一个字节存储四位数的每一个部分,将IP 号转换为整数形式。这便可以节省空间又可加快查找速度。但另外一方面,将IP 号表示为INT值会使诸如查找某个子网的号码这样的模式匹配难于完成。所以,不能只考虑空间问题;必须根据利用这些值作什么来决定哪一种表示更适合。
2. 更小的类型与更大的类型
更小的类型比更大的类型处理要快得多。首先,它们占用的空间较小,且涉及的磁盘活动开销也少。对于串,其处理时间与串长度直接相关。通常状况下,较小的表处理更快,由于查询处理须要的磁盘I/O少。对于定长类型的列,应该选择最小的类型,只要能存储所需范围的值便可。例如,若是MEDIUMINT 够用,就不要选择BIGINT。若是只须要FLOAT精度,就不该该选择DOUBLE。对于可变长类型,也仍然可以节省空间。一个BLOB 类型的值用2字节记录值的长度,而一个LONGBLOB 则用4 字节记录其值的长度。若是存储的值长度永远不会超过64KB,使用BLOB 将使每一个值节省2字节(固然,对于TEXT 类型也能够作相似的考虑)。
3. 定长与可变长类型
定长类型通常比可变长类型处理得更快:
■ 对于可变长列,因为记录大小不一样,在其上进行许多删除和更改将会使表中的碎片更多。须要按期运行OPTIMIZE TABLE以保持性能。而定长列就没有这个问题。
■ 在出现表崩溃时,定长列的表易于从新构造,由于每一个记录的开始位置是肯定的。可变长列就没有这种便利。这不是一个与查询处理有关的性能问题,但它一定能加快表的修复过程。若是表中有可变长的列,将它们转换为定长列可以改进性能,由于定长记录易于处理。在试图这样作以前,应该考虑下列问题:
■ 使用定长列涉及某种折衷。它们更快,但占用的空间更多。CHAR(n) 类型列的每一个值总要占用n个字节(即便空串也是如此),由于在表中存储时,值的长度不够将在右边补空格。而VARCHAR(N)类型的列所占空间较少,由于只给它们分配存储每一个值所须要的空间,每一个值再加一个字节用于记录其长度。所以,若是在CHAR和VARCHAR列之间进行选择,须要对时间与空间做出折衷。若是速度是主要关心的因素,则利用CHAR
列来取得定长列的性能优点。若是空间是关键,应该使用VARCHAR 列。
■ 有时不能使用定长类型,即便想这样作也不行。例如对于比255 字符长的串,没有定长类型。
4. 可索引类型
索引能加快查询速度,所以,应该选择可索引的类型。
5. NULL 与NOT NULL 类型
若是定义一列为NOT NULL,其处理更快,由于MySQL在查询处理中没必要检查该列的值弄清它是否为NULL,表中每行还能节省一位。避免列中有NULL可使查询更简单,由于不须要将NULL做为一种特殊情形来考虑。一般,查询越简单,处理就越快。所给出的性能准则有时是互相矛盾的。例如,根据MySQL能对行定位这一方面来讲,包含CHAR列的定长行比包含VARCHAR 列的可变长行处理快。但另外一方面,它也将占用更多的空间,所以,会致使更多的磁盘活动。从这个观点来看, VARCHAR可能会更快。做为一个经验规则,可假定定长列能改善性能,即便它占用更多的空间也如此。对于某个特殊的关键应用,可能会但愿以定长和可变长两种方式实现一个表,并进行某些测试以决定哪一种方式对您的特定应用来讲更快。
2.3.4 但愿对值进行什么样的比较
根据定义串的方式,可使串类型以区分大小写或不区分大小写的方式进行比较和排序。表2-14示出不区分大小写的每一个类型及其等价的区分大小写类型。根据列定义中给不给出关键字BINARY,有的类型(CHAR、VARCHAR)是二进制编码或非二进制编码的。其余类型(BLOB、TEXT)的“二进制化”隐含在类型名中。
请注意,二进制(区分大小写)类型仅在比较和排序行为上不一样于相应的非二进制(不区分大小写)类型。任意串类型均可以包含任意种类的数据。特别是, TEXT类型尽管在列类型名中称为“TEXT(文本)”,但它能够很好地存储二进制数据。若是但愿使用一个在比较时既区分大小写,又可不区分大小写的列。可在但愿进行区分大小写的比较时,利用BINARY关键字强制串做为二进制串值。例如,若是my_col 为一个CHAR 列,可按不一样的方式对其进行比较:
my_col = “A B C” 不区分大小写
BINARY my_col =“A B C” 区分大小写
my_col = BINARY“A B C” 区分大小写
若是有一个但愿以非字典顺序存储的串值,可考虑使用ENUM 列。ENUM值的排序是根据列定义中所列出枚举值的顺序进行的,所以可使这些值以任意想要的次序排序。
2.3.5 计划对列进行索引吗使用索引可更有效地处理查询。索引的选择是第4 章中的一个主题,但通常原则是将WHERE子句中用来选择行的列用于索引。若是您要对某列进行索引或将该列包含在多列索引中,则在类型的选择上可能会有限定。在早于3.23.2 版的MySQL发行版中,索引列必须定义为NOT NULL,而且不能对BLOB或TEXT类型进行索引。这些限制在MySQL3.23.2版中都撤消了,但若是您正使用一个更早的版本,不能或不肯升级,那么必须听从这些约束。不过在下列情形中能够绕过它们:
■ 若是能够指定某个值做为专用的值,那么可以将其做为与NULL 相同的东西对待。对于DATE 列,能够指定“0000-00-00”表示“无日期”。在串列中,能够指定空串表明“缺值”。在数值列中,若是该列通常只存储非负值,则可以使用- 1。
■ 不能对BLOB或TEXT类型进行索引,但若是串不超过255它符,可以使用等价的VARCHAR 列类型并对其进行索引。可VARCHAR(255) BINARY 用于BLOB 值,将VARCHAR(255) 用于TEXT 值。
2.3.6 列类型选择问题的相互关联程度
不要觉得列类型的选择是相互独立的。例如,数值的取值范围与存储大小有关;在增大取值的范围时,须要更多的存储空间,这会影响性能。另外,考虑选择使用AUTO_INCREMENT 来建立一个存放惟一序列号的列有何含义。这个选择有几个结果,它们涉及列的类型、索引和NULL 的使用,现列出以下:
■ AUTO_INCREMENT 是一个应该只用于整数类型的列属性。它将您的选择限定在TINYINT 到BIGINT 之上。
■ AUTO_INCREMENT 列应该进行索引,从而当前最大的序列号能够很快就肯定,不用对表进行所有扫描。此外,为了防止序列号被重用,索引号必须是惟一的。这表示必须将列定义为PRIMARY KEY 或定义为UNIQUE 索引。
■ 若是所用的MySQL版本早于3 . 2 3 . 2,则索引列不能包含NULL 值,所以,必须定义列为NOT NULL。全部这一切表示,不能像以下这样只定义一个AUTO_INCREMENT 列:
使用AUTO_INCREMENT 获得的另外一个结果是,因为它是用来生成一个正值序列的,所以,最好将AUTO_INCREMENT 列定义为UNSIGNED: