一、表结构的优化
1、标准化
标准化是在数据库中组织数据的过程。其中包括,根据设计规则创建表并在这些表间建立关系;通过取消冗余度与不一致相关性,该设计规则可以同时保护数据并提高数据的灵活性。通常数据库标准化是让数据库设计符合某一级别的范式,通常满足第三范式即可。也有第四范式(也称为 Boyce Codd范式,BCNF))与第五范式存在,但是在实际设计中很少考虑。忽视这些规则可能使得数据库的设计不太完美,但这不应影响功能。
标准化的特点:
1) 所有的“对象”都在它自己的table中,没有冗余。
2) 数据库通常由E-R图生成。
3) 简洁,更新属性通常只需要更新很少的记录。
4) Join操作比较耗时。
5) Select,sort优化措施比较少。
6) 适用于OLTP应用。
非标准化的特点:
1) 在一张表中存储很多数据,数据冗余。
2) 更新数据开销很大,更新一个属性可能会更新很多表,很多记录。
3) 在删除数据是有可能丢失数据。
4) Select,order有很多优化的选择。
5) 适用于DSS应用。
标准化和非标准化都有各自的优缺点,通常在一个数据库设计中可以混合使用,一部分表格标准化,一部分表格保留一些冗余数据:
1) 对OLTP使用标准化,对DSS使用非标准化
2) 使用物化视图。MySQL不直接支持该数据库特性,但是可以用MyISAM表代替。
3) 冗余一些数据在表格中,例如将ref_id和name存在同一张表中。但是要注意更新问题。
4) 对于一些简单的对象,直接使用value作为建。例如IP address等
2、适度的反范式,注意是适度的
我们都知道三范式,基于三范式建立的模型是最有效保存数据的方式,也是最容易扩展的模式。我们在开发应用程序时,设计的数据库要最大程度的遵守三范式,特别是对于OLTP型的系统,三范式是必须遵守的规则。当然,三范式最大的问题在于查询时通常需要join很多表,导致查询效率很低。所以有时候基于性能考虑,我们需要有意的违反三范式,适度的做冗余,以达到提 高查询效率的目的。注意这里的反范式是适度的,必须为这种做法提供充分的理由。
3、适当建立索引
说起提高数据库性能,索引是最物美价廉的东西了。不用加内存,不用改程序,不用调sql,只要执行个正确的’create index’,查询速度就可能提高百倍千倍,这可真有诱惑力。可是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的I/O。由于索引的存储结构不同于表的存储,一个表的索引所占空间比数据所占空间还大的情况经常发生。这意味着我们在写数据库的时候做了很多额外的工作,而这个工作只是为了提高读的效率。因此,我们建立一个索引,必须保证这个索引不会“亏本”。
所有MySQL列类型可以被索引。对相关列使用索引是提高SELECT操作性能的最佳途径。使用索引应该注意以下几点:
1) MySQL只会使用前缀,例如key(a, b) …where b=5 将使用不到索引。
2) 要选择性的使用索引。在变化很少的列上使用索引并不是很好,例如性别列。
3) 在Unique列上定义Unique index。
4) 避免建立使用不到的索引。
5) 在Btree index中(InnoDB使用Btree),可以在需要排序的列上建立索引。
6) 避免重复的索引。
7) 避免在已有索引的前缀上建立索引。例如:如果存在index(a,b)则去掉index(a)。
8) 控制单个索引的长度。使用key(name(8))在数据的前面几个字符建立索引。
9) 越是短的键值越好,最好使用integer。
10) 在查询中要使用到索引(使用explain查看),可以减少读磁盘的次数,加速读取数据。
11) 相近的键值比随机好。Auto_increment就比uuid好。
12) Optimize table可以压缩和排序index,注意不要频繁运行。
13) Analyze table可以更新数据。
4、选择适当的数据类型
最基本的优化之一就是使表在磁盘上占据的空间尽可能小。这能带来性能非常大的提升,因为数据小,磁盘读入较快,并且在查询过程中表内容被处理所占用的内存更少。同时,在更小的列上建索引,索引也会占用更少的资源。可以使用下面的技术可以使表的性能更好并且使存储空间最小:
1) 使用正确合适的类型,不要将数字存储为字符串。
2) 尽可能地使用最有效(最小)的数据类型。MySQL有很多节省磁盘空间和内存的专业化类型。
3) 尽可能使用较小的整数类型使表更小。例如,MEDIUMINT经常比INT好一些,因为MEDIUMINT列使用的空间要少25%。
4) 如果可能,声明列为NOT NULL。它使任何事情更快而且每列可以节省一位。注意如果在应用程序中确实需要NULL,应该毫无疑问使用它,只是避免 默认地在所有列上有它。
5) 对于MyISAM表,如果没有任何变长列(VARCHAR、TEXT或BLOB列),使用固定尺寸的记录格式。这比较快但是不幸地可能会浪费一些空间。即使你已经用CREATE选项让 VARCHAR列ROW_FORMAT=fixed,也可以提示想使用固定长度的行。
6) 使用sample character
set,例如latin1。尽量少使用utf-8,因为utf-8占用的空间是latin1的3倍。可以在不需要使用utf-8的字段上面使用latin1,例如mail,url等。
- 数字类型:非万不得已不要使用DOUBLE,不仅仅只是存储长度的问题,同时还会存在精确性的问题。同样,固定精度的小数,也不建议使用DECIMAL,建议乘以固定倍数转换成整数存储,可以大大节省存储空间,且不会带来任何附加维护成本。对于整数的存储,在数据量较大的情况下,建议区分开 TINYINT / INT / BIGINT 的选择,因为三者所占用的存储空间也有很大的差别,能确定不会使用负数的字段,建议添加unsigned定义。当然,如果数据量较小的数据库,也可以不用严格区分三个整数类型。
- 字符类型:非万不得已不要使用 TEXT 数据类型,其处理方式决定了他的性能要低于char或者是varchar类型的处理。定长字段,建议使用 CHAR 类型,不定长字段尽量使用 VARCHAR,且仅仅设定适当的最大长度,而不是非常随意的给一个很大的最大长度限定,因为不同的长度范围,MySQL也会有不一样的存储处理。
- 时间类型:尽量使用TIMESTAMP类型,因为其存储空间只需要 DATETIME 类型的一半。对于只需要精确到某一天的数据类型,建议使用DATE类型,因为他的存储空间只需要3个字节,比TIMESTAMP还少。不建议通过INT类型类存储一个unix timestamp 的值,因为这太不直观,会给维护带来不必要的麻烦,同时还不会带来任何好处。
- ENUM & SET:对于状态字段,可以尝试使用 ENUM 来存放,因为可以极大的降低存储空间,而且即使需要增加新的类型,只要增加于末尾,修改结构也不需要重建表数据。如果是存放可预先定义的属性数据呢?可以尝试使用SET类型,即使存在多种属性,同样可以游刃有余,同时还可以节省不小的存储空间。
- LOB类型:强烈反对在数据库中存放 LOB 类型数据,虽然数据库提供了这样的功能,但这不是他所擅长的,我们更应该让合适的工具做他擅长的事情,才能将其发挥到极致。在数据库中存储 LOB 数据就像让一个多年前在学校学过一点Java的营销专业人员来写 Java 代码一样。
5、永远为每张表设置一个ID
我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT标志。
就算是你users表有一个主键叫“email”的字段,你也别让它成为主键。使用VARCHAR类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区……
6、尽可能的使用NOT NULL
除非你有一个很特别的原因去使用NULL值,你应该总是让你的字段保持NOT NULL。不要以为 NULL 不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值。
7、固定长度的表会更快
如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。
固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。使用“垂直分割”技术,你可以分割你的表成为两个一个是定长的,一个则是不定长的。
8、选择合适的引擎
Mysql提供了很多种引擎,我们用的最多的是myisam,innodb,memory这三类。官方手册上说道myisqm比innodb的读速度要 快,大概是3倍。不过书不能尽信啊,《OreIlly.High.Performance.Mysql》这本书里提到了myisam和innodb的比 较,在测试中myisam的表现还不及innodb。至于memory,哈哈,还是比较好用的。在批处理种作临时表是个不错的选择(如果内存够大)。在我的一个批处理中,速度比近乎1:10。
9、文件、图片等大文件用文件系统存储,不用数据库
不用多说,铁律!!!数据库只存储路径。
10、使用ENUM而不是VARCHAR
ENUM类型是非常快和紧凑的。在实际上,其保存的是TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用ENUM而不是VARCHAR。
MySQL也有一个“建议”告诉你怎么去重新组织你的表结构。当你有一个VARCHAR字段时,这个建议会告诉你把其改成ENUM类型。使用PROCEDURE ANALYSE() 你可以得到相关的建议。
11、将IP地址存储为无符号整型
许多程序员在创建一个VARCHAR(15)时并没有意识到他们可以将IP地址以整数形式来存储。当你有一个INT类型时,你只占用4个字节的空间,这是一个固定大小的领域。而且,这会为你带来查询上的优势,尤其是当你需要使用这样的WHERE条件:IP between ip1 and ip2。我们必需要使用UNSIGNED INT,因为IP地址会使用整个32位的无符号整形。而你的查询,你可以使用 INET_ATON()来把一个字符串IP转成一个整形,并使用INET_NTOA()把一个整形转成一个字符串IP。在PHP中,也有这样的函数
ip2long()和long2ip()。
12、Prepared Statements
Prepared Statements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用prepared statements获得很多好处,无论是性能问题还是安全问题。
Prepared Statements可以检查一些你绑定好的变量,这样可以保护你的程序不会受到“SQL注入式”攻击。在性能方面,当一个相同的查询被使用多次的时候,这会为你带来可观的性能优势。你可以给这些Prepared Statements定义一些参数,而MySQL只会解析一次。
虽然最新版本的MySQL在传输Prepared Statements是使用二进制形势,所以这会使得网络传输非常有效率。
当然,也有一些情况下,我们需要避免使用Prepared Statements,因为其不支持查询缓存。但据说版本5.1后支持了。 在PHP中要使用prepared
statements,你可以查看其使用手册:mysqli扩展或是使用数据库抽象层,如:PDO.
二、表的拆分
1、对表进行水平划分
如果一个表的记录数太多了,比如上千万条,而且需要经常检索,那么我们就有必要化整为零了。如果我拆成100个表,那么每个表只有10万条记录。当然这 需要数据在逻辑上可以划分。一个好的划分依据,有利于程序的简单实现,也可以充分利用水平分表的优势。比如系统界面上只提供按月查询的功能,那么把表按月 拆分成12个,每个查询只查询一个表就够了。如果非要按照地域来分,即使把表拆的再小,查询还是要联合所有表来查,还不如不拆了。所以一个好的拆分依据是最重要的。
2、对表进行垂直划分
有些表记录数并不多,可能也就2、3万条,但是字段却很长,表占用空间很大,检索表时需要执行大量I/O,严重降低了性能。这个时候需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。
三、表分区
1、什么是表分区
通俗地讲表分区是将一大表,根据条件分割成若干个小表。mysql5.1开始支持数据表分区了。如:某用户表的记录超过了600万条,那么就可以根据入库日期将表分区,也可以根据所在地将表分区。当然也可根据其他的条件分区。
2、为什么要对表进行分区
为了改善大型表以及具有各种访问模式的表的可伸缩性,可管理性和提高数据库效率。
4、什么时候使用分区
海量数据表
历史表快速的查询,可以采用ARCHIVE+PARTITION的方式。
数据表索引大于服务器有效内存
对于大表,特别是索引远远大于服务器有效内存时,可以不用索引,此时分区效率会更有效。
5、使用表分区并不是性能提高的保证。它依赖于以下因素:
分区使用的列the column used for partitioning;
分区函数,如果原始字段不是int型;
服务器速度;
内存数量.
四、读写分离
如果数据库压力很大,一台机器支撑不了,那么可以用mysql复制实现多台机器同步,将数据库的压力分散。
Master Slave1 Slave2 Slave3
主库master用来写入,slave1—slave3都用来做select,每个数据库分担的压力小了很多。
要实现这种方式,需要程序特别设计,写都操作master,读都操作slave,给程序开发带来了额外负担。当然目前已经有中间件来实现这个代理,对程 序来读写哪些数据库是 透明的。官方有个mysql-proxy,但是还是alpha版本的。新浪有个amobe for mysql,也可达到这个目的,结构如下
五、表优化工具PROCEDURE ANALYSE()
PROCEDURE ANALYSE() 会让MySQL帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,因为要做一些大的决定是需要有数据作为基础的。
例如,如果你创建了一个INT字段作为你的主键,然而并没有太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改成MEDIUMINT。或是你使用了一个VARCHAR字段,因为数据不多,你可能会得到一个让你把它改成ENUM的建议。这些建议,都是可能因为数据不够多,所以决策做得就不够准。
在phpmyadmin里,你可以在查看表时,点击“Propose
table structure”来查看这些建议。一定要注意,这些只是建议,只有当你的表里的数据越来越多时,这些建议才会变得准确。一定要记住,你才是最终做决定的人