4月 272011
 

10.6 选择一种表类型

用MySQL,当前(版本 3.23.5)你能从一个速度观点在4可用表的格式之间选择。

静态MyISAM

这种格式是最简单且最安全的格式,它也是在磁盘格式最快的。速度来自于数据能在磁盘上被找的难易方式。当所定有一个索引和静态格式的东西时,它很简单,只是行长度乘以行数量。而且在扫描一张表时,用每次磁盘读取来读入常数个记录是很容易的。安全性来自于如果当写入一个静态MyISAM文件时,你的计算机崩溃,myisamchk能很容易指出每行在哪儿开始和结束,因此它通常能回收所有记录,除了部分被写入的那个。注意,在MySQL中,所有索引总能被重建。

动态MyISAM

这种格式有点复杂,因为每一行必须有一个头说明它有多长。当一个记录在更改时变长时,它也可以在多于一个位置上结束。你能使用OPTIMIZE table或myisamchk整理一张表。如果你在同一个表中有象某些VARCHAR或BLOB列那样存取/改变的静态数据,将动态列移入另外一个表以避免碎片可能是一个好主意。

压缩MyISAM

这是一个只读类型,用可选的myisampack工具生成。

内存(HEAP 堆)

这种表格式对小型/中型查找表十分有用。对拷贝/创建一个常用的查找表(用联结)到一个(也许临时)HEAP表有可能加快多个表联结。假定我们想要做下列联结,用同样数据可能要几倍时间。

SELECT tab1.a, tab3.a FROM tab1, tab2, tab3
        WHERE tab1.a = tab2.a and tab2.a = tab3.a and tab2.c != 0;

为了加速它,我们可用tab2和tab3的联结创建一张临时表,因为用相同列( tab1.a )查找。这里是创建该表和结果选择的命令。

CREATE TEMPORARY TABLE test TYPE=HEAP
        SELECT
                tab2.a as a2, tab3.a as a3
        FROM
                tab2, tab3
        WHERE
                tab2.a = tab3.a and c = 0;
SELECT tab1.a, test.a3 from tab1, test where tab1.a = test.a1;
SELECT tab1.b, test.a3 from tab1, test where tab1.a = test.a1 and something;

10.6.1 静态(定长)表的特点
这是缺省格式。它用在表不包含VARCHAR、BLOB或TEXT列时候。
所有的CHAR、NUMERIC和DECIMAL列充填到列宽度。
非常快。
容易缓冲。
容易在崩溃后重建,因为记录位于固定的位置。
不必被重新组织(用myisamchk),除非一个巨量的记录被删除并且你想要归还空闲磁盘空间给操作系统。
通常比动态表需要更多的磁盘空间。
10.6.2 动态表的特点
如果表包含任何VARCHAR、BLOB或TEXT列,使用该格式。
所有字符串列是动态的(除了那些长度不到4的列)。
每个记录前置一个位图,对字符串列指出哪个列是空的(''),或对数字列哪个是零(这不同于包含NULL值的列)。如果字符串列在删除尾部空白后有零长度,或数字列有零值,它在位图中标记并且不保存到磁盘上。非空字符串存储为一个长度字节加字符串内容。
通常比定长表占更多的磁盘空间。
每个记录仅使用所需的空间。如果一个记录变得更大,它按需要被切开多段,这导致记录碎片。
如果你与超过行长度的信息更新行,行将被分段。在这种情况中,你可能必须时时运行myisamchk -r以使性能更好。使用myisamchk -ei tbl_name做一些统计。
在崩溃后不容易重建,因为一个记录可以是分很多段并且一个连接(碎片)可以丢失。
对动态尺寸记录的期望行长度是:
3
+ (number of columns + 7) / 8
+ (number of char columns)
+ packed size of numeric columns
+ length of strings
+ (number of NULL columns + 7) / 8

对每个连接有6个字节的惩罚。无论何时更改引起记录的增大,一个动态记录被链接。每个新链接将至少是20个字节,因此下一增大将可能在同一链连中。如果不是,将有另外一个链接。你可以用myisamchk -ed检查有多少链接。所有的链接可以用 myisamchk -r 删除。

10.6.3 压缩表的特点
一张用myisampack实用程序制作的只读表。所有具有MySQL扩展电子邮件支持的客户可以为其内部使用保留一个myisampack拷贝。
解压缩代码存在于所有MySQL分发,以便甚至没有myisampack的客户能读取用myisampack压缩的表。
占据很小的磁盘空间,使磁盘使用量减到最小。
每个记录被单独压缩(很小的存取开销)。对一个记录的头是定长的(1-3 字节),取决于表中最大的记录。每列以不同方式被压缩。一些压缩类型是:
通常对每列有一张不同的哈夫曼表。
后缀空白压缩。
前缀空白压缩。
用值0的数字使用1位存储。
如果整数列的值有一个小范围,列使用最小的可能类型来存储。例如,如果所有的值在0到255的范围,一个BIGINT列(8个字节)可以作为一个TINYINT列(1字节)存储。
如果列仅有可能值的一个小集合,列类型被变换到ENUM。
列可以使用上面的压缩方法的组合。
能处理定长或动态长度的记录,然而不能处理BLOB或TEXT列。
能用myisamchk解压缩。
MySQL能支持不同的索引类型,但是一般的类型是ISAM。这是一个B树索引并且你能粗略地为索引文件计算大小为(key_length+4)*0.67,在所有的键上的总和。(这是对最坏情况,当所有键以排序顺序被插入时。)

字符串索引是空白压缩的。如果第一个索引部分是一个字符串,它也将压缩前缀。如果字符串列有很多尾部空白或是一个总不能用到全长的VARCHAR列,空白压缩使索引文件更小。如果很多字符串有相同的前缀,前缀压缩是有帮助的。

10.6.4 内存表的特点
堆桌子仅存在于内存中,因此如果mysqld被关掉或崩溃,它们将丢失,但是因为它们是很快,不管怎样它们是有用的。

MySQL内部的HEAP表使用没有溢出区的100%动态哈希并且没有与删除有关的问题。

你只能通过使用在堆表中的一个索引的用等式存取东西(通常用=操作符)。

堆表的缺点是:

你要为你想要同时使用的所有堆表需要足够的额外内存。
你不能在索引的一个部分上搜索。
你不能顺序搜索下一个条目(即使用这个索引做一个ORDER BY)。
MySQL也不能算出在2个值之间大概有多少行。这被优化器使用来决定使用哪个索引,但是在另一方面甚至不需要磁盘寻道。
10.7 其他优化技巧
对加快系统的未分类的建议是:

使用持久的连接数据库以避免连接开销。
总是检查你的所有询问确实使用你已在表中创建了的索引。在MySQL中,你可以用EXPLAIN命令做到。见7.22 EXPLAIN句法(得到关于SELECT的信息)。
尝试避免在被更改了很多的表上的复杂的SELECT查询。这避免与锁定表有关的问题。
在一些情况下,使得基于来自其他表的列的信息引入一个“ 哈希”的列有意义。如果该列较短并且有合理的唯一值,它可以比在许多列上的一个大索引快些。在MySQL中,很容易使用这个额外列:SELECT * from table where hash='calculated hash on col1 and col2' and col_1='constant' and col_2='constant' and .. 。
对于有很多更改的表,你应该试着避免所有VARCHAR或BLOB列。只要你使用单个VARCHAR或BLOB列,你将得到动态行长度。见9.4 MySQL表类型。
只是因为行太大,分割一张表为不同的表一般没有什么用处。为了存取行,最大的性能命冲击磁盘寻道以找到行的第一个字节。在找到数据后,大多数新型磁盘对大多数应用程序来说足够快,能读入整个行。它确实有必要分割的唯一情形是如果其动态行尺寸的表(见上述)能变为固定的行大小,或如果你很频繁地需要扫描表格而不需要大多数列。见9.4 MySQL表类型。
如果你很经常地需要基于来自很多行的信息计算(如计数),引入一个新表并实时更新计数器可能更好一些。类型的更改UPDATE table set count=count+1 where index_column=constant是很快的!当你使用象MySQL那样的只有表级锁定(多重读/单个写)的数据库时,这确实重要。这也将给出大多数数据库较好的性能,因为锁定管理器在这种情况下有较少的事情要做。 11111111111111111111111
如果你需要从大的记录文件表中收集统计信息,使用总结性的表而不是扫描整个表。维护总结应该比尝试做“实时”统计要快些。当有变化而不是必须改变运行的应用时,从记录文件重新生成新的总结表(取决于业务决策)要快多了!
如果可能,应该将报告分类为“实时”或“统计”,这里统计报告所需的数据仅仅基于从实际数据产生的总结表中产生。
充分利用列有缺省值的事实。当被插入值不同于缺省值时,只是明确地插入值。这减少MySQL需要做的语法分析并且改进插入速度。
在一些情况下,包装并存储数据到一个BLOB中是很方便的。在这种情况下,你必须在你的应用中增加额外的代码来打包/解包BLOB中的东西,但是这种方法可以在某些阶段节省很多存取。当你有不符合静态的表结构的数据时,这很实用。
在一般情况下,你应该尝试以第三范式保存数据,但是如果你需要这些以获得更快的速度,你应该不用担心重复或创建总结表。
存储过程或UDF(用户定义函数)可能是获得更好性能的一个好方法,然而如果你使用某些不支持它的数据库,在这种情况中,你应该总是有零一个方法(较慢的)做这些。
你总是能通过在你的应用程序中缓冲查询/答案并尝试同时做很多插入/更新来获得一些好处。如果你的数据库支持锁定表(象MySQL和Oracle),这应该有助于确保索引缓冲在所有更新后只清空一次。
但你不知道何时写入你的数据时,使用INSERT /*! DELAYED */。这加快处理,因为很多记录可以用一次磁盘写入被写入。
当你想要让你的选择显得更重要时,使用INSERT /*! LOW_PRIORITY */。
使用SELECT /*! HIGH_PRIORITY */来取得塞入队列的选择,它是即使有人等待做一个写入也要完成的选择。
使用多行INSERT语句来存储很多有一条SQL命令的行(许多SQL服务器支持它)。
使用LOAD DATA INFILE装载较大数量的数据。这比一般的插入快并且当myisamchk集成在mysqld中时,甚至将更快。
使用AUTO_INCREMENT列构成唯一值。
当使用动态表格式时,偶尔使用OPTIMIZE TABLE以避免碎片。见7.9O PTIMIZE TABLE句法。
可能时使用HEAP表以得到更快的速度。见9.4 MySQL表类型。
当使用一个正常Web服务器设置时,图象应该作为文件存储。这仅在数据库中存储的一本文件的引用。这样做的主要原因是是一个正常的Web服务器在缓冲文件比数据库内容要好得多,因此如果你正在使用文件,较容易得到一个较快的系统。
对经常存取的不重要数据(象有关对没有cookie用户最后显示标语的信息)使用内存表。
在不同表中具有相同信息的列应该被声明为相同的并有相同的名字。在版本 3.23 前,你只能靠较慢的联结。尝试使名字简单化(在客户表中使用name而不是customer_name)。为了使你的名字能移植到其他SQL服务器,你应该使他们短于18 个字符。
如果你需要确实高的速度,你应该研究一下不同SQL服务器支持的数据存储的底层接口!例如直接存取MySQL MyISAM,比起使用SQL 接口,你能得到2-5倍的速度提升。然而为了能做到它,数据必须是在与应用程序性在同一台机器的服务器上,并且通常它只应该被一个进程存取(因为外部文件锁定确实很慢)。通过在MySQL服务器中引进底层MyISAM命令能消除以上问题(如果需要,这可能是获得更好性能的一个容易的方法)。借助精心设计的数据库接口,应该相当容易支持这类优化。
在许多情况下,从一个数据库存取数据(使用一个实时连接)比存取一个文本文件快些,只是因为数据库比文本文件更紧凑(如果你使用数字数据)并且这将涉及更少的磁盘存取。你也节省代码,因为你不须分析你的文本文件来找出行和列的边界。
你也能使用复制加速。见19.1 数据库复制。
10.8 使用你自己的基准测试
你决定应该测试你的应用程序和数据库,以发现瓶颈在哪儿。通过修正它(或通过用一个“哑模块”代替瓶颈),你能容易确定下一个瓶颈(等等)。即使对你的应用程序来说,整体性能“足够好”,你至少应该对每个瓶颈做一个“计划”,如果某人“确实需要修正它”,如何解决它。

对于一些可移植的基准程序的例子,参见MySQL基准套件。见11 MySQL 基准套件。你能利用这个套件的任何程序并且为你的需要修改它。通过这样做,你能尝试不同的你的问题的解决方案并测试哪一个对你是最快的解决方案。

在系统负载繁重时发生一些问题是很普遍的,并且我们有很多与我们联系的客户,他们在生产系统中有一个(测试)系统并且有负载问题。到目前为止,被一种这些的情况是与基本设计有关的问题(表扫描在高负载时表现不好)或OS/库问题。如果系统已经不在生产系统中,它们大多数将很容易修正。

为了避免这样的问题,你应该把一些力气放在在可能最坏的负载下测试你的整个应用!

10.9 设计选择
MySQL在分开的文件中存储行数据和索引数据。许多(几乎全部)其他数据库在同一个文件中混合行和索引数据。我们相信,MySQL的选择对非常广泛的现代系统的来说是较好的。

存储行数据的另一个方法是在一个分开的区域保存每列信息(例子是SDBM和Focus)。这将对每个存取多于一列的查询获得一个性能突破。因为在多于一列被存取时,这快速退化,我们相信这个模型对通用功能的数据库不是最好。

更常见的情形是索引和数据一起被存储(就象Oracle/Sybase)。在这种情况中,你将在索引的叶子页面上找到行信息。有这布局的好处是它在许多情况下(取决于这个索引被缓冲得怎样)节省一次磁盘读。有这布局的坏处是:

表扫描更慢,因为你必须读完索引以获得数据。
你损失很多空间,因为你必须重复来自节点的索引(因为你不能在节点上存储行)
删除将随时间变化恶化数据库表(因为节点中的索引在删除后通常不被更新)。
你不能仅使用索引表为一个查询检索数据。
索引数据很难缓冲。
10.10 MySQL设计局限/折衷
因为MySQL使用极快的表锁定(多次读/一次写),留下的最大问题是在同一个表中的一个插入的稳定数据流与慢速选择的一个混合。

我们相信,在其他情况下,对大多数系统,异常快速的性能使它成为一个赢家。这种情形通常也可能通过表的多个副本来解决,但是它要花更多的力气和硬件。

对一些常见的应用环境,我们也在开发一些扩展功能以解决此问题。

10.11 可移植性
因为所有SQL服务器实现了SQL的不同部分,要花功夫写可移植的SQL应用程序。对很简单的选择/插入,它是很容易的,但是你需要越多,它越困难,而且如果你想要应用程序对很多数据库都快,它变得更难!

为了使一个复杂应用程序可移植,你需要选择它应该与之工作的很多SQL服务器。

当你能使用MySQL的crash-me 程序(http://www.mysql.com/crash-me-choose.htmy)来找出你能与之使用的数据库服务器的选择的函数、类型和限制。crash-me现在对任何可能的事情测试了很长时间,但是它仍然理解测试过的大约450件事情。

例如,如果你想要能使用Informix 或 DB2,你不应该有比18个字符更长的列名。

MySQL基准程序和crash-me是独立于数据库的。通过观察我们怎么处理它,你能得到一个感觉,你必须为编写你的独立于数据库的应用程序做什么。基准本身可在MySQL源代码分发的“sql-bench”目录下找到。他们用DBI数据库接口以Perl写成(它解决问题的存取部分)。

到http://www.mysql.com/benchmark.html看这个基准的结果。

正如你可在这些结果看见的那样,所有数据库都有一些弱点。这就是他们不同的设计折衷导致的不同行为。

如果你为数据库的独立性而努力,你需要获得每个SQL服务器瓶颈的良好感受。MySQL在检索和更新方面很快,但是在同一个表上混合读者/写者方面将有一个问题。在另一方面,当你试图存取你最近更新了的行时,Oracle有一个很大问题(直到他们被清空到磁盘上)。事务数据库总的来说在从记录文件表中生成总结表不是很好,因为在这种情况下,行级锁定几乎没用处。

为了使你的应用程序“确实独立于数据库”,你需要定义一个容易的可扩展的接口,用它你可操纵你的数据。因为C++在大多数系统上可以得到的,使用数据库的一个C++ 类接口是有意义的。

如果你使用一些某个数据库特定的功能(在MySQL中,象REPLACE命令),你应该为SQL服务器编码一个方法以实现同样的功能 (但是慢些)。用MySQL,你能使用/*! */语法把MySQL特定的关键词加到查询中。在/**/中的代码将被大多数其他SQL服务器视为一篇注释(被忽略)。

如果高性能真的比准确性更重要,就像在一些web应用程序那样。一种可能性是创建一个应用层,缓冲所有的结果以给你更高的性能。通过只是让老的结果在短时间后‘过期’,你能保持缓存合理地刷新。这在极高负载的情况下是相当不错的,在此情况下,你能动态地增加缓存到更大并且设定较高的过期时限直到一切回到正常。

在这种情况下,创建信息的表应该包含缓存初始大小和表一般应该被刷新几次的信息。

10.12 我们已将MySQL用在何处?
在MySQL起初开发期间,MySQL的功能适合我们的最大客户。他们为在瑞典的一些最大的零售商处理数据仓库。

我们从所有商店得到所有红利卡交易的每周总结并且我们被期望为所有店主提供有用的信息以帮助他们得出他们的广告战如何影响他们的顾客。

数据是相当巨量的(大约每月7百万宗交易总结)并且我们保存4-10年来的数据需要呈现给用户。我们每周顾客那里得到请求,他们想要“立刻”访问来自该数据的新报告。

我们通过每月将所有信息存储在压缩的“交易”表中来解决它。我们有一套简单的宏/脚本用来生成来自交易表的不同条件( 产品组,顾客id,商店…)的总结表。报告是由一个进行语法分析网页的小perl脚本动态生成的网页,在脚本中执行SQL语句并且插入结果。现在我们很想使用PHP或mod_perl,但是那时他们没有。

对图形数据,我们用C语言编写了一个简单的工具,它能基于SQL查询的果(对结果的一些处理)产生赠品,这也从分析HTML文件的perl脚本中动态地执行。

在大多数情况下,一个新的报告通过简单地复制一个现有脚本并且修改其中的SQL查询来完成。在一些情况下,我们将需要把更多的字段加到一个现有的总结表中或产生一个新的,但是这也相当简单,因为我们在磁盘上保存所有交易表。(目前我们有至少50G的交易表和200G的其他顾客数据)。

我们也让我们的顾客直接用ODBC存取总结表以便高级用户能自己试验数据。

我们用非常中档的Sun Ultra sparcstation ( 2×200 Mz )来处理,没有任何问题。最近我们升级了服务器之一到一台2个CPU 400 Mz的Ultra sparc,并且我们现在计划处理产品级的交易,这将意味着数据增加10番。我们认为我们能通过只是为我们的系统增加更多的磁盘就能赶上它。

我们也在试验Intel-Linux以便能更便宜地得到更多的cpu动力。既然我们有二进制可移植的数据库格式(在3.32中引入),我们将开始在应用程序的某些部分使用它。

我们最初的感觉是Linux在低到中等负载时执行的较好,但是你开始得到导致的高负载时,Solaris将表现更好,因为磁盘IO的极限,但是我们还没有关于这方面的任何结论。在与一些Linux核心开发者的讨论以后,这可能是 Linux 的副作用,它给批处理以太多的资源使得交互的性能变得很低。当大的批处理正在进行时,这使机器感觉很慢且无反应。希望这将在未来的Linux内核中解决。

 回复

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>