5 存取或更新数据的查询速度
首先,一件事情影响所有的询问。你有的许可系统设置越复杂,你得到更多的开销。
如果你不让任何GRANT语句执行,MySQL将稍微优化许可检查。因此如果你有很大量,值得花时间来避免授权,否则更多的许可检查有更大的开销。
如果你的问题是与一些明显的MySQL函数有关,你总能在MySQL客户中计算其时间:
mysql> select benchmark(1000000,1+1);
+------------------------+
| benchmark(1000000,1+1) |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.32 sec)
上面显示MySQL能在PentiumII 400MHz上以0.32秒执行1,000,000个+表达式。
所有MySQL函数应该被高度优化,但是以可能有一些例外并且benchmark(loop_count,expression)是找出是否你的查询有问题的一个极好工具。
5.1 估计查询性能
在大多数情况下,你能通过计算磁盘寻道估计性能。对小的表,你通常能在1次磁盘寻道中找到行(因为这个索引可能被缓冲)。对更大的表,你能估计它(使用 B++ 树索引),你将需要:log(row_count)/log(index_block_length/3*2/(index_length + data_pointer_length))+1次寻道找到行。
在MySQL中,索引块通常是1024个字节且数据指针通常是4个字节,这对一个有一个索引长度为3(中等整数)的 500,000 行的表给你:log(500,000)/log(1024/3*2/(3+4)) + 1= 4 次寻道。
象上面的索引将要求大约 500,000 * 7 * 3/2 = 5.2M,(假设索引缓冲区被充满到2/3(它是典型的)),你将可能在内存中有索引的大部分并且你将可能仅需要1-2调用从OS读数据来找出行。
然而对于写,你将需要 4 次寻道请求(如上)来找到在哪儿存放新索引并且通常需2次寻道更新这个索引并且写入行。
注意,上述不意味着你的应用程序将缓慢地以 N log N 退化!当表格变得更大时,只要一切被OS或SQL服务器缓冲,事情将仅仅或多或少地更慢。在数据变得太大不能被缓冲后,事情将开始变得更慢直到你的应用程序仅仅受磁盘寻道限制(它以N log N增加)。为了避免这个增加,索引缓冲随数据增加而增加。见10.2.3 调节服务器参数。
5.2 SELECT查询的速度
总的来说,当你想要使一个较慢的SELECT ... WHERE更快,检查的第一件事情是你是否能增加一个索引。见10.4 MySQL 索引的使用。在不同表之间的所有引用通常应该用索引完成。你可以使用EXPLAIN来确定哪个索引用于一条SELECT语句。见7.22 EXPLAIN句法(得到关于一条SELECT的信息)。
一些一般的建议:
* 为了帮助MySQL更好地优化查询,在它已经装载了相关数据后,在一个表上运行myisamchk --analyze。这为每一个更新一个值,指出有相同值地平均行数(当然,对唯一索引,这总是1。)
* 为了根据一个索引排序一个索引和数据,使用myisamchk --sort-index --sort-records=1(如果你想要在索引1上排序)。如果你有一个唯一索引,你想要根据该索引地次序读取所有的记录,这是使它更快的一个好方法。然而注意,这个排序没有被最佳地编写,并且对一个大表将花很长时间!
5.3 MySQL怎样优化WHERE子句
where优化被放在SELECT中,因为他们最主要在那里使用里,但是同样的优化被用于DELETE和UPDATE语句。
也要注意,本节是不完全的。MySQL确实作了许多优化而我们没有时间全部记录他们。
由MySQL实施的一些优化列在下面:
* 删除不必要的括号:
((a AND b) AND c OR (((a AND b) AND (c AND d))))
-> (a AND b AND c) OR (a AND b AND c AND d)
* 常数调入:
(a<b AND b=c) AND a=5
-> b>5 AND b=c AND a=5
* 删除常数条件(因常数调入所需):
(B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6)
-> B=5 OR B=6
* 索引使用的常数表达式仅计算一次。
* 在一个单个表上的没有一个WHERE的COUNT(*)直接从表中检索信息。当仅使用一个表时,对任何NOT NULL表达式也这样做。
* 无效常数表达式的早期检测。MySQL快速检测某些SELECT语句是不可能的并且不返回行。
* 如果你不使用GROUP BY或分组函数(COUNT()、MIN()……),HAVING与WHERE合并。
* 为每个子联结(sub join),构造一个更简单的WHERE以得到一个更快的WHERE计算并且也尽快跳过记录。
* 所有常数的表在查询中的任何其他表前被首先读出。一个常数的表是:
o 一个空表或一个有1行的表。
o 与在一个UNIQUE索引、或一个PRIMARY KEY的WHERE子句一起使用的表,这里所有的索引部分使用一个常数表达式并且索引部分被定义为NOT NULL。
所有下列的表用作常数表:
mysql> SELECT * FROM t WHERE primary_key=1;
mysql> SELECT * FROM t1,t2
WHERE t1.primary_key=1 AND t2.primary_key=t1.id;
* 对联结表的最好联结组合是通过尝试所有可能性来找到:(。如果所有在ORDER BY和GROUP BY的列来自同一个表,那么当廉洁时,该表首先被选中。
* 如果有一个ORDER BY子句和一个不同的GROUP BY子句,或如果ORDER BY或GROUP BY包含不是来自联结队列中的第一个表的其他表的列,创建一个临时表。
* 如果你使用SQL_SMALL_RESULT,MySQL将使用一个在内存中的表。
* 因为DISTINCT被变换到在所有的列上的一个GROUP BY,DISTINCT与ORDER BY结合也将在许多情况下需要一张临时表。
* 每个表的索引被查询并且使用跨越少于30% 的行的索引。如果这样的索引没能找到,使用一个快速的表扫描。
* 在一些情况下,MySQL能从索引中读出行,甚至不咨询数据文件。如果索引使用的所有列是数字的,那么只有索引树被用来解答查询。
* 在每个记录被输出前,那些不匹配HAVING子句的行被跳过。
下面是一些很快的查询例子:
mysql> SELECT COUNT(*) FROM tbl_name;
mysql> SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;
mysql> SELECT MAX(key_part2) FROM tbl_name
WHERE key_part_1=constant;
mysql> SELECT ... FROM tbl_name
ORDER BY key_part1,key_part2,... LIMIT 10;
mysql> SELECT ... FROM tbl_name
ORDER BY key_part1 DESC,key_part2 DESC,... LIMIT 10;
下列查询仅使用索引树就可解决(假设索引列是数字的):
mysql> SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val;
mysql> SELECT COUNT(*) FROM tbl_name
WHERE key_part1=val1 AND key_part2=val2;
mysql> SELECT key_part2 FROM tbl_name GROUP BY key_part1;
下列查询使用索引以排序顺序检索,不用一次另外的排序:
mysql> SELECT ... FROM tbl_name ORDER BY key_part1,key_part2,...
mysql> SELECT ... FROM tbl_name ORDER BY key_part1 DESC,key_part2 DESC,...
5.4 MySQL怎样优化LEFT JOIN
在MySQL中,A LEFT JOIN B实现如下:
* 表B被设置为依赖于表A。
* 表A被设置为依赖于所有用在LEFT JOIN条件的表(除B外)。
* 所有LEFT JOIN条件被移到WHERE子句中。
* 进行所有标准的联结优化,除了一个表总是在所有它依赖的表之后被读取。如果有一个循环依赖,MySQL将发出一个错误。
* 进行所有标准的WHERE优化。
* 如果在A中有一行匹配WHERE子句,但是在B中没有任何行匹配LEFT JOIN条件,那么在B中生成所有列设置为NULL的一行。
* 如果你使用LEFT JOIN来找出在某些表中不存在的行并且在WHERE部分你有下列测试:column_name IS NULL,这里column_name 被声明为NOT NULL的列,那么MySQL在它已经找到了匹配LEFT JOIN条件的一行后,将停止在更多的行后寻找(对一特定的键组合)。
5.5 MySQL怎样优化LIMIT
在一些情况中,当你使用LIMIT #而不使用HAVING时,MySQL将以不同方式处理查询。
* 如果你用LIMIT只选择一些行,当MySQL一般比较喜欢做完整的表扫描时,它将在一些情况下使用索引。
* 如果你使用LIMIT #与ORDER BY,MySQL一旦找到了第一个 # 行,将结束排序而不是排序整个表。
* 当结合LIMIT #和DISTINCT时,MySQL一旦找到#个唯一的行,它将停止。
* 在一些情况下,一个GROUP BY能通过顺序读取键(或在键上做排序)来解决,并然后计算摘要直到键值改变。在这种情况下,LIMIT #将不计算任何不必要的GROUP。
* 只要MySQL已经发送了第一个#行到客户,它将放弃查询。
* LIMIT 0将总是快速返回一个空集合。这对检查查询并且得到结果列的列类型是有用的。
* 临时表的大小使用LIMIT #计算需要多少空间来解决查询。
5.6 INSERT查询的速度
插入一个记录的时间由下列组成:
* 连接:(3)
* 发送查询给服务器:(2)
* 分析查询:(2)
* 插入记录:(1 x 记录大小)
* 插入索引:(1 x 索引)
* 关闭:(1)
这里的数字有点与总体时间成正比。这不考虑打开表的初始开销(它为每个并发运行的查询做一次)。
表的大小以N log N (B 树)的速度减慢索引的插入。
加快插入的一些方法:
* 如果你同时从同一客户插入很多行,使用多个值表的INSERT语句。这比使用分开INSERT语句快(在一些情况中几倍)。
* 如果你从不同客户插入很多行,你能通过使用INSERT DELAYED语句得到更高的速度。见7.14 INSERT句法。
* 注意,用MyISAM,如果在表中没有删除的行,能在SELECT:s正在运行的同时插入行。
* 当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这通常比使用很多INSERT语句快20倍。见7.16 LOAD DATA INFILE句法。
* 当表有很多索引时,有可能多做些工作使得LOAD DATA INFILE更快些。使用下列过程:
1. 有选择地用CREATE TABLE创建表。例如使用mysql或Perl-DBI。
2. 执行FLUSH TABLES,或外壳命令mysqladmin flush-tables。
3. 使用myisamchk --keys-used=0 -rq /path/to/db/tbl_name。这将从表中删除所有索引的使用。
4. 用LOAD DATA INFILE把数据插入到表中,这将不更新任何索引,因此很快。
5. 如果你有myisampack并且想要压缩表,在它上面运行myisampack。见10.6.3 压缩表的特征。
6. 用myisamchk -r -q /path/to/db/tbl_name再创建索引。这将在将它写入磁盘前在内存中创建索引树,并且它更快,因为避免大量磁盘寻道。结果索引树也被完美地平衡。
7. 执行FLUSH TABLES,或外壳命令mysqladmin flush-tables。
这个过程将被构造进在MySQL的某个未来版本的LOAD DATA INFILE。
* 你可以锁定你的表以加速插入。
mysql> LOCK TABLES a WRITE;
mysql> INSERT INTO a VALUES (1,23),(2,34),(4,33);
mysql> INSERT INTO a VALUES (8,26),(6,29);
mysql> UNLOCK TABLES;
主要的速度差别是索引缓冲区仅被清洗到磁盘上一次,在所有INSERT语句完成后。一般有与有不同的INSERT语句那样夺的索引缓冲区清洗。如果你能用一个单个语句插入所有的行,锁定就不需要。锁定也将降低多连接测试的整体时间,但是对某些线程最大等待时间将上升(因为他们等待锁)。例如:
thread 1 does 1000 inserts
thread 2, 3, and 4 does 1 insert
thread 5 does 1000 inserts
如果你不使用锁定,2、3和4将在1和5前完成。如果你使用锁定,2、3和4将可能不在1或5前完成,但是整体时间应该快大约40%。因为INSERT, UPDATE和DELETE操作在MySQL中是很快的,通过为多于大约5次连续不断地插入或更新一行的东西加锁,你将获得更好的整体性能。如果你做很多一行的插入,你可以做一个LOCK TABLES,偶尔随后做一个UNLOCK TABLES(大约每1000行)以允许另外的线程存取表。这仍然将导致获得好的性能。当然,LOAD DATA INFILE对装载数据仍然是更快的。
为了对LOAD DATA INFILE和INSERT得到一些更快的速度,扩大关键字缓冲区。见10.2.3 调节服务器参数。
5.7 UPDATE查询的速度
更改查询被优化为有一个写开销的一个SELECT查询。写速度依赖于被更新数据大小和被更新索引的数量。
使更改更快的另一个方法是推迟更改并且然后一行一行地做很多更改。如果你锁定表,做一行一行地很多更改比一次做一个快。
注意,动态记录格式的更改一个较长总长的记录,可能切开记录。因此如果你经常这样做,时不时地OPTIMIZE TABLE是非常重要的。见7.9 OPTIMIZE TABLE句法。
5.8 DELETE查询的速度
删除一个记录的时间精确地与索引数量成正比。为了更快速地删除记录,你可以增加索引缓存的大小。见10.2.3 调节服务器参数。
从一个表删除所有行比删除行的一大部分也要得多。
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;
6.1 静态(定长)表的特点
* 这是缺省格式。它用在表不包含VARCHAR、BLOB或TEXT列时候。
* 所有的CHAR、NUMERIC和DECIMAL列充填到列宽度。
* 非常快。
* 容易缓冲。
* 容易在崩溃后重建,因为记录位于固定的位置。
* 不必被重新组织(用myisamchk),除非一个巨量的记录被删除并且你想要归还空闲磁盘空间给操作系统。
* 通常比动态表需要更多的磁盘空间。
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 删除。
6.3 压缩表的特点
* 一张用myisampack实用程序制作的只读表。所有具有MySQL扩展电子邮件支持的客户可以为其内部使用保留一个myisampack拷贝。
* 解压缩代码存在于所有MySQL分发,以便甚至没有myisampack的客户能读取用myisampack压缩的表。
* 占据很小的磁盘空间,使磁盘使用量减到最小。
* 每个记录被单独压缩(很小的存取开销)。对一个记录的头是定长的(1-3 字节),取决于表中最大的记录。每列以不同方式被压缩。一些压缩类型是:
o 通常对每列有一张不同的哈夫曼表。
o 后缀空白压缩。
o 前缀空白压缩。
o 用值0的数字使用1位存储。
o 如果整数列的值有一个小范围,列使用最小的可能类型来存储。例如,如果所有的值在0到255的范围,一个BIGINT列(8个字节)可以作为一个TINYINT列(1字节)存储。
o 如果列仅有可能值的一个小集合,列类型被变换到ENUM。
o 列可以使用上面的压缩方法的组合。
* 能处理定长或动态长度的记录,然而不能处理BLOB或TEXT列。
* 能用myisamchk解压缩。
MySQL能支持不同的索引类型,但是一般的类型是ISAM。这是一个B树索引并且你能粗略地为索引文件计算大小为(key_length+4)*0.67,在所有的键上的总和。(这是对最坏情况,当所有键以排序顺序被插入时。)
字符串索引是空白压缩的。如果第一个索引部分是一个字符串,它也将压缩前缀。如果字符串列有很多尾部空白或是一个总不能用到全长的VARCHAR列,空白压缩使索引文件更小。如果很多字符串有相同的前缀,前缀压缩是有帮助的。
6.4 内存表的特点
堆桌子仅存在于内存中,因此如果mysqld被关掉或崩溃,它们将丢失,但是因为它们是很快,不管怎样它们是有用的。
MySQL内部的HEAP表使用没有溢出区的100%动态哈希并且没有与删除有关的问题。
你只能通过使用在堆表中的一个索引的用等式存取东西(通常用=操作符)。
堆表的缺点是:
1. 你要为你想要同时使用的所有堆表需要足够的额外内存。
2. 你不能在索引的一个部分上搜索。
3. 你不能顺序搜索下一个条目(即使用这个索引做一个ORDER BY)。
4. MySQL也不能算出在2个值之间大概有多少行。这被优化器使用来决定使用哪个索引,但是在另一方面甚至不需要磁盘寻道。
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 数据库复制。
8 使用你自己的基准测试
你决定应该测试你的应用程序和数据库,以发现瓶颈在哪儿。通过修正它(或通过用一个“哑模块”代替瓶颈),你能容易确定下一个瓶颈(等等)。即使对你的应用程序来说,整体性能“足够好”,你至少应该对每个瓶颈做一个“计划”,如果某人“确实需要修正它”,如何解决它。
对于一些可移植的基准程序的例子,参见MySQL基准套件。见11 MySQL 基准套件。你能利用这个套件的任何程序并且为你的需要修改它。通过这样做,你能尝试不同的你的问题的解决方案并测试哪一个对你是最快的解决方案。
在系统负载繁重时发生一些问题是很普遍的,并且我们有很多与我们联系的客户,他们在生产系统中有一个(测试)系统并且有负载问题。到目前为止,被一种这些的情况是与基本设计有关的问题(表扫描在高负载时表现不好)或OS/库问题。如果系统已经不在生产系统中,它们大多数将很容易修正。
为了避免这样的问题,你应该把一些力气放在在可能最坏的负载下测试你的整个应用!
9 设计选择
MySQL在分开的文件中存储行数据和索引数据。许多(几乎全部)其他数据库在同一个文件中混合行和索引数据。我们相信,MySQL的选择对非常广泛的现代系统的来说是较好的。
存储行数据的另一个方法是在一个分开的区域保存每列信息(例子是SDBM和Focus)。这将对每个存取多于一列的查询获得一个性能突破。因为在多于一列被存取时,这快速退化,我们相信这个模型对通用功能的数据库不是最好。
更常见的情形是索引和数据一起被存储(就象Oracle/Sybase)。在这种情况中,你将在索引的叶子页面上找到行信息。有这布局的好处是它在许多情况下(取决于这个索引被缓冲得怎样)节省一次磁盘读。有这布局的坏处是:
* 表扫描更慢,因为你必须读完索引以获得数据。
* 你损失很多空间,因为你必须重复来自节点的索引(因为你不能在节点上存储行)
* 删除将随时间变化恶化数据库表(因为节点中的索引在删除后通常不被更新)。
* 你不能仅使用索引表为一个查询检索数据。
* 索引数据很难缓冲。
10 MySQL设计局限/折衷
因为MySQL使用极快的表锁定(多次读/一次写),留下的最大问题是在同一个表中的一个插入的稳定数据流与慢速选择的一个混合。
我们相信,在其他情况下,对大多数系统,异常快速的性能使它成为一个赢家。这种情形通常也可能通过表的多个副本来解决,但是它要花更多的力气和硬件。
对一些常见的应用环境,我们也在开发一些扩展功能以解决此问题。
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应用程序那样。一种可能性是创建一个应用层,缓冲所有的结果以给你更高的性能。通过只是让老的结果在短时间后‘过期’,你能保持缓存合理地刷新。这在极高负载的情况下是相当不错的,在此情况下,你能动态地增加缓存到更大并且设定较高的过期时限直到一切回到正常。
在这种情况下,创建信息的表应该包含缓存初始大小和表一般应该被刷新几次的信息。
12 我们已将MySQL用在何处?
在MySQL起初开发期间,MySQL的功能适合我们的最大客户。他们为在瑞典的一些最大的零售商处理数据仓库。
我们从所有商店得到所有红利卡交易的每周总结并且我们被期望为所有店主提供有用的信息以帮助他们得出他们的广告战如何影响他们的顾客。
数据是相当巨量的(大约每月7百万宗交易总结)并且我们保存4-10年来的数据需要呈现给用户。我们每周顾客那里得到请求,他们想要“立刻”访问来自该数据的新报告。
我们通过每月将所有信息存储在压缩的“交易”表中来解决它。我们有一套简单的宏/脚本用来生成来自交易表的不同条件( 产品组,顾客id,商店...)的总结表。报告是由一个进行语法分析网页的小perl脚本动态生成的网页,在脚本中执行SQL语句并且插入结果。现在我们很想使用PHP或mod_perl,但是那时他们没有。
对图形数据,我们用C语言编写了一个简单的工具,它能基于SQL查询的结果(对结果的一些处理)产生赠品,这也从分析HTML文件的perl脚本中动态地执行。
在大多数情况下,一个新的报告通过简单地复制一个现有脚本并且修改其中的SQL查询来完成。在一些情况下,我们将需要把更多的字段加到一个现有的总结表中或产生一个新的,但是这也相当简单,因为我们在磁盘上保存所有交易表。(目前我们有至少50G的交易表和200G的其他顾客数据)。
我们也让我们的顾客直接用ODBC存取总结表以便高级用户能自己试验数据。
我们用非常中档的Sun Ultra sparcstation ( 2x200 Mz )来处理,没有任何问题。最近我们升级了服务器之一到一台2个CPU 400 Mz的Ultra sparc,并且我们现在计划处理产品级的交易,这将意味着数据增加10番。我们认为我们能通过只是为我们的系统增加更多的磁盘就能赶上它。
我们也在试验Intel-Linux以便能更便宜地得到更多的cpu动力。既然我们有二进制可移植的数据库格式(在3.32中引入),我们将开始在应用程序的某些部分使用它。
我们最初的感觉是Linux在低到中等负载时执行的较好,但是你开始得到导致的高负载时,Solaris将表现更好,因为磁盘IO的极限,但是我们还没有关于这方面的任何结论。在与一些Linux核心开发者的讨论以后,这可能是 Linux 的副作用,它给批处理以太多的资源使得交互的性能变得很低。当大的批处理正在进行时,这使机器感觉很慢且无反应。希望这将在未来的Linux内核中解决。
文章来源于领测软件测试网 https://www.ltesting.net/