前言
此篇为 MySQL入门 下篇,共十章,绝大部分内容来自《MySQL 必知必会》这本书,文末有其余内容链接
二十一、创建和操纵表
21.1 创建表
MySQL不仅用于表数据操纵,而且还可以用来执行数据库和表的所 有操作,包括表本身的创建和处理。一般有两种创建表的方法:
- 使用具有交互式创建和管理表的工具
- 以直接用MySQL语句操纵
21.1.1 创建表基础
用CREATE TABLE创建表,必须给出下列信息:
- 新表的名字,在关键字CREATE TABLE之后给出;
- 表列的名字和定义,用逗号分隔。
CREATE TABLE customers
(
cust_id int NOT NULL AUTO_INCREMENT,
cust_name char(50) NOT NULL
cust_address char(50) NULL
cust_city char(50) NULL
cust_state char(5) NULL
cust_Zip char(10) NULL
cust_country char(50) NULL
cust_contact char(50) NULL
cust_email char(255) NULL
PRIMARY KEY (cust_id)
) ENGINE=InnoDB;
// 表的主键可以在创建表时用
//PRIMARY KEY关键字指定。这里,列cust_id指定作为主键列。
//整条语句由右圆括号后的分号结束。(现在先忽略ENGINE=InnoDB和AUTO_INCREMENT,后面会对它们进行介绍)
处理现有的表
在创建新表时,指定的表名必须不存在,否则 将出错。如果要防止意外覆盖已有的表,SQL要求首先手工删 除该表(请参阅后面的小节),然后再重建它,而不是简单地 用创建表语句覆盖它。
如果你仅想在一个表不存在时创建它,应该在表名后给出 IF NOT EXISTS。这样做不检查已有表的模式是否与你打算创建 的表模式相匹配。它只是查看表名是否存在,并且仅在表名不 存在时创建它。
21.1.2 使用 NULL 值
上面的语句中 表在定义的时候,字段就给了 NULL 或者 NOT NULL。
理解NULL 不要把NULL值与空串相混淆。NULL值是没有值, 它不是空串。如果指定''(两个单引号,其间没有字符),这 在NOT NULL列中是允许的。空串是一个有效的值,它不是无 值。NULL值用关键字NULL而不是空串指定。
21.1.3 主键再介绍
主键值必须唯一。即,表中的每个行必须具有唯一的主 键值。如果主键使用单个列,则它的值必须唯一。如果使用多个列,则 这些列的组合值必须唯一。
CREATE TABLE orderitems
(
order_num int NOT NULL,
order item int NOT NULL,
prod_id char(10) NOT NULL,
item_price decimal(8,2) NOT NULL,
PRIMARY KEY(order_num, order_num)
) ENGINE=InnoDB:
1、主键可以在创建表时定义(如这里所示),或者在创建表之后定义。
2、主键和NULL值:
第1章介绍过,主键为其值唯一标识表中每个 行的列。主键中只能使用不允许NULL值的列。允许NULL值的 列不能作为唯一标识。
21.1.4 使用 AUTO_INCREMENT
cust_id int NOT NULL AUTO_INCREMENT,
每个表只允许一个AUTO_INCREMENT列,而且它必须被索引(如,通 过使它成为主键)。
让MySQL生成(通过自动增量)主 键的一个缺点是你不知道这些值都是谁。
可以使用 SELECT last_insert_id(); 此语句返回最后一个 AUTO_INCREMENT值,然后可以将它用于后续的 MySQL 语句。
21.15 指定默认值
如果在插入行时没有给出值,MySQL允许指定此时使用的默认值。 默认值用CREATE TABLE语句的列定义中的DEFAULT关键字指定。
CREATE TABLE orderitems
(
order_num int NOT NULL,
order item int NOT NULL,
prod_id char(10) NOT NULL,
item_price decimal(8,2) NOT NULL,
quantity init NOT NULL DEFAULT 1,
PRIMARY KEY(order_num, order_num)
) ENGINE=InnoDB:
1、不允许函数 与大多数DBMS不一样,MySQL不允许使用函 数作为默认值,它只支持常量。
2、使用默认值而不是NULL值 许多数据库开发人员使用默认值而不是NULL列,特别是对用于计算或数据分组的列更是如此。
21.1.6 引擎类型
与其他DBMS一样,MySQL有一个具体管理和处理数据的内部引擎。 在你使用CREATE TABLE语句时,该引擎具体创建表,而在你使用SELECT 语句或进行其他数据库处理时,该引擎在内部处理你的请求。多数时候, 此引擎都隐藏在DBMS内,不需要过多关注它。
但MySQL与其他DBMS不一样,它具有多种引擎。它打包多个引擎, 这些引擎都隐藏在MySQL服务器内,全都能执行CREATE TABLE和SELECT 等命令。
不同引擎有不同的功能。如果省略 ENGINE = 语句,则使用默认引擎(很可能是 MyISAN)。需要了解的引擎
- InnoDB是一个可靠的事务处理引擎(参见第26章),它不支持全文本搜索;
- MEMORY在功能等同于MyISAM,但由于数据存储在内存(不是磁盘) 中,速度很快(特别适合于临时表);
- MyISAM是一个性能极高的引擎,它支持全文本搜索(参见第18章), 但不支持事务处理。
外键不能跨引擎 混用引擎类型有一个大缺陷。外键(用于 强制实施引用完整性)不能跨引擎,即使用一 个引擎的表不能引用具有使用不同引擎的表的外键。
21.2 更新表
复杂的表结构更改一般需要手动删除过程,它涉及以下步骤:
- 用新的列布局创建一个新表;
- 使用INSERT SELECT语句从旧表复制数据到新表。如果有必要,可使用转换函数和 计算字段;
- 检验包含所需数据的新表;
- 重命名旧表(如果确定,可以删除它);
- 用旧表原来的名字重命名新表;
- 根据需要,重新创建触发器、存储过程、索引和外键。
小心使用ALTER TABLE 使用ALTER TABLE要极为小心,应该 在进行改动前做一个完整的备份(模式和数据的备份)。数据 库表的更改不能撤销,如果增加了不需要的列,可能不能删 除它们。类似地,如果删除了不应该删除的列,可能会丢失 该列中的所有数据。
21.3 删除表
删除表(删除整个表而不是其内容)非常简单,使用DROP TABLE语 句即可:
21.4 重命名表
使用RENAME TABLE语句可以重命名一个表:
RENAME TABLE customers2 TO customers;
RENAME TABLE backup_customers TO customer,
backuo_vendors TO vendors;
backup_products TO products;
二十二、视图(View)
22.1 视图
视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态 检索数据的查询。
22.1.1 为什么使用视图
- 重用 SQL语句
- 简化复杂的 SQL操作
- 使用原表的组成部分而不是整个表
- 保护数据。可以给用户授权表的特定部分的访问权限而不是整张表的权限
- 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据
视图仅仅是用来查看存储在别处的数据的一种设施。视图本身不包含数据,因此他们返回的数据是从其他表中检索出来的。在添加或更改这些表中的数据时,视图将返回改变过的数据
性能问题:
因为视图不包含书数据,所以每次使用视图时,都必须处理查询执行时所需的任何一个检索。如果使用了视图嵌套或者使用多个联结创建了复杂的视图,性能可能会下降的很厉害。因此,在部署使用了大量视图的应用前,应该进行测试。
22.1.2 视图的规则和限制
- 与表一样,视图必须唯一命名(不能给视图取与别的视图或表相 同的名字)。
- 对于可以创建的视图数目没有限制
- 为了创建视图,必须具有足够的访问权限。这些限制通常由数据 库管理人员授予。
- 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造 一个视图。
- ORDER BY可以用在视图中,但如果从该视图检索数据SELECT中也 含有ORDER BY,那么该视图中的ORDER BY将被覆盖。
- 视图不能索引,也不能有关联的触发器或默认值。
- 视图可以和表一起使用。例如,编写一条联结表和视图的SELECT 语句。
22.2 使用视图
- 使用 CREATE VIEW 来创建
- 使用 SHOW CREATE VIEW viewname;来查看创建视图的语句
- 使用 DROP 来删除视图,语法为 DROP VIEW viewname;
- 更新视图时,先用 DROP 再用 CREATE,也可以直接用 DROP OR REPLACE VIEW。如果视图不存在会创建一个新视图,如果存在就会替换原来的视图。
22.2.1 利用视图简化复杂的联结
视图最常见的应用是隐藏复杂的 SQL,通常会设计联结
CREATE VIEW productcustomers AS
SELECT cust_name, cust_concat, prod_id
FROM customers, orders, orderitems
WHERE customers.cust_id = orders.cust_id
AND orderitems.order_num = orders.order_num;
这条语句创建一个名为productcustomers的视图,它联结三个 表,返回已订购了任意产品的所有客户的列表。如果执行 SELECT * FROM productcustomers,将列出订购了任意产品的客户
22.2.2 利用视图 重新格式化检索出的数据
CREATE VIEW vendorlocations AS
SELECT Concat(RTrim(vend_name), ' (', RTrim(vend_contry), ' )') AS vend_title
FROM vendors
ORDER BY vend_name;
22.2.3 用视图过滤不想要的数据
视图对于应用普通的 WHERE 子句也很有用(主要起个简化作用)
CREATE VIEW customeremaillist AS
SELECT cust_id, cust_name, cust_email
FROM customers
WHERE cust_email IS NOT NULL;
现在,可以像使用其他表一样使用customeremaillist
WHERE子句与WHERE子句 如果从视图检索数据时使用了一条 WHERE子句,则两组子句(一组在视图中,另一组是传递给视 图的)将自动组合。
22.2.4 使用视图与计算字段
视图对于简化计算字段的使用特别有用。
CREATE VIEW orderitemsexpanded AS
SELECT order_num,
prod_id,
quantity,
item_price,
quantity*item_price AS expanded_price
FROM orderitems;
检索订单20005的详细内容:
可以看到,视图非常容易创建,而且很好使用。正确使用视图可 极大地简化复杂的数据处理
22.2.5 更新视图
迄今为止的所有视图都是和SELECT语句使用的。然而,视图的数据能否更新?
答案视情况而定。 如果视图定义中有以下操作,则不能进行视图的更新:
- 分组(使用GROUP BY和HAVING);
- 联结;
- 子查询
- 并;
- 聚集函数(Min()、Count()、Sum()等)
- DISTINCT;
- 导出(计算)列
换句话说,本章许多例子中的视图都是不可更新的。这听上去好像 是一个严重的限制,但实际上不是,因为视图主要用于数据检索。
1、可能的变动 上面列出的限制自MySQL 5以来是正确的。不 过,未来的MySQL很可能会取消某些限制
2、将视图用于检索 一般应该将视图用于检索(SELECT语句) 而不用于更新(INSERT、UPDATE和DELETE)。
二十三、使用存储过程
23.1 存储过程
迄今为止,使用的大多数SQL语句都是针对一个或多个表的单条语 句。并非所有操作都这么简单,经常会有一个完整的操作需要多条语句 才能完成。举个书中的例子
- 为了处理订单,需要核对以保证库存中有相应的物品
- 如果库存有物品,这些物品需要预定以便不将它们再卖给别的人, 并且要减少可用的物品数量以反映正确的库存量
- 库存中没有的物品需要订购,这需要与供应商进行某种交互。
- 关于哪些物品入库(并且可以立即发货)和哪些物品退订,需要 通知相应的客户
执行这个处理需要针对许多表的多条MySQL语句。
那么,怎样编写此代码?可以单独编写每条语句,并根据结果有条件地执行另外的语句。在每次需要这个处理时(以及每个需要它的应用 中)都必须做这些工作。
可以创建存储过程。存储过程简单来说,就是为以后的使用而保存 的一条或多条MySQL语句的集合。可将其视为批文件,虽然它们的作用 不仅限于批处理。
23.2 为什么要使用存储过程
- 通过把处理封装在容易使用的单元中,简化复杂的操作。
- 由于不要求反复建立一系列处理步骤,这保证了数据的完整性。 如果所有开发人员和应用程序都使用同一(试验和测试)存储过 程,则所使用的代码都是相同的。 这一点的延伸就是防止错误。需要执行的步骤越多,出错的可能性就越大。防止错误保证了数据的一致性
- 简化对变动的管理。如果表名、列名或业务逻辑(或别的内容) 有变化,只需要更改存储过程的代码。使用它的人员甚至不需要 知道这些变化。
- 提高性能。因为使用存储过程比使用单独的SQL语句要快
一般来说,存储过程的编写比基本SQL语句复杂,编写存储过程 需要更高的技能,更丰富的经验。
你可能没有创建存储过程的安全访问权限。许多数据库管理员限制存储过程的创建权限,允许用户使用存储过程,但不允许他们创建存储过程。
23.3 使用存储过程
使用存储过程需要知道如何执行(运行)它们。存储过程的执行远 比其定义更经常遇到。所以先看怎么使用
23.3.1 执行存储过程
MySQL称存储过程的执行为调用,因此MySQL执行存储过程的语句 为CALL。CALL接受存储过程的名字以及需要传递给它的任意参数。
CALL productpricing(
@pricelow,
@pricehigh,
@priceacerage
);
// 存储过程可以显示或者不显示结果
23.3.2 创建存储过程
CREATE PROCEDURE productpricing()
BEGIN
SELECT Avg(prod_price) AS priceaverage
FROM productsl;
ENDl
代码分析:此存储过程名为 productpricing,用CREATE PROCEDURE productpricing()语 句定义。如果存储过程接受参数,它们将在()中列举出来。此存储过程没有参数,但后跟的()仍然需要。BEGIN和END语句用来限定存储过程体,过程体本身仅是一个简单的SELECT语句
mysql命令行客户机的分隔符
如果你使用的是mysql命令行 实用程序,应该仔细阅读此说明。
默认的MySQL语句分隔符为;(正如你已经在迄今为止所使用 的MySQL语句中所看到的那样)。mysql命令行实用程序也使 用 ; 作为语句分隔符。如果命令行实用程序要解释存储过程自身内的;字符,则它们最终不会成为存储过程的成分,这会使 存储过程中的SQL出现句法错误。
解决办法是临时更改命令行实用程序的语句分隔符,如下所示:
DELIMITER //
CREATE PROCEDURE productpricing()
BEGIN
SELECT Avg (prod_price) As priceaverage
FROM products;
END //
DELIMITER ;
其中,“DELIMITER //” 告诉命令行实用程序使用“//“作为新的语 句结束分隔符,可以看到标志存储过程结束的END定义为 “END //” 而不是“ END”;。这样,存储过程体内的;仍然保持不动,并且正确地传递给数据库引擎。最后,为恢复为原来的语句分隔符,可使用“ DELIMITER ; ”。
除\符号外,任何字符都可以用作语句分隔符。
怎么使用上面这个存储过程?
代码分析:CALL productpricing();执行刚创建的存储过程并显示返回 的结果。因为存储过程实际上是一种函数,所以存储过程名后 需要有()符号(即使不传递参数也需要)。
23.3.3 删除存储过程
存储过程在创建之后,被保存在服务器上以供使用,直至被删除
删除刚刚的存储过程
DROP PROCEDURE productpricing;
// 请注意没有使用后面的(),只给出存储过程名。
和之前删除表的操作一样,如果不确定表是否存在,可以使用 IF EXISTS
DROP PROCEDURE IF EXISTS
23.3.4 使用参数
producrpricing 只是一个简单的存储过程,它简单地显示 SELECT 语句的结果。一般,存储过程并不显示结果,而是把结果返回给你指定的变量。
变量(variable)内存中一个特定的位置,用来临时存储数据。
- 例子一
以下是 productpricing 的修改版本(如果不先删除此存储过程,则 不能再次创建它):
CREATE PRODUCEDURE productpricing(
OUT pl DECIMAL(8,2),
OUT ph DECIMAL(8,2),
OUT pa DECIMAL(8,2),
)
BEGIN
SELECT Min(prod_price)
INTO pl
FROM products;
SELECT Max(prod_price)
INTO ph
FROM products;
SELECT Avg(prod_price)
INTO pa
FROM products;
END;
代码分析:此存储过程接受3个参数:pl存储产品最低价格,ph存储产品 最高价格,pa存储产品平均价格。每个参数必须具有指定的类 型,这里使用十进制值。关键字OUT指出相应的参数用来从存储过程传出 一个值(返回给调用者)。MySQL支持IN(传递给存储过程)、OUT(从存 储过程传出,如这里所用)和INOUT(对存储过程传入和传出)类型的参数。存储过程的代码位于BEGIN和END语句内,如前所见,它们是一系列 SELECT语句,用来检索值,然后保存到相应的变量(通过指定INTO关键字)。
调用代码:productpricing,
CALL productpricing(
@pricelow,
@pricehigh,
@priceaverage
);
// 在调用时,这条语句并不显示任何数据。它返回以后可以显示(或在其他处理中使用)的变量。
变量名 所有MySQL变量都必须以@开始。
使用上面返回的三个值:
- 例子二
这次使用IN和OUT参数。ordertotal接受订单 号并返回该订单的合计。
CREATE PROCEDURE ordertotal(
IN onumber INT,
OUT ototal DECIMAL(8,2)
BEGIN
SELECT Sum(item_price*quantity)
FROM orderitems
WHERE order_num = onumber
INTO ototal;
END:
代码分析:onumber定义为IN,因为订单号被传入存储过程。tototal定义 为OUT,因为要从存储过程返回合计。SELECT语句使用这两个 参数,WHERE子句使用onumber选择正确的行,INTO使用ototal存储计算 出来的合计。
调用:
CALL ordertotal(20005, @total);
代码分析:必须给ordertotal传递两个参数;第一个参数为订单号,第二 个参数为包含计算出来的合计的变量名
23.3.5 建立智能存储过程
一个完整的复杂存储过程
-- Name: ordertotal
-- Parameters: onumber = order number
-- taxable = 0 if not taxable, 1 if taxable
-- ototal = order total variable
CREATE PROCEDURE ordertotal(
IN onumber INT,
IN taxable BOOLEAN,
OUT ototal DECIMAL(8, 2)
) COMMENT ‘Obtain order total, optionally adding tax’
BEGIN
-- Declare variable for total
DECLARE total DECIMAL(8, 2);
-- Declare tax percentage
DECLARE taxrate INT DEFAULT 6;
-- Get the order total
SELECT Sum(item_price*quantity)
FROM orderitems
WHERE order_num = onumber
INTO total;
-- Is this taxable?
IF taxable THEN
--Yes,So add taxrate to the total
SELECT total+(total/100*taxrate) INTO total;
END IF;
-- And finally, save to out variable
SELECT total INTO ototal;
END;
代码分析:此存储过程有很大的变动。首先,增加了注释(前面放置--)。 在存储过程复杂性增加时,这样做特别重要。添加了另外一个 参数taxable,它是一个布尔值(如果要增加税则为真,否则为假)。在存储过程体中,用DECLARE语句定义了两个局部变量。DECLARE要求指定 变量名和数据类型,它也支持可选的默认值(这个例子中的taxrate的默 认被设置为6%)。SELECT语句已经改变,因此其结果存储到total(局部 变量)而不是ototal。IF语句检查taxable是否为真,如果为真,则用另 一SELECT语句增加营业税到局部变量total。最后,用另一SELECT语句将 total(它增加或许不增加营业税)保存到ototal。
COMMENT关键字 本例子中的存储过程在CREATE PROCEDURE语 句中包含了一个COMMENT值。它不是必需的,但如果给出,将 在SHOW PROCEDURE STATUS的结果中显示。
分析:BOOLEAN值指定为1表示真,指定为0表示假(实际上,非零值 都考虑为真,只有0被视为假)。通过给中间的参数指定0或1,可以有条件地将营业税加到订单合计上。
IF语句 这个例子给出了MySQL的IF语句的基本用法。IF语 句还支持ELSEIF和ELSE子句(前者还使用THEN子句,后者不使用)。在以后章节中我们将会看到IF的其他用法(以及其他流控制语句)。
23.3.6 检查存储过程
显示一个存储过程
SHOW CREATE PROCEDURE ordertotal;
获得详细的存储过程信息
SHOW PROCEDURE STATUS // 会列出所有存储过程信息
// 使用 LIKE 关键字可以做过滤
SHOW PROCEDURE STATUS LIKE 'ordertotal'
二十四、使用游标(cursor)
游标是一个存储在Mysql 服务器上的数据库查询,它不是一条 SELECT 语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。
游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改。
只能用于存储过程 不像多少 DBMS,Mysql 游标只能用于存储过程(函数)
24.1 使用游标
- 在能够使用游标前,必须声明(定义)它。这个过程实际上没有 检索数据,它只是定义要使用的SELECT语句。
- 一旦声明后,必须打开游标以供使用。这个过程用前面定义的 SELECT语句把数据实际检索出来。
- 对于填有数据的游标,根据需要取出(检索)各行。
- 在结束游标使用时,必须关闭游标。
24.1.1 创建游标
使用 DECLARE 命名游标,并定义相应的 SELECT语句,根据需要带 WHERE 和其他子句。例如,下面的语句定 义了名为ordernumbers的游标,使用了可以检索所有订单的SELECT语
DELIMITER //
CREATE PROCEDURE processorders()
BEGIN
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
END //
DELIMITER ;
分析:这个存储过程并没有做很多事情,DECLARE语句用来定义和命名游标,这里为ordernumbers。存储过程处理完成后,游标就消失(因为它局限于存储过程)。
在定义游标之后,可以打开它。
24.1.2 打开和关闭游标
游标使用 OPEN CURSOR 语句来打开:
OPEN CURSOR
分析: 在处理OPEN语句时执行查询,存储检索出的数据以供浏览和滚动。
游标使用 CLOS CURSOR 语句来关闭:
CLOSE ordernumbers;
分析:CLOSE释放游标使用的所有内部内存和资源,因此在每个游标 不再需要时都应该关闭。 在一个游标关闭后,如果没有重新打开,则不能使用它。但是,使用声明过的游标不需要再次声明,用OPEN语句打开它就可以了。
隐含关闭 如果你不明确关闭游标,MySQL将会在到达END语 句时自动关闭它。
上面例子的修改版本:
CREATE PROCEDURE processorders(
BEGIN
-- Declare the cursor
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
-- Open the cursor
OPEN ordernumbers;
-- Close the cursor
CLOSE ordernumbers;
END;
24.1.3 使用游标数据
在一个游标被打开后,可以使用FETCH语句分别访问它的每一行。 FETCH指定检索什么数据(所需的列),检索出来的数据存储在什么地方。 它还向前移动游标中的内部行指针,使下一条FETCH语句检索下一行(不 重复读取同一行)。
CREATE PROCEDURE processorders(
BEGIN
-- Declare 1ocal variables
DECLARE 0 INT;
-- Declare the cursor
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
-- Open the cursor
OPEN ordernumbers
-- Get order number
FETCH ordernumbers INTO 0;
-- Close the cursor
CLOSE ordernumbers;
END:
分析:其中FETCH用来检索当前行的order_num列(将自动从第一行开 始)到一个名为o的局部声明的变量中。对检索出的数据不做 任何处理。
循环检索数据的例子:
CREATE PROCEDURE processorders()
BEGIN
-- 声明本地变量
DECLARE done BOOLEAN DEFAULT 0;
DECLARE O INT;
-- 声明游标
DECLARE ordernumbers CURSOR
FOR
SELECT order num FROM orders;
-- 声明 continue handler
DECLARE CONTINUE HANDLER FOR SQLSTATE *02000’ SET done=1;
-- 打开游标
OPEN ordernumbers:
-- 遍历所有行
REPEAT
-- 获取 order number
FETCH ordernumbers INTO o;
-- 结束循环
UNTIL done END REPEAT;
-- 关闭游标
CLOSE ordernumbers;
END;
分析:与前一个例子一样,这个例子使用FETCH检索当前order_num 到声明的名为o的变量中。但与前一个例子不一样的是,这个 例子中的FETCH是在REPEAT内,因此它反复执行直到done为真(由UNTIL done END REPEAT;规定)。为使它起作用,用一个DEFAULT 0(假,不结 束)定义变量done。那么,done怎样才能在结束时被设置为真呢?答案 是用以下语句:
DECLARE CONTINUE HANDLER FOR SQLSTATE *02000’ SET done=1;
这条语句定义了一个CONTINUE HANDLER,它是在条件出现时被执行 的代码。这里,它指出当SQLSTATE '02000'出现时,SET done=1。SQLSTATE '02000'是一个未找到条件(一个 Mysql 的错误代码),当REPEAT由于没有更多的行供循环而不能继续时,出现这个条件。
DECLARE语句的次序 DECLARE语句的发布存在特定的次序。 用DECLARE语句定义的局部变量必须在定义任意游标或句柄 之前定义,而句柄必须在游标之后定义。不遵守此顺序将产 生错误消息。
如果调用这个存储过程,它将定义几个变量和一个CONTINUE HANDLER,定义并打开一个游标,重复读取所有行,然后关闭游标。 如果一切正常,你可以在循环内放入任意需要的处理(在FETCH语句之后,循环结束之前)。
重复或循环? 除这里使用的REPEAT语句外,MySQL还支持 循环语句,它可用来重复执行代码,直到使用LEAVE语句手动 退出为止。通常REPEAT语句的语法使它更适合于对游标进行循环。
为了把这些内容组织起来,下面给出游标存储过程样例的更进一步修改的版本,这次对取出的数据进行某种实际的处理:
CREATE PROCEDURE processorders()
BEGIN
-- Declare 1ocal variables
DECLARE done BOOLEAN DEFAULT 0;
DECLARE OINT;
DECLARE t DECIMAL(8, 2);
-- Declare the cursor
DECLARE ordernumbers CURSOR
FOR
SELECT ordernum FROM orders;
-- Declare continue handler
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000" SET done=1;
-- Create a table to store the results
CREATE TABLE IF NOT EXISTS ordertotals
(order_num INT, total DECIMAL(8,2));
-- Open the cursor
OPEN ordernumbers;
-- Loop through all rows
REPEAT
-- Get order number
FETCH ordernumbers INTO o;
-- Get the total for this order
CALL ordertotal(o, 1,t);
-- Insert order and total into ordertotals
INSERT INTO ordertotals (order_num,, total)
VALUES(O, t);
-- End of 1oop
UNTIL done END REPEAT;
-- Close the cursor
CLOSE ordernumbers;
END;
分析:在这个例子中,我们增加了另一个名为t的变量(存储每个订 单的合计)。此存储过程还在运行中创建了一个新表(如果它不 存在的话),名为ordertotals。这个表将保存存储过程生成的结果。FETCH 像以前一样取每个order_num,然后用CALL执行另一个存储过程(我们在 前一章中创建)来计算每个订单的带税的合计(结果存储到t)。最后, 用INSERT保存每个订单的订单号和合计。
此存储过程不返回数据,但它能够创建和填充另一个表,可以用一 条简单的SELECT语句查看该表:
这样,我们就得到了存储过程、游标、逐行处理以及存储过程调用 其他存储过程的一个完整的工作样例。
二十五、使用触发器
25.1 触发器
触发器是 MySQL 响应以下任意语句(DELETE、INSERT、UPDATE)而自动执行的一条 MySQL 语句(或位于 BEGIN 和 END语句之间的一组语句)。
25.2 创建触发器
创建触发器需要的四个信息:
- 唯一的触发器名
- 触发器关联的表
- 触发器应该响应的活动(DELETE、INSERT、UPDATE)
- 触发器何时执行(AFTER/BEFORE)
保持每个数据库的触发器名唯一
虽然要求是必须在每个表中唯一,也就是说一个库里可能重复,但推荐做法还是在保持唯一
CREATE TRIGGER newproduct AFTER INSERT ON products
FOR EACH ROW SELECT 'Product added'
分析: CREATE TRIGGER用来创建名为newproduct的新触发器。触发器 可在一个操作发生之前或之后执行,这里给出了AFTER INSERT, 所以此触发器将在INSERT语句成功执行后执行。这个触发器还指定FOR EACH ROW,因此代码对每个插入行执行。在这个例子中,文本Product added将对每个插入的行显示一次。
仅支持表 只有表才支持触发器,视图不支持(临时表也不支持)。
触发器按每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器,因此,每个表最多支持 6 个(每条 INSERT、UPDATE、DELETE 的 AFTER和 BEFORE)
触发器失败 如果BEFORE触发器失败,则MySQL将不执行请 求的操作。此外,如果BEFORE触发器或语句本身失败,MySQL 将不执行AFTER触发器(如果有的话)。
25.3 删除触发器
DROP TRIGGER newproduct
触发器不能更新或者覆盖。为了修改一个触发器,必须先删除它然后再重新创建
25.4 使用触发器
25.4.1 INSERT 触发器
INSERT触发器在INSERT语句执行之前或之后执行。需要知道以下几点:
- 在INSERT触发器代码内,可引用一个名为NEW的虚拟表,访问被插入的行;
- 在BEFORE INSERT触发器中,NEW中的值也可以被更新(允许更改 被插入的值);
- 对于AUTO_INCREMENT列,NEW在INSERT执行之前包含0,在INSERT 执行之后包含新的自动生成值。
举一个例子,AUTO_INCREMENT列具有 MySQL 自动赋予的值。
CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW SELECT NEW.order_num
分析:此代码创建一个名为neworder的触发器,它按照AFTER INSERT ON orders执行。在插入一个新订单到orders表时,MySQL生 成一个新订单号并保存到order_num中。触发器从NEW. order_num取得 这个值并返回它。此触发器必须按照AFTER INSERT执行,因为在BEFORE INSERT语句执行之前,新order_num还没有生成。对于orders的每次插入使用这个触发器将总是返回新的订单号。
BEFORE或AFTER? 通常,将BEFORE用于数据验证和净化(目 的是保证插入表中的数据确实是需要的数据)。本提示也适用 于UPDATE触发器。
25.4.2 DELETE触发器
DELETE触发器在DELETE语句执行之前或之后执行。需要知道以下两点:
- 在DELETE触发器代码内,你可以引用一个名为OLD的虚拟表,访问被删除的行;
- OLD中的值全都是只读的,不能更新。
下面的例子演示使用OLD保存将要被删除的行到一个存档表中:
CREATE TRIGGER deleteorder BEFORE DELETE ON orders
FOR EACH ROW
INSERT INTO achive_orders(order_num, order_date, cust_id)
VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
END;
分析:在任意订单被删除前将执行此触发器。它使用一条INSERT语句 将OLD中的值(要被删除的订单)保存到一个名为archive_ orders的存档表中(为实际使用这个例子,你需要用与orders相同的列 创建一个名为archive_orders的表)。
使用BEFORE DELETE触发器的优点(相对于AFTER DELETE触发器 来说)为,如果由于某种原因,订单不能存档,DELETE本身将被放弃。
多语句触发器 正如所见,触发器deleteorder使用BEGIN和 END语句标记触发器体。这在此例子中并不是必需的,不过也 没有害处。使用BEGIN END块的好处是触发器能容纳多条SQL 语句(在BEGIN END块中一条挨着一条)。
25.4.3 UPDATE触发器
UPDATE触发器在UPDATE语句执行之前或之后执行。需要知道以下几点:
- 在UPDATE触发器代码中,你可以引用一个名为OLD的虚拟表访问 以前(UPDATE语句前)的值,引用一个名为NEW的虚拟表访问新 更新的值
- 在BEFORE UPDATE触发器中,NEW中的值可能也被更新(允许更改 将要用于UPDATE语句中的值);
- OLD中的值全都是只读的,不能更新
下面的例子保证州名缩写总是大写(不管UPDATE语句中给出的是大 写还是小写):
CREATE TRIGGER updatevendor BEFORE UPDATE ON vendors
FOR EACH ROW SET NEW.vend_state = Upper(NEW.vend_state);
分析:何数据净化都需要在UPDATE语句之前进行,就像这 个 例 子 中一样。每次更新一个行时,NEW.vend_state中的 值(将用来更新表行的值)都用Upper(NEW.vend_state)替换。
25.4.4 关于触发器的进一步介绍
- 创建触发器可能需要特殊的安全访问权限,但是,触发器的执行 是自动的。如果INSERT、UPDATE或DELETE语句能够执行,则相关 的触发器也能执行。
- 应该用触发器来保证数据的一致性(大小写、格式等)。在触发器 中执行这种类型的处理的优点是它总是进行这种处理,而且是透 明地进行,与客户机应用无关。
- 触发器的一种非常有意义的使用是创建审计跟踪。使用触发器, 把更改(如果需要,甚至还有之前和之后的状态)记录到另一个 表非常容易。
- 遗憾的是,MySQL触发器中不支持CALL语句。这表示不能从触发 器内调用存储过程。所需的存储过程代码需要复制到触发器内
二十六、管理事务处理
26.1 事务处理
事务处理(transaction processing),可以用来维护数据库的完整性,它保证成批的 MYSQL 操作要么完全执行,要么完全不执行。利用事务处理,可以保证一组操作不会中途停止,它们 或者作为整体执行,或者完全不执行(除非明确指示)。如果没有错误发 生,整组语句提交给(写到)数据库表。如果发生错误,则进行回退(撤 销)以恢复数据库到某个已知且安全的状态。
并非所有引擎都支持事务处理 MyISAM和InnoDB是两种最常使用 的引擎。前者不支持明确的事务处理管理,而后者支持。
给系统添加订单的过程如下:
- 1、 检查数据库中是否存在相应的客户(从customers表查询),如果 不存在,添加他/她。
- 2、 检索客户的ID。
- 3、添加一行到orders表,把它与客户ID关联
- 4、检索orders表中赋予的新订单ID
- 5、对于订购的每个物品在orderitems表中添加一行,通过检索 出来的ID把它与orders表关联(以及通过产品ID与products表关联)。
现在,假如由于某种数据库故障(如超出磁盘空间、安全限制、表 锁等)阻止了这个过程的完成。数据库中的数据会出现什么情况? 如果故障发生在添加了客户之后,orders表添加之前,不会有什么问题。某些客户没有订单是完全合法的。在重新执行此过程时,所插入的客户记录将被检索和使用。可以有效地从出故障的地方开始执行此过程。
但是,如果故障发生在orders行添加之后,orderitems行添加之前, 怎么办呢?现在,数据库中有一个空订单。 更糟的是,如果系统在添加orderitems行之中出现故障。结果是数据库中存在不完整的订单,而且你还不知道。
如何解决这种问题?这里就需要使用事务处理了
相同的例子,这次说明过程如何工作
- 1、检查数据库中是否存在相应的客户,如果不存在,添加他/她。
- 2、提交客户信息。
- 3、检索客户的ID
- 4、添加一行到orders表。
- 5、如果在添加行到orders表时出现故障,回退(rollback)。
- 6、检索orders表中赋予的新订单ID
- 7、对于订购的每项物品,添加新行到orderitems表
- 8、如果在添加新行到orderitems时出现故障,回退所有添加的 orderitems行和orders行。
- 9、提交订单信息。
几个术语:
事务(transaction) 指一组SQL语句;
回退(rollback) 指撤销指定SQL语句的过程;
提交(commit) 指将未存储的SQL语句结果写入数据库表;
保留点(savepoint) 指事务处理中设置的临时占位符(placeholder),你可以对它发布回退(与回退整个事务处理不同)。
26.2 控制事务处理
管理事务处理的关键在于将SQL语句组分解为逻辑块,并明确规定数 据何时应该回退,何时不应该回退。
MySQL使用下面的语句来标识事务的开始:
START TRANSACTION
26.2.1 使用ROLLBACK
MYSQL 使用 ROLLBACK来回退(撤销)MYSQL语句
SELECT * FROM orders;
START TRANSACTION;
DELETE FROM orders;
SELECT * FROM orders;
ROLLBACK;
SELECT * FROM orders;
分析:首先执行一条SELECT以显示该表不为空。然后开始一 个事务处理,用一条DELETE语句删除ordertotals中的所有行,另一条 SELECT语句验证orders确实为空。这时用一条ROLLBACK语句回退 START TRANSACTION之后的所有语句,最后一条SELECT语句显示该表不为 空。
ROLLBACK只能在一个事务处理内使用(在执行一条START TRANSACTION命令之后)。
哪些语句可以回退? 事务处理用来管理INSERT、UPDATE和 DELETE语句。你不能回退SELECT语句。(这样做也没有什么意 义。)你不能回退CREATE或DROP操作。事务处理块中可以使用 这两条语句,但如果你执行回退,它们不会被撤销。
26.2.2 使用 COMMIT
一般的 MYSQL都是直接针对数据库表执行和编写的,这就是所谓的隐含提交(implicit commit),即提交操作是自动进行的。
但是,在事务处理块中,提交不会隐含地进行。为进行明确的提交, 使用COMMIT语句,如下所示
START TRANSACTION;
DELETE FROM orderitems WHERE order_num = 20010;
DELETE FROM orders WHERE order_num = 20010;
COMMIT;
分析:在这个例子中,从系统中完全删除订单20010。因为涉及更新 两个数据库表orders和orderItems,所以使用事务处理块来 保证订单不被部分删除。最后的COMMIT语句仅在不出错时写出更改。如 果第一条DELETE起作用,但第二条失败,则DELETE不会提交(实际上, 它是被自动撤销的)。
隐含事务关闭 当COMMIT或ROLLBACK语句执行后,事务会自动关闭(将来的更改会隐含提交)。
26.2.3 使用保留点
简单的ROLLBACK和COMMIT语句就可以写入或撤销整个事务处理。但 是,只是对简单的事务处理才能这样做,更复杂的事务处理可能需要部 分提交或回退。
例如,前面描述的添加订单的过程为一个事务处理。如果发生错误, 只需要返回到添加orders行之前即可,不需要回退到customers表(如果 存在的话)。 为了支持回退部分事务处理,必须能在事务处理块中合适的位置放 置占位符。这样,如果需要回退,可以回退到某个占位符。
这些占位符称为保留点。为了创建占位符,可如下使用SAVEPOINT 语句:
SAVEPOINT DELETE1;
每个保留点都取标识它的唯一名字,以便在回退时,MySQL知道要 回退到何处。为了回退到本例给出的保留点,可如下进行:
ROLLBACK TO DELETE1;
保留点越多越好 可以在MySQL代码中设置任意多的保留 点,越多越好。为什么呢?因为保留点越多,你就越能按自己 的意愿灵活地进行回退。
释放保留点 保留点在事务处理完成(执行一条ROLLBACK或 COMMIT)后自动释放。自MySQL 5以来,也可以用RELEASE SAVEPOINT明确地释放保留点。
26.2.4 更改默认提交行为
正如所述,默认的MySQL行为是自动提交所有更改。换句话说,任何 时候你执行一条MySQL语句,该语句实际上都是针对表执行的,而且所做 的更改立即生效。为指示MySQL不自动提交更改,需要使用以下语句:
SET autocommit = 0;
分析:autocommit标志决定是否自动提交更改,不管有没有COMMIT 语句。设置autocommit为0(假)指示MySQL不自动提交更改 (直到autocommit被设置为真为止)。
autocommit标志是针对每个连接而不是服务器的
二十七、全球化和本地化
27.1 字符集和校对顺序
数据库表被用来存储和检索数据。不同的语言和字符集需要以不同 的方式存储和检索。因此,MySQL需要适应不同的字符集(不同的字母 和字符),适应不同的排序和检索数据的方法。
在讨论多种语言和字符集时,将会遇到以下重要术语:
- 字符集为字母和符号的集合;
- 编码为某个字符集成员的内部表示;
- 校对为规定字符如何比较的指令
在MySQL的正常数据库活动(SELECT、INSERT等)中,不需要操心太多的东西。使用何种字符集和校对的决定在服务器、数据库和表级进行。
27.2 使用字符集和校对顺序
MySQL支持众多的字符集。为查看所支持的字符集完整列表,使用 以下语句
SHOW CHARACTER SET
分析:这条语句显示所有可用的字符集以及每个字符集的描述和默认校对。
为了查看所支持校对的完整列表,使用以下语句:
SHOW COLLATION
分析:此语句显示所有可用的校对,以及它们适用的字符集。可以看 到有的字符集具有不止一种校对。通常系统管理在安装时定义一个默认的字符集和校对。此外,也可 以在创建数据库时,指定默认的字符集和校对。为了确定所用的字符集 和校对,可以使用以下语句:
SHOW VARIABLES LIKE 'character%';
SHOW VARIABLES LIKE 'collation'
实际上,字符集很少是服务器范围(甚至数据库范围)的 设置。不同的表,甚至不同的列都可能需要不同的字符集,而且两者都 可以在创建表时指定。 为了给表指定字符集和校对,可使用带子句的CREATE TABLE
这个例子中指定了CHARACTER SET和COLLATE两者。一般,MySQL如 下确定使用什么样的字符集和校对
- 如果指定CHARACTER SET和COLLATE两者,则使用这些值。
- 如果只指定CHARACTER SET,则使用此字符集及其默认的校对(如 SHOW CHARACTER SET的结果中所示)。
- 如果既不指定CHARACTER SET,也不指定COLLATE,则使用数据库默认
除了能指定字符集和校对的表范围外,MySQL还允许对每个列设置 它们,如下所示:
如前所述,校对在对用ORDER BY子句检索出来的数据排序时起重要 的作用。如果你需要用与创建表时不同的校对顺序排序特定的SELECT语 句,可以在SELECT语句自身中进行:
临时区分大小写 上面的SELECT语句演示了在通常不区分大 小写的表上进行区分大小写搜索的一种技术。当然,反过来 也是可以的
SELECT的其他COLLATE子句 除了这里看到的在ORDER BY子 句中使用以外,COLLATE还可以用于GROUP BY、HAVING、聚集 函数、别名等。
最后,值得注意的是,如果绝对需要,串可以在字符集之间进行转换。为此,使用Cast()或Convert()函数。
二十八、安全管理
数据库服务器通常包含关键的数据,确保这些数据的安全和完整需 要利用访问控制。本章将学习MySQL的访问控制和用户管理
28.1 访问控制
MySQL服务器的安全基础是:用户应该对他们需要的数据具有适当 的访问权,既不能多也不能少。换句话说,用户不能对过多的数据具有 过多的访问权。
需要给用户 提供他们所需的访问权,且仅提供他们所需的访问权。这就是所谓的访问控制,管理访问控制需要创建和管理用户账号。
使用root进行过登录,在对非现实的数据库试验MySQL时,这样做很好很方便。不过在现实世界的日常工作中,决不能使用root。应该创建一系列的账号,有的用于管理,有的供用户使用,有的供开发人员使用,
28.2 管理用户
MySQL用户账号和信息存储在名为mysql的MySQL数据库中。一般 不需要直接访问mysql数据库和表(你稍后会明白这一点),但有时需要 直接访问。需要直接访问它的时机之一是在需要获得所有用户账号列表时。为此,可以使用如下代码:
USE mysql;
SELECT user FROM user;
分析:mysql数据库有一个名为user的表,它包含所有用户账号。user 表有一个名为user的列,它存储用户登录名。
用多个客户机进行试验 试验对用户账号和权限进行更改的 最好办法是打开多个数据库客户机(如mysql命令行实用程序的 多个副本),一个作为管理登录,其他作为被测试的用户登录。
28.2.1 创建用户账号
为了创建一个新用户账号,使用CREATE USER语句,如下所示:
CREATE USER ben IDENTIFIED BY '123456';
再次查看用户列表:
使用GRANT或INSERT GRANT语句(稍后介绍)也可以创建用 户账号,但一般来说CREATE USER是最清楚和最简单的句子。 此外,也可以通过直接插入行到user表来增加用户,不过为安 全起见,一般不建议这样做。MySQL用来存储用户账号信息 的表(以及表模式等)极为重要,对它们的任何毁坏都可能严重地伤害到MySQL服务器。因此,相对于直接处理来 说,最好是用标记和函数来处理这些表。
为重新命名一个用户账号,使用RENAME USER语句,如下所示:
RENAME USER ben TO tim;
28.2.2 删除用户账号
用DROP USER语句:
DROP USER tim;
MySQL 5之前 自MySQL 5以来,DROP USER删除用户账号和 所有相关的账号权限。在MySQL 5以前,DROP USER只能用来 删除用户账号,不能删除相关的权限。因此,如果使用旧版 本的MySQL,需要先用REVOKE删除与账号相关的权限,然后 再用DROP USER删除账号。
28.2.3 设置访问权限
在创建用户账号后,必须接着分配访问权限。新创建的用户账号没有访 问权限。它们能登录MySQL,但不能看到数据,不能执行任何数据库操作。
为看到赋予用户账号的权限,使用 SHOW GRANTS FOR
SHOW GRANTS FOR tim;
分析:输出结果显示用户ben有一个权限USAGE ON .。USAGE表 示根本没有权限(我知道,这不很直观),所以,此结果表示在 任意数据库和任意表上对任何东西没有权限。
用户定义为user@host MySQL的权限用用户名和主机名结 合定义。如果不指定主机名,则使用默认的主机名%(授予用 户访问权限而不管主机名)。
为设置权限,使用GRANT语句。GRANT要求你至少给出以下信息:
- 要授予的权限;
- 被授予访问权限的数据库或表
- 用户名。
例子:
GRANT SELECT ON products.* TO ben;
分析:此GRANT允许用户在crashcourse.*(crashcourse数据库的所 有表)上使用SELECT。通过只授予SELECT访问权限,用户ben 对crashcourse数据库中的所有数据具有只读访问权限
再次查下 ben 所有的权限,注意这里是 GRANTS
分析:发现多了一个 products 表的查询权限
GRANT的反操作为REVOKE,用它来撤销特定的权限:
REVOKE SELECT ON products.* FROM ben; // 这里是 FROM
GRANT和REVOKE可在几个层次上控制访问权限
- 整个服务器,使用GRANT ALL和REVOKE ALL;
- 整个数据库,使用ON database.*;
- 特定的表,使用ON database.table;
- 特定的列;
- 特定的存储过程。
未来的授权 在使用GRANT和REVOKE时,用户账号必须存在, 但对所涉及的对象没有这个要求。这允许管理员在创建数据库 和表之前设计和实现安全措施。 这样做的副作用是,当某个数据库或表被删除时(用DROP语 句),相关的访问权限仍然存在。而且,如果将来重新创建该 数据库或表,这些权限仍然起作用。
简化多次授权 可通过列出各权限并用逗号分隔,将多条 GRANT语句串在一起,如下所示:
GRANT SELECT, INSERT ON crashcourse.x T0 ben;
28.2.4 更改口令
为了更改用户口令,可使用SET PASSWORD语句。新口令必须如下加密:
SET PASSWORD ben = Psssword('aaaaaa');
分析:SET PASSWORD更新用户口令。新口令必须传递到Password()函 数进行加密。
SET PASSWORD还可以用来设置你自己的口令:
SET PASSWORD = PASSWORD('mysql213');
在不指定用户名时,SET PASSWORD更新当前登录用户的口令。
二十九、数据库维护
29.1 备份数据
像所有数据一样,MySQL的数据也必须经常备份。由于MySQL数据 库是基于磁盘的文件,普通的备份系统和例程就能备份MySQL的数据。 但是,由于这些文件总是处于打开和使用状态,普通的文件副本备份不 一定总是有效。
下面列出这个问题的可能解决方案。
- 使用命令行实用程序mysqldump转储所有数据库内容到某个外部 文件。在进行常规备份前这个实用程序应该正常运行,以便能正 确地备份转储文件。
- 可用命令行实用程序mysqlhotcopy从一个数据库复制所有数据 (并非所有数据库引擎都支持这个实用程序)。
- 可以使用MySQL的BACKUP TABLE或SELECT INTO OUTFILE转储所 有数据到某个外部文件。这两条语句都接受将要创建的系统文件 名,此系统文件必须不存在,否则会出错。数据可以用RESTORE TABLE来复原。
首先刷新未写数据 为了保证所有数据被写到磁盘(包括索引 数据),可能需要在进行备份前使用FLUSH TABLES语句。
29.2 进行数据库维护
MySQL提供了一系列的语句,可以(应该)用来保证数据库正确和 正常运行。
- ANALYZE TABLE,用来检查表键是否正确。ANALYZE TABLE返回如 下所示的状态信息:
- CHECK TABLE用来针对许多问题对表进行检查。在MyISAM表上还对 索引进行检查。CHECK TABLE支持一系列的用于MyISAM表的方式。 CHANGED检查自最后一次检查以来改动过的表。EXTENDED执行最 彻底的检查,FAST只检查未正常关闭的表,MEDIUM检查所有被删 除的链接并进行键检验,QUICK只进行快速扫描。如下所示,CHECK TABLE发现和修复问题:
第一次输入了个不存在的表--> 'priducts'; 第二次是正确的
- 如果MyISAM表访问产生不正确和不一致的结果,可能需要用 REPAIR TABLE来修复相应的表。这条语句不应该经常使用,如果 需要经常使用,可能会有更大的问题要解决。
- 如果从一个表中删除大量数据,应该使用OPTIMIZE TABLE来收回所用的空间,从而优化表的性能。
29.3 诊断启动问题
服务器启动问题通常在对MySQL配置或服务器本身进行更改时出现。MySQL在这个问题发生时报告错误,但由于多数MySQL服务器是作为系统进程或服务自动启动的,这些消息可能看不到。
在排除系统启动问题时,首先应该尽量用手动启动服务器。MySQL 服务器自身通过在命令行上执行mysqld启动。下面是几个重要的mysqld 命令行选项:
- --help显示帮助——一个选项列表
- --safe-mode装载减去某些最佳配置的服务器;
- --verbose显示全文本消息(为获得更详细的帮助消息与--help 联合使用);
- --version显示版本信息然后退出。
29.4 查看日志文件
MySQL维护管理员依赖的一系列日志文件。主要的日志文件有以下几种。
错误日志。 它包含启动和关闭问题以及任意关键错误的细节。此日志通常名为hostname.err,位于data目录中。此日志名可用 --log-error命令行选项更改。
查询日志。 它记录所有MySQL活动,在诊断问题时非常有用。此日志文件可能会很快地变得非常大,因此不应该长期使用它。此日志通常名为hostname.log,位于data目录中。此名字可以用 --log命令行选项更改
二进制日志。 它记录更新过数据(或者可能更新过数据)的所有 语句。此日志通常名为hostname-bin,位于data目录内。此名字 可以用--log-bin命令行选项更改。注意,这个日志文件是MySQL5中添加的,以前的MySQL版本中使用的是更新日志
缓慢查询日志。顾名思义,此日志记录执行缓慢的任何查询。这个日志在确定数据库何处需要优化很有用。此日志通常名为 hostname-slow.log ,位于 data 目录中。此名字可以用 --log-slow-queries命令行选项更改
在使用日志时,可用FLUSH LOGS语句来刷新和重新开始所有日志文件。
三十、改善性能
数据库管理员把他们生命中的相当一部份时间花在了调整、试验以改善DBMS性能之上。在诊断应用的滞缓现象和性能问题时,性能不良的数据库(以及数据库查询)通常是最常见的祸因。
-
MySQL(与所有DBMS一样)具有特定的硬件建议。在学习和研究MySQL时,使用任何旧的计算机作为服务器都可以。但对用于生产的服务器来说,应该坚持遵循这些硬件建议。
-
一般来说,关键的生产DBMS应该运行在自己的专用服务器上。
-
MySQL是用一系列的默认设置预先配置的,从这些设置开始通常 是很好的。但过一段时间后你可能需要调整内存分配、缓冲区大 小等。(为查看当前设置,可使用SHOW VARIABLES;和SHOW STATUS;。)
-
MySQL一个多用户多线程的DBMS,换言之,它经常同时执行多 个任务。如果这些任务中的某一个执行缓慢,则所有请求都会执行缓慢。如果你遇到显著的性能不良,可使用SHOW PROCESSLIST 显示所有活动进程(以及它们的线程ID和执行时间)。你还可以用KILL命令终结某个特定的进程(使用这个命令需要作为管理员登录)。
-
总是有不止一种方法编写同一条SELECT语句。应该试验联结、并、 子查询等,找出最佳的方法
-
使用EXPLAIN语句让MySQL解释它将如何执行一条SELECT语句。
-
一般来说,存储过程执行得比一条一条地执行其中的各条MySQL 语句快。
-
应该总是使用正确的数据类型
-
决不要检索比需求还要多的数据。换言之,不要用SELECT *(除非你真正需要每个列)
-
有的操作(包括INSERT)支持一个可选的DELAYED关键字,如果使用它,将把控制立即返回给调用程序,并且一旦有可能就实际执行该操作
-
在导入数据时,应该关闭自动提交。你可能还想删除索引(包括 FULLTEXT索引),然后在导入完成后再重建它们
-
必须索引数据库表以改善数据检索的性能。确定索引什么不是一件微不足道的任务,需要分析使用的SELECT语句以找出重复的 WHERE和ORDER BY子句。如果一个简单的WHERE子句返回结果所花的时间太长,则可以断定其中使用的列(或几个列)就是需要索引的对象。(因为没有索引就会全表扫描,就会很慢)
-
你的SELECT语句中有一系列复杂的OR条件吗?通过使用多条 SELECT语句和连接它们的UNION语句,你能看到极大的性能改进
-
索引改善数据检索的性能,但损害数据插入、删除和更新的性能。 如果你有一些表,它们收集数据且不经常被搜索,则在有必要之前不要索引它们。(索引可根据需要添加和删除。)
-
LIKE很慢。一般来说,最好是使用FULLTEXT而不是LIKE
-
数据库是不断变化的实体。一组优化良好的表一会儿后可能就面 目全非了。由于表的使用和内容的更改,理想的优化和配置也会 改变。
-
最重要的规则就是,每条规则在某些条件下都会被打破
篇幅所限,其余内容在这里: