分类: Mysql/postgreSQL
2008-05-12 15:20:15
正式发布之前,每个MySQL版本均在很多平台上进行了测试。这不表示MySQL中不存在缺陷,但是,如果存在缺陷,它们应很少,而且很难发现。如果你遇到问题,如果你尝试找出导致系统崩溃的准确原因,这始终很有帮助,这是因为,如果这样的话,快速解决问题的机会很大。
首先,应尝试找出问题是否与mysqld服务器有关,或是否与客户端有关。通过执行mysqladmin version,可检查mysqld服务器运行了多长时间。如果mysqld宕机并重启,应查看服务器的错误日志以找出原因。请参见5.11.1节,“错误日志”。
在某些系统上,在错误日志中,可发现mysqld宕机的堆栈跟踪,可使用resolve_stack_dump程序解决它。请参见E.1.4节,“使用堆栈跟踪”。注意,错误日志中的变量值并非始终是100%正确的。
很多服务器崩溃是因损坏的数据文件或索引文件而导致的,每次执行完SQL语句之后并在向客户端通告结果之前,MySQL将使用write()系统调用更新磁盘上的文件(如果你使用了“--delay-key-write”选项,情况并非如此,此时将写入数据文件而不是索引文件)。这意味着,即使mysqld崩溃,数据文件的内容也是安全的,这是因为操作系统能保证将未刷新的数据写入磁盘。使用“--flush”选项启动mysqld,这样,每次执行完SQL语句后,可强制MySQL将所有内容写入磁盘。
前述介绍表明,在正常情况下不会出现损坏的表,除非出现了下述情况之一:
在更新过程中,MySQL服务器或服务器主机被停止。
你发现了mysqld中存在的1个缺陷,该缺陷导致mysqld在更新中途中止。
在mysqld操作的同时,某些外部程序正在操控数据文件或索引文件,未恰当锁定表。
你正使用系统上的相同数据目录运行很多mysqld服务器,该系统不支持良好的文件系统锁定(通常是由lockd锁定管理器负责的),或使用“--skip-external-locking”选项运行了多个服务器。
崩溃的数据文件或索引文件,其中包含导致mysqld混乱的损坏很严重的数据。
在数据存储节点发现缺陷。这种可能性不大,但至少是可能的。在该情况下,可在修复的表副本上,通过使用ALTER TABLE,尝试将表类型更改为另一种存储引擎。
由于很难得知为什么某事会出现崩溃,首先请检查用于其他方面的事项是否崩溃。请尝试采取下述措施:
用mysqladmin shutdown停止mysqld服务器,从数据目录运行myisamchk --silent --force */*.MYI,检查所有的MyISAM表,并重启mysqld。这样,就能确保从干净的状态运行服务器。请参见第5章:数据库管理。
使用“--log”选项启动mysqld,并根据写入日志的信息确定是否某些特殊的查询杀死了服务器。约95%的缺陷与特定的查询有关。正常情况下,这是服务器重启前日志文件中最够数个查询中的1个。请参见5.11.2节,“通用查询日志”。如果能够用特殊查询重复杀死MySQL,即使在发出查询前检查了所有表的情况下也同样,那么你就应能确定缺陷,并应提交关于该缺陷的缺陷报告。请参见1.7.1.3节,“如何通报缺陷和问题”。
尝试提供一个测试范例,我们应能利用该范例重复问题。请参见E.1.6节,“如果出现表崩溃,请生成测试案例”。
请在mysql-test目录下并根据MySQL基准进行测试。请参见27.1.2节,“MySQL测试套件”。它们能相当良好地测试MySQL。你也可以为基准测试增加代码,以模拟你的应用程序。基准测试可在源码分发版的sql-bench目录下找到,对于二进制分发版,可在MySQL安装目录下的sql-bench目录下找到。
尝试使用fork_big.pl脚本(它位于源码分发版的测试目录下)。
如果你将MySQL配置为调试模式,如果某事出错,可更为容易地搜集关于可能错误的信息。如果将MySQL配置为调试模式,可生成1个安全的内存分配程序,可使用它发现某些错误。此外,它还提供了很多输出,这类输出与出现的问题相关。在configure上使用“--with-debug”或“--with-debug=full”选项重新配置MySQL,然后再编译它。请参见E.1节,“调试MySQL服务器”。
确保为你的操作系统应用了最新的补丁。
对mysqld使用“--skip-external-locking”选项。在某些系统上,lockd锁定管理器不能正确工作,“--skip-external-locking”选项通知mysqld不使用外部锁定。(这意味着,你不能在相同的数据目录上运行2个mysqld服务器,如果使用myisamchk,必须谨慎。然而,尝试将该选项用作测试也是有益的)。
当mysqld看上去正在运行但并未响应时,是否运行了mysqladmin -u root processlist?某些时候,即使你认为mysqld处于闲置状态时,实际情况并非如此。问题可能是因为所有连接均已使用,或存在某些内部锁定问题。即使在该情况下,mysqladmin -u root processlist通常能够进行连接,并能提供关于当前连接数以及其状态的有用信息。
在运行其他查询的同时,在单独的窗口中运行命令mysqladmin -i 5 status或mysqladmin -i 5 -r status,以生成统计信息。
尝试采用下述方法:
从gdb(或另一个调试器)启动mysqld。请参见E.1.3节,“在gdb环境下调试mysqld”。
运行测试脚本。
在3个较低层面上输出backtrace(向后跟踪)和局部变量。在gdb中,当mysqld在gdb内崩溃时,可使用下述命令完成该任务:
backtrace
info local
up
info local
up
info local
使用gdb,你还能检查与info线程共存的线程,并切换至特定的线程N,其中,N是线程ID。
尝试用Perl脚本模拟你的应用程序,强制MySQL崩溃或行为异常。
发送正常的缺陷报告。请参见1.7.1.3节,“如何通报缺陷和问题”。应比通常的报告更详细。由于MySQL是为很多人提供服务的,它可能因仅存在于你的计算机上的某事崩溃(例如,与你的特定系统库有关的错误)。
如果你遇到与包含动态长度行的表有关的问题,而且你仅使用VARCHAR列(而不是BLOB或TEXT列),可尝试用ALTER TABLE将所有VARCHAR列更改为CHAR列。这样,就会强制MySQL使用固定大小的行。固定大小的行占用的空间略多,但对损坏的容忍度更高。
目前的动态行代码在MySQL AB已使用多年,很少遇到问题,但从本质上看,动态长度行更倾向于出现错误,因此,不妨尝试采用该策略以查看它是否有帮助,这不失为一个好主意。
诊断问题时不要将你的服务器硬件排除在外。有缺陷的硬件能够导致数据损坏。对硬件进行故障诊断与排除操作时,尤其应注意RAM和硬盘驱动器。
在本节中,介绍了MySQL响应磁盘满错误的方式(如“设备上无剩余空间”),以及响应超配额错误的方式(如“写入失败”或“达到了用户屏蔽限制”)。
本节介绍的内容与写入MyISAM表有关。它也适用于写入二进制日志文件和二进制索引文件,但对“row”和“record”的应用应被视为“event”。
出现磁盘满状况时,MySQL将:
每分钟检查一次,查看是否有足够空间写入当前行。如果有足够空间,将继续,就像什么也未发生一样。
每10分钟将1个条目写入日志文件,提醒磁盘满状况。
为了减轻问题,可采取下述措施:
要想继续,仅需有足够的磁盘空间以插入所有记录。
要想放弃线程,必须使用mysqladmin kill。下次检查磁盘时将放弃线程(1分钟)。
其他线程可能会正在等待导致磁盘满状况的表。如果有数个“已锁定”的线程,杀死正在磁盘满状况下等待的某一线程,以便允许其他线程继续。
对前述行为的例外是,当你使用REPAIR TABLE或OPTIMIZE TABLE时,或当索引是在LOAD DATA INFILE或ALTER TABLE语句后、在批操作中创建的。所有这些语句能创建大的临时文件,如果保留这些文件,会导致系统其他部分出现大问题。如果在MySQL执行这类操作的同时磁盘已满,它将删除大的临时文件,并将表标注为崩溃。但对于ALTER TABLE例外,旧表保持不变。
MySQL使用环境变量TMPDIR的值作为保存临时文件的目录的路径名。如果未设置TMPDIR,MySQL将使用系统的默认值,通常为/tmp、/var/tmp或/usr/tmp。如果包含临时文件目录的文件系统过小,可对mysqld使用“—tmpdir”选项,在具有足够空间的文件系统内指定1个目录。
在MySQL 5.1中,“—tmpdir”选项可被设置为数个路径的列表,以循环方式使用。在Unix平台上,路径用冒号字符“:”隔开,在Windows、NetWare和OS/2平台上,路径用分号字符“;”隔开。注意,为了有效分布负载,这些路径应位于不同的物理磁盘上,而不是位于相同磁盘的不同分区中。
如果MySQL服务器正作为复制从服务器使用,不应将“--tmpdir”设置为指向基于内存的文件系统的目录,或当服务器主机重启时将清空的目录。对于复制从服务器,需要在机器重启时仍保留一些临时文件,以便能够复制临时表或执行LOAD DATA INFILE操作。如果在服务器重启时丢失了临时文件目录下的文件,复制将失败。
MySQL会以隐含方式创建所有的临时文件。这样,就能确保中止mysqld时会删除所有临时文件。使用隐含文件的缺点在于,在临时文件目录所在的位置中,看不到占用了文件系统的大临时文件。
进行排序时(ORDER BY或GROUP BY),MySQL通常会使用1个或多个临时文件。所需的最大磁盘空间由下述表达式决定:
(length of what is sorted + sizeof(row pointer))
* number of matched rows
* 2
“row pointer”(行指针)的大小通常是4字节,但在以后,对于大的表,该值可能会增加。
对于某些SELECT查询,MySQL还会创建临时SQL表。它们不是隐含表,并具有SQL_*形式的名称。
ALTER TABLE会在与原始表目录相同的目录下创建临时表。
对于服务器用来与本地客户端进行通信的Unix套接字文件,其默认位置是/tmp/mysql.sock。这有可能导致问题,原因在于,在某些版本的Unix上,任何人都能删除/tmp目录下的文件。
在大多数Unix版本中,可对/tmp目录进行保护,使得文件只能被其所有这或超级用户(根用户)删除。为此,以根用户身份登录,并使用下述命令在/tmp目录上设置粘着位:
shell> chmod +t /tmp
通过执行ls -ld /tmp,可检查是否设置了粘着位。如果最后一个许可字符是“t”,表明设置了粘着位。
另一种方法是改变服务器创建Unix套接字文件的位置。如果进行了这类操作,还应让客户端程序知道文件的位置。能够以多种不同方式指定文件位置:
在全局或局部选项文件中指定路径。例如,将下述行置于文件/etc/my.cnf中:
[mysqld]
socket=/path/to/socket
[client]
socket=/path/to/socket
请参见4.3.2节,“使用选项文件”。
在运行客户端程序时,在命令行上为mysqld_safe指定“--socket”选项。
将MYSQL_UNIX_PORT环境变量设置为Unix套接字文件的路径。
重新从源码编译MySQL,以使用不同的默认Unix套接字文件位置。运行configure时,用“--with-unix-socket-path”选项定义文件路径。请参见2.8.2节,“典型配置选项”。
用下述命令连接服务器,能够测试新的套接字位置是否工作:
shell> mysqladmin --socket=/path/to/socket version
如果遇到与SELECT NOW()有关的问题,它返回GMT值而不是当地时间,就应通知服务器你的当前失去。如果UNIX_TIMESTAMP()返回错误值,上述方式同样适用。应为服务器所运行的环境进行这类设置,例如,在mysqld_safe或mysql.server中。请参见附录F:环境变量。
也可以对mysqld_safe使用“--timezone=timezone_name”选项,为服务器设置失去。也可以在启动mysqld之前,通过设置TZ环境变量完成该设置。
“--timezone”或TZ的允许值与系统有关。关于可接受的值,请参见操作系统文档。
在默认情况下,MySQL搜索不区分大小写(但某些字符集始终区分大小写,如czech)。这意味着,如果你使用col_name LIKE 'a%'进行搜索,你将获得以A或a开始的所有列。如果打算使搜索区分大小写,请确保操作数之一具有区分大小写的或二进制校对。例如,如果你正在比较均适用latin1字符集的列和字符串,可使用COLLATE操作符,使1个操作数具有latin1_general_cs或latin1_bin校对特性。例如:
col_name COLLATE latin1_general_cs LIKE 'a%'
col_name LIKE 'a%' COLLATE latin1_general_cs
col_name COLLATE latin1_bin LIKE 'a%'
col_name LIKE 'a%' COLLATE latin1_bin
如果希望总是以区分大小写的方式处理列,可使用区分大小写的或二进制校对声明它。请参见13.1.5节,“CREATE TABLE语法”。
简单的比较操作(>=, >, =, <, <=, 排序和分组)基于每个字符的“排序值”。具有相同排序值的字符(如‘E’, ‘e’,和‘é’)将被当作相同的写字符。
DATE值的格式是'YYYY-MM-DD'。按照标准的SQL,不允许其他格式。在UPDATE表达式以及SELECT语句的WHERE子句中应使用该格式。例如:
mysql> SELECT * FROM tbl_name WHERE date >= '2003-05-05';
为了方便,如果日期是在数值环境下使用的,MySQL会自动将日期转换为数值(反之亦然)。它还具有相当的智能,在更新时或在与TIMESTAMP、DATE或DATETIME列比较日期的WHERE子句中,允许“宽松的”字符串形式(“宽松形式”表示,任何标点字符均能用作各部分之间的分隔符。例如,'2004-08-15'和'2004#08#15'是等同的)。MySQL还能转换不含任何分隔符的字符串(如'20040815'),前体是它必须是有意义的日期。
使用<、<=、=、>=、>、或BETWEEN操作符将DATE、TIME、DATETIME或TIMESTAMP与常量字符串进行比较时,MySQL通常会将字符串转换为内部长整数,以便进行快速比较(以及略为“宽松”的字符串检查)。但是,该转换具有下述例外:
比较两列时
将DATE、TIME、DATETIME或TIMESTAMP列与表达式进行比较时
使用其他比较方法时,如IN或STRCMP()。
对于这些例外情形,会将对象转换为字符串并执行字符串比较,采用该方式进行比较。
为了保持安全,假定按字符串比较字符串,如果你打算比较临时值和字符串,将使用恰当的字符串函数。
对于特殊日期'0000-00-00',能够以'0000-00-00'形式保存和检索。在MyODBC中使用'0000-00-00'日期时,对于MyODBC 2.50.12或更高版本,该日期将被自动转换为NULL,这是因为ODBC不能处理这类日期。
由于MySQL能够执行前面所介绍的转换,下述语句均能正常工作:
mysql> INSERT INTO tbl_name (idate) VALUES (19970505);
mysql> INSERT INTO tbl_name (idate) VALUES ('19970505');
mysql> INSERT INTO tbl_name (idate) VALUES ('97-05-05');
mysql> INSERT INTO tbl_name (idate) VALUES ('1997.05.05');
mysql> INSERT INTO tbl_name (idate) VALUES ('1997 05 05');
mysql> INSERT INTO tbl_name (idate) VALUES ('0000-00-00');
mysql> SELECT idate FROM tbl_name WHERE idate >= '1997-05-05';
mysql> SELECT idate FROM tbl_name WHERE idate >= 19970505;
mysql> SELECT MOD(idate,100) FROM tbl_name WHERE idate >= 19970505;
mysql> SELECT idate FROM tbl_name WHERE idate >= '19970505';
但是,下述语句不能正常工作:
mysql> SELECT idate FROM tbl_name WHERE STRCMP(idate,'20030505')=0;
STRCMP()是一种字符串函数,它能将idate转换为'YYYY-MM-DD'格式的字符串,并执行字符串比较。它不能将'20030505'转换为日期'2003-05-05'并进行日期比较。
如果你正在使用ALLOW_INVALID_DATES SQL模式,MySQL允许以仅执行给定的有限检查方式保存日期:MySQL仅保证天位于1~31的范围内,月位于1~12的范围内。
这样就使得MySQL很适合于Web应用程序,其中,你能获得三个不同字段中的年、月、日值,也能准确保存用户插入的值(无日期验证)。
如果未使用NO_ZERO_IN_DATE SQL模式,“天”和“月”部分可能为0。如果你打算将生日保存在DATE列而且仅知道部分日期,它十分方便。
如果未使用NO_ZERO_DATE SQL模式,MySQL也允许你将'0000-00-00'保存为“伪日期”。在某些情况下,它比使用NULL值更方便。
如果无法将日期转换为任何合理值,“0”将保存在DATE列中,并被检索为'0000-00-00'。这是兼顾速度和便利性的事宜。我们认为,数据库服务器的职责是检索与你保存的日期相同的日期(即使在任何情况下,数据在逻辑上不正确也同样)。我们认为,对日期的检查应由应用程序而不是服务器负责。
如果你希望MySQL检查所有日期并仅接受合法日期(除非由IGNORE覆盖),应将sql_mode设置为"NO_ZERO_IN_DATE,NO_ZERO_DATE"。
对于SQL的新手,NULL值的概念常常会造成混淆,他们常认为NULL是与空字符串''相同的事。情况并非如此。例如,下述语句是完全不同的:
mysql> INSERT INTO my_table (phone) VALUES (NULL);
mysql> INSERT INTO my_table (phone) VALUES ('');
这两条语句均会将值插入phone(电话)列,但第1条语句插入的是NULL值,第2条语句插入的是空字符串。第1种情况的含义可被解释为“电话号码未知”,而第2种情况的含义可被解释为“该人员没有电话,因此没有电话号码”。
为了进行NULL处理,可使用IS NULL和IS NOT NULL操作符以及IFNULL()函数。
在SQL中,NULL值与任何其它值的比较(即使是NULL)永远不会为“真”。包含NULL的表达式总是会导出NULL值,除非在关于操作符的文档中以及表达式的函数中作了其他规定。下述示例中的所有列均返回NULL:
mysql> SELECT NULL, 1+NULL, CONCAT('Invisible',NULL);
如果打算搜索列值为NULL的列,不能使用expr = NULL测试。下述语句不返回任何行,这是因为,对于任何表达式,expr = NULL永远不为“真”:
mysql> SELECT * FROM my_table WHERE phone = NULL;
要想查找NULL值,必须使用IS NULL测试。在下面的语句中,介绍了查找NULL电话号码和空电话号码的方式:
mysql> SELECT * FROM my_table WHERE phone IS NULL;
mysql> SELECT * FROM my_table WHERE phone = '';
更多信息和示例,请参见3.3.4.6节,“使用NULL值”。
如果你正在使用MyISAM、InnoDB、BDB、或MEMORY存储引擎,能够在可能具有NULL值的列上增加1条索引。如不然,必须声明索引列为NOT NULL,而且不能将NULL插入到列中。
用LOAD DATA INFILE读取数据时,对于空的或丢失的列,将用''更新它们。如果希望在列中具有NULL值,应在数据文件中使用\N。在某些情况下,也可以使用文字性单词“NULL”。请参见13.2.5 “LOAD DATA INFILE语法”。
使用DISTINCT、GROUP BY或ORDER BY时,所有NULL值将被视为等同的。
使用ORDER BY时,首先将显示NULL值,如果指定了DESC按降序排列,NULL值将最后显示。
对于聚合(累计)函数,如COUNT()、MIN()和SUM(),将忽略NULL值。对此的例外是COUNT(*),它将计数行而不是单独的列值。例如,下述语句产生两个计数。首先计数表中的行数,其次计数age列中的非NULL值数目:
mysql> SELECT COUNT(*), COUNT(age) FROM person;
对于某些列类型,MySQL将对NULL值进行特殊处理。如果将NULL插入TIMESTAMP列,将插入当前日期和时间。如果将NULL插入具有AUTO_INCREMENT属性的整数列,将插入序列中的下一个编号。