前期回顾:
MySQL性能优化(一):MySQL架构与核心问题mysql
良好的设计是高性能的基石,应该根据系统的实际业务需求、使用场景进行设计、优化、再调整,在这其中每每须要权衡各类因素,例如,数据库表究竟如何划分、字段如何选择合适的数据类型等等问题。web
MySQL支持的数据类型很是之多,对于选择恐惧症的小伙伴而言,苦不可言。大部分人在建立数据库表时,基本一股脑的使用INT
、VARCHAR
这两种类型最多,至于长度,则会选择足够大便可,避免往后不够用咋办。只顾当时一时爽,以后坑谁谁难受。算法
若是你是一个追求极致、高效的开发者,对于上面的状况确定是不肯让其发生的。在众多的数据类型面前,如何选择正确的数据类型,对于高性能是相当重要的。本文将介绍如何选择优化的数据类型,来提升MySQL的性能,将会选取最为经常使用的类型进行说明,便于在实际开发中建立表、优化表字段类型时提供帮助。sql
无论存储哪一种类型的数据,下面几个简单的原则将有助于你作出更好的选择。数据库
通常状况下,应该尽量选择正确存储数据的最小数据类型。更小的数据类型一般更快,由于它们占用更少的磁盘空间、内存,而且处理时须要的CPU周期更少。后端
可是,在选择更小数据类型时,必定不要低估存储值的范围,由于后期修改数据类型及长度是一件很是痛苦、耗时的操做。若是没法肯定哪一个数据类型是最好的,就选择你认为不会超过范围的最小类型。缓存
简单的数据类型操做一般须要更少的CPU周期。例如,整型比字符操做代价更低,由于字符集和校队规则(如:排序规则)使得字符比较比整型比较更复杂。安全
NULL
是在常见不过的值了,一般都习惯对某些字段设置默认值为NULL
,这实际上是一种很是很差的习惯。若是查询中的字段值恰巧是设置的NULL
值,对MySQl来讲更难优化,由于可为NULL
的字段使得索引、值比较都更复杂。性能优化
NULL值不能进行索引,影响索引的统计信息,影响优化器的判断。复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。服务器
字符串类型是数据库中使用频率最高的数据类型,VARCHAR
和CHAR
是两种最主要的字符串类型,均可以用来存储字符串,但它们保存和检索的方式不一样。VARCHAR
属于可变长度的字符类型,而CHAR
属于固定长度的字符类型。下面是关于这两种类型的说明、比较。
VARCHAR
类型用于存储可变长字符串,它比定长类型更节省空间,由于它仅使用必要的空间(例如,越短的字符串使用最少的空间)。
VARCHAR
须要使用1或2个额外的字节来记录字符串的长度(若是字段的最大长度小于或等于255字节,则只使用1个字节表示长度,不然使用2个字节来表示长度)。例如,一个VARCHAR(10)
的字段须要11个字节的存储空间,VARCHAR(1000)
则须要1002个字节的存储空间,其中须要2个字节来存储长度。
CHAR
类型是定长的。当数据类型为CHAR
时,MySQL会删除全部的末尾空格。
CHAR
类型适合存储很短的字符串,或者全部值都接近同一个长度。例如,CHAR
类型很是适合存储密码的MD5
值,由于这是一个定长的值。对于常常变动的数据,CHAR
类型也比VARCHAR
类型更好,由于定长的CHAR
类型不容易产生碎片。对于存储很是短的列,CHAR
类型比VARCHAR
在存储空间上更有效率。例如,用CHAR(1)
来存储只有Y和N的值,若是采用VARCHAR(1)
却须要2个字节,由于还会有一个记录长度的额外字节。
经过下面具体例子来对CHAR
进行说明,有助于更好的理解。这里建立一张只有一个CHAR(10)
字段的表char_test
,并往里面插入三个字符串xcbeyond
,注意先后有空格的区别:
mysql> create table char_test(ch char(10)); Query OK, 0 rows affected mysql> insert into char_test(ch) values('xcbeyond'),(' xcbeyond'),('xcbeyond '); Query OK, 3 rows affected Records: 3 Duplicates: 0 Warnings: 0
奇怪的事情发生了,当咱们查询时,会发现第三个字符串末尾的空格被自动截断了。为了更好的显示出是否有空格,对ch字段先后拼接'
字符便于查看对比。
mysql> select concat("'",ch,"'") from char_test; +--------------------+ | concat("'",ch,"'") | +--------------------+ | 'xcbeyond' | | ' xcbeyond' | | 'xcbeyond' | +--------------------+ 3 rows in set
若是用VARCHAR(10)
字段存储相同的值,则字符串末尾的空格是不会被截断的。
MySQL提供了两种类似的日期类型:DATETIME
和TIMESTAMP
,使用起来傻傻分不清,看完本节后不要再说不知道如何选择了。
对于应用程序而言,他们都能很好的表示日期,可是再某些场景下,各有不一样。接下来让咱们一块儿看看吧。
DATETIME
类型可以保持很大范围的日期,从1001年到9999年,精度为秒。它把日期和时间封装到格式为YYYYMMDDHHMMSS
的整数中,与时区无关,使用8个字节的存储空间。
默认状况下,MySQL是以一种可排序、无歧义的格式显示DATETIME
值,例如2020-03-05 22:38:40
。
TIMESTAMP
,从它的名字不难看出,它和UNIX时间戳相同,保存了从1970年1月1日0时0分0秒以来的秒数。TIMESTAMP
只使用4个字节的存储空间,所以它的范围比DATETIME
小得多,只能表示从1970年到2038年。
TIMESTAMP
显示的值依赖于时区,MySQL服务器、操做系统,以及客户端链接都有时区设置。所以,存储值为0的TIMESTAMP
在美国东部时区显示为1969-12-31 19:00:00
,与格林尼治时差5个小时。
一般应该尽可能使用TIMESTAMP
,由于它比DATETIME
更节省存储空间,并且对于跨时区的业务,TIMESTAMP
更为合适。
若是须要存储比秒更小粒度的日期和时间值该怎么办?MySQL目前没有提供合适的数据类型,但能够采用其余变通的方式,如可使用本身的存储格式:可使用BIGINT
类型存储微妙级别的时间戳,或者使用DOUBLE
存储秒以后的小数部分。或者也可使用MariaDB
数据库替代MySQL
。
通常在保存少了字符串的时候,咱们会选择CHAR
或VARCHAR
类型,而在保存较大文本等数据时,一般会选择使用TEXT
和BLOB
。
TEXT
和BLOB
类型都是存储很大的数据而设计的字符串数据类型,分别采用字符串和二进制方式存储。例如,TEXT一般用来保存文章内容、日志等字符串内容,而BLOB一般用来保存图片、视频等二进制数据内容。有以下特色:
TEXT类型有字符集和排序规则。
BLOB类型存储的是二进制数据,没有排序规则或字符集。
MySQL中不能将TEXT和BLOB类型的列进行索引,也不能使用这些索引消除排序。
与其余数据类型不一样,MySQL把每一个TEXT
和BLOB
类型的值看成一个独立的对象处理。存储引擎在存储时一般会作特殊处理,当它们的值太大时,InnoDB
会使用专门的“外部”存储区域来进行存储,此时每一个值在行内须要1~4个字节来存储一个指针,而后在外部存储区域存储实际的值。
在面对TEXT、BLOB之间的选择时,应该根据实际状况选择可以知足需求的最小存储类型,接下来主要针对TEXT、BLOB类型存在的一些常见问题进行介绍。
1.在执行了大量的删除操做时,TEXT
和BLOB
会引发一些性能问题
删除操做会在数据库表中留下很大的“空洞”,之后填入这些“空洞”的记录在插入的性能上会有影响。为了提升性能,建议按期使用OPTIMZE TABLE
功能对这类表进行碎片整理,避免由于“空洞”致使性能问题。
实战演示验证说明以下:
1)建立测试表text_test
,字段id
和context
的类型分别为int(11)
和text
:
mysql> create table text_test(id int(11),context text); Query OK, 0 rows affected
2)往表text_test
中插入大量的数据,这里使用repeat
函数插入字符串:
repeat函数用于字符串的复制
mysql> insert into text_test(id,context) values(1,repeat('xcbeyond',1000)); Query OK, 1 row affected mysql> insert into text_test(id,context) values(2,repeat('xcbeyond',1000)); Query OK, 1 row affected mysql> insert into text_test(id,context) values(3,repeat('xcbeyond',1000)); Query OK, 1 row affected mysql> insert into text_test(id,context) values(4,repeat('xcbeyond',1000)); Query OK, 1 row affected mysql> insert into text_test(id,context) values(5,repeat('xcbeyond',1000)); Query OK, 1 row affected mysql> insert into text_test(id,context) values(6,repeat('xcbeyond',1000)); Query OK, 1 row affected ……
3)此时看看表text_test
的物理文件大小:
2020/03/07 周六 15:58 540,672 text_test.ibd
这里显示数据文件大小为540Kb
。
4)从表text_test
中删除一大部分数据,这些数据占总数据量的2/3:
mysql> delete from text_test where id < 10; Query OK, 9 rows affected
5)在此查看text_test
的物理文件大小:
2020/03/07 周六 16:05 573,440 text_test.ibd
奇怪的是,数据文件大小并无由于删除数据而减小,反而还增长了一点。
6)接下来对表text_test
进行OPTIMIZE优化操做:
mysql> optimize table text_test; +----------------+----------+----------+-------------------------------------------------------------------+ | Table | Op | Msg_type | Msg_text | +----------------+----------+----------+-------------------------------------------------------------------+ | test.text_test | optimize | note | Table does not support optimize, doing recreate + analyze instead | | test.text_test | optimize | status | OK | +----------------+----------+----------+-------------------------------------------------------------------+ 2 rows in set
7)再次查看表text_test
的物理文件大小:
2020/03/07 周六 16:08 458,752 text_test.ibd
能够发现,表的数据文件大小减小了,则说明“空洞”空间已经被回收了。
2.使用合成索引来提升大文本字段(TEXT
、BLOB
类型)的查询性能
合成索引,就是根据大文本字段的内容创建一个散列值,并把这个值存储在单独的数据列中,接下来就能够经过检索散列值找到数据行了。
可是,要注意这种技术只能用于精确匹配的查询(散列值对于相似<
或>=
等范围搜索操做符是没有用处的)。可使用MD5()
函数生成散列值,也可使用SHA1()
或 CRC32()
,或者使用本身的应用程序逻辑来计算散列值。请记住数值型散列值能够很高效率地存储。一样,若是散列算法生成的字符串带有尾部空格,就不要把它们存储在CHAR
或 VARCHAR
列中,它们会受到尾部空格去除的影响。合成的散列索引对于那些 BLOB
或 TEXT
数据列特别有用。用散列标识符值查找的速度比搜索BLOB
列自己的速度快不少。
实战演示验证说明以下:
1)建立测试表text_test2
,字段id
、context
、hashValue
字段类型分别为int(11)
、text
、varchar(40)
:
mysql> create table text_test2(id int(11),context text,hashValue varchar(40)); Query OK, 0 rows affected
2)往表text_test2
中插入数据,其中hashValue
用来存入context
列内容的MD5
值:
mysql> insert into text_test2 values(1,repeat('xcbeyond',10),md5(context)); Query OK, 1 row affected mysql> insert into text_test2 values(2,repeat('xcbeyond',10),md5(context)); Query OK, 1 row affected mysql> select * from text_test2; +----+----------------------------------------------------------------------------------+----------------------------------+ | id | context | hashValue | +----+----------------------------------------------------------------------------------+----------------------------------+ | 1 | xcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyond | 537f6020f5b2b59456a61271a2b3f285 | | 2 | xcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyond | 537f6020f5b2b59456a61271a2b3f285 | +----+----------------------------------------------------------------------------------+----------------------------------+ 2 rows in set
3)若是须要查询context
列的值,则经过散列值hashValue
来查询:
mysql> select * from text_test2 where hashValue = md5(repeat('xcbeyond',10)); +----+----------------------------------------------------------------------------------+----------------------------------+ | id | context | hashValue | +----+----------------------------------------------------------------------------------+----------------------------------+ | 1 | xcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyond | 537f6020f5b2b59456a61271a2b3f285 | | 2 | xcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyondxcbeyond | 537f6020f5b2b59456a61271a2b3f285 | +----+----------------------------------------------------------------------------------+----------------------------------+ 2 rows in set
上面的例子则是展现了合成索引的用法,因为这种技术只能用于精确匹配,在必定程度上减小 I/O,从而提升查询效率。
3.在没必要要的状况下避免检索TEXT
、BLOB
类型的值
例如,SELECT *
查询就不是很好的操做,除非可以肯定做为约束条件的 WHERE 子句只会找到所须要的数据行。不然,极可能毫无目的地在网络上传输大量的值。这也是 BLOB 或 TEXT标识符信息存储在合成的索引列中对用户有所帮助的例子。用户能够搜索索引列,决定须要的哪些数据行,而后从符合条件的数据行中检索 BLOB 或 TEXT 值。
4.把 BLOB 或 TEXT 列分离到单独的表中
在某些环境中,若是把这些数据列移动到第二张数据表中,能够把原数据表中的数据列转换为固定长度的数据行格式,那么它就是有意义的。这会减小主表中的碎片,能够获得固定长度数据行的性能优点。它还可使主数据表在运行 SELECT * 查询的时候不会经过网络传输大量的 BLOB 或 TEXT 值。
惟一标识符,也就是咱们经常所说的主键,用于充当表记录的惟一判断依据。惟一标识符,选择合适的数据类型是很是重要的。
一般惟一标识符更多的是用来与其它值或者其它表的值进行比较(如,关联查询中),标识列也可能在其它表中做为外键使用,因此为标识列选择数据类型时,应该选择根关联表中对应列同样的类型。
当选择惟一标识符的类型时,不只仅须要考虑存储类型,还须要考虑MySQL对这种类型怎么执行计算和比较的,由于比较在SQL查询中使用最多,并且也是制约性能的最大因素。
一旦选定了一种类型,就必定要确保全部关联表中都使用相同的类型。由于类型直接每每都是须要精确匹配,混用不一样数据类型可能致使性能问题,即便没有性能影响,在比较操做时隐式类型转换也可能致使很难发现的错误问题。
在能够知足值的范围的需求,而且预留将来增加空间的前提下,应该选择最小的数据类型。
下面是一些小技巧:
1.整数类型
整数一般是标识列最好的选择,由于它很快,而且可使用AUTO_INCREMENT
。
2.字符串类型
若是能够避免,尽量的避免使用字符串类型做为标识列的类型,由于它很消耗空间,而且一般比数字类型慢。尤为是在MyISAM
存储引擎的表里使用字符串做为标识列时,要特别的当心,MyISAM
默认对字符串使用压缩索引,这会致使查询慢不少。
对于彻底“随机”的字符串也需多加注意,例如MD5()
、SHA1()
或者UUID()
产生的字符串。这些函数生成的新值会任意分布在很大的空间内,会致使insert以及一些select操做变得很慢:
insert
语句更慢。这会致使页分裂、磁盘随机访问。select
语句会变得更慢,是由于逻辑上不相邻的数据会分布在磁盘和内存的不一样地方。在实际开发中,有不少工具会自动生成建表脚本等等,自动生成前期给开发带来了很大的便利,但与此同时却致使严重的性能问题。有些工具生成的东西,在存储任何数据都会使用很大的VARCHAR
类型,这每每是不正确的。若是是自动生成的,必定要反复检查确认是否合理。
例如,一些ORM
框架(如,MyBatis
、Hibernate
),会存储任意类型的数据列到任意类型的后端数据,这一般意味着没有设计使用更优的数据类型来存储,后期安全隐患很大,出现问题也很难排查。总之,必定要反复检查确认是否合理。这也是我我的不太喜欢用这类相似的工具,来生成代码的缘由,检查真的很浪费个人时间。
在这里已经介绍了大部分经常使用的数据类型,各自都有哪些特色,哪些地方会严重影响性能等等。在选择数据类型时,把握好“选择原则”,你就成功了一半,其他细节在平常开发接触中慢慢琢磨、留意,选择类型时不要随意、盲目选择就好。
简单概括以下:
对于字符串类型,最好的策略是只分配真正须要的空间。
日期类型,要根据实际须要选择可以知足应用的最小存储的日期类型。
对含有 TEXT
和BLOB
字段的表,若是常常作删除和修改记录的操做要定时执行OPTIMIZE TABLE
功能对表进行碎片整理。