一、SQL基础
- 数据库概念:保存有组织的数据的容器。
- 表:某种特定类型数据的结构化清单。
- 模式:关于数据库和表的布局及特性的信息。表具有一些特性,这些特性定义了数据在表中如何存储,如可以存储什么样的数据,数据如何分解,各部分信息如何命名,等等。
- 列:表中的一个字段。所有表都是由一个或多个列组成的。
- 数据类型:所容许的数据的类型。每个表列都有相应的数据类型,它限制(或容许)该列中存储的数据。
- 行:表中的一个记录。
- 主键(primary key):一列(或一组列),其值能够唯一区分表中每个行。
- 主键用来表示一个特定的行。没有主键,更新或删除表中特定行很困难,因为没有安全的方法保证只涉及相关的行。
- 主键的最好习惯:不更新主键列中的值;不重用主键列的值;不在主键列中使用可能更改的值。
- SQL(Structured Qurey Language),结构化查询语言。
二、MySQL基础
- 服务器部分是负责所有数据访问和处理的一个软件。这个软件运行在称为数据库服务器的计算机上。
- 与数据文件打交道的只有服务器软件。关于数据、数据添加、删除和数据更新的所有请求都由服务器软件完成。这些请求来自或更改来自运行客户机软件的计算机。
mysql -u root -proot -P 3306
登录root用户,密码为root,端口号为3306- 命令输入在
mysql>
之后 - 命令用
;
或\g
结束 help
或\h
获得帮助。如help select
、\h select
。- 输入
exit
或quit
退出命令行实用程序。
三、SQL语法
3.1 选择数据库。
- 必须先使用打开数据库,才能读取其中的数据。
USE database_name; -- 选择数据库,名字叫database_name
3.2 显示所有现存的数据库名称
SHOW DATABASES;
3.3 显示一个数据库内的所有表
USE bookstore;
SHOW TABLES;
3.4 显示表中列的信息
SHOW COLUMNS FROM book_list; -- 显示所有列信息,在一个表中
DESCRIBE book_list; -- 上下两个命令等价
输出,分别显示字段名、数据类型、是否允许null、键信息、默认值以及其他信息(如字段cust_id
的auto_increment
)。
+-----------+------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(100) | NO | | NULL | |
| price | decimal(10,2) unsigned | NO | | NULL | |
| author | varchar(100) | YES | | NULL | |
| sales | int(11) | YES | | NULL | |
| inventory | int(11) | YES | | NULL | |
+-----------+------------------------+------+-----+---------+----------------+
3.5 自动增量
- 某些表列需要唯一值,每个行添加到表中时,MySQL可以自动地为每个行分配下一个可用编号,不用在添加一行时手动分配唯一值。
auto_increment
3.6 显示广泛的服务器状态信息
SHOW STATUS;
四、检索数据
4.1 检索单个列
SELECT id
FROM book_list;
返回数据的顺序可能是数据被添加到表中的顺序,也可能不是。只要返回相同数目的行,就是正常的。
4.2 检索多个列
SELECT id, `name`
FROM book_list;
4.3 检索所有列
SELECT *
FROM book_list;
虽然使用通配符可能会使你自己省事,不用明确列出所需列,但检索不需要的列通常会降低检索和不用程序的性能。所以,最好别用。
4.4 检索不同的行
SELECT DISTINCT author
FROM book_list;
只显示不同的行,不显示重复的。
4.5 限制输出结果的行数
SELECT *
FROM book_list
LIMIT 5; -- 只显示前5行
SQL默认第一行为0。
SELECT *
FROM book_list
LIMIT 5, 5; -- 开始行、行数
SELECT *
FROM book_list
LIMIT 4 OFFSET 3; -- 显示4行, 从第4行开始
五、排序
5.1 升序排列
SELECT id, `name`, price
FROM book_list
ORDER BY price; -- 以价格升序排列
5.2 按多个列排序
SELECT id, `name`, price, sales
FROM book_list
ORDER BY price, sales; -- 首先按价格排序,其次按销量排序,均是升序
5.3 按降序排列
SELECT id, `name`, price, sales
FROM book_list
ORDER BY price DESC; -- DESC按降序排列,ASC按升序排列
排序中,默认不区分大小写
5.4 查找最大值
SELECT id, `name`, price, sales
FROM book_list
ORDER BY price DESC
LIMIT 1; -- 只显示第一行,所以就是找出最大值了
六、过滤
6.1 筛选出满足条件的行
SELECT id, `name`, price, sales
FROM book_list
WHERE price=20; -- 筛选出价格为20的所有商品
- 单引号用来限定字符串,如果将值与串类型的列进行比较,则需要限定引号。用来与数值列进行比较的值不需要用引号。
- 在同时使用
ORDER BY
和WHERE
子句时,应该让ORDER BY
位于WHERE
之后,否则将会产生错误。SELECT id, `name`, price, sales FROM book_list WHERE `name` REGEXP '[a-z]' ORDER BY price;
6.2 范围值检查
SELECT id, `name`, price, sales
FROM book_list
WHERE price BETWEEN 20 AND 100; -- 左闭右闭的区间
6.3 空值检查
SELECT id, `name`, price, sales
FROM book_list
WHERE sales IS NULL; -- 挑选为sales为控制的内容
6.4 组合where
子句
SELECT id, `name`, price, sales
FROM book_list
WHERE id <= 30 AND price <= 20; -- 多条件组合判断
SELECT id, `name`, price, sales
FROM book_list
WHERE id = 26 OR id = 30; -- 或判断,切记等于号用`=`
- SQL在处理
OR
操作符前,优先处理AND
操作符。因此,保险起见,如果处理多个逻辑运算符的时候,最好用()
扩起来。
6.5 IN
操作符
SELECT id, `name`, price, sales
FROM book_list
WHERE id IN (20, 30); -- id为20或30的所有行,枚举条件在括号里面
七、通配符
7.1 用通配符进行过滤
7.1.1 百分号%
通配符
%
代表给定位置的0个、1个或多个字符。
SELECT id, `name`, price, sales
FROM book_list
WHERE `name` LIKE 'java%'; -- `%`告诉MySQL接受java之后的任意字符,不管它有多少字符。
SELECT id, `name`, price, sales
FROM book_list
WHERE `name` LIKE '%java%'; -- `%`告诉MySQL接受包含“java”的任意字符,不管它有多少字符。
7.1.2 下划线_
通配符
_
匹配单个字符,不能多也不能少
SELECT id, `name`, price, sales
FROM book_list
WHERE `name` LIKE 'java_'; -- `%`告诉MySQL接受“java”后面包含有且仅有一个字符的字符串
7.1.3 使用通配符的技巧
- 通配符搜索的处理一般要比前面讨论的其他搜索所花时间更长。
- 不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。
- 在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符之于搜索模式的开始处,搜索起来是最慢的。
- 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。
八、正则表达式
- 正则表达式:是用来匹配文本的特殊的串(字符集合)。所有种类的程序设计语言、文本编辑器、操作系统等都支持正则表达式。
8.1 基本字符匹配
SELECT id, `name`, price, sales
FROM book_list
WHERE `name` REGEXP 'java'; -- 匹配该列中,包含'java'字符串的所在行
SELECT id, `name`, price, sales
FROM book_list
WHERE `name` REGEXP '.ava'; -- . 是正则表达式语言中一个特殊的字符,它匹配任意一个字符
正则表达式在MySQL中不区分大小写,如果要区分大小写,需要在匹配的字符串前面添加上BINARY
关键字
SELECT id, `name`, price, sales
FROM book_list
WHERE `name` REGEXP BINARY 'java';
8.2 进行OR
匹配
SELECT id, `name`, price, sales
FROM book_list
WHERE `name` REGEXP BINARY 'java|JAVA'; -- `|`字符表示OR运算,表示匹配包含`java`或`JAVA`的字符串
SELECT id, `name`, price, sales
FROM book_list
WHERE `name` REGEXP '[tj]'; -- 匹配字符串中包含t或j的行
SELECT id, `name`, price, sales
FROM book_list
WHERE `name` REGEXP '[a-z]'; -- 匹配字符串中包含任意字母的行
SELECT vend_name
FROM vendors
WHERE vend_name REGEXP '\\.' -- 特殊字符,需要通过转义字符`\\`表示
匹配字符类
类 | 说明 |
---|---|
[:alpha:] | 任意字符,同[a-zA-Z] |
[:lower:] | 任意小写字母,同[a-z] |
[:upper:] | 任意大写字母,同[A-Z] |
[:alnum:] | 任意字母和数字,同[a-zA-Z0-9] |
[:digit:] | 任意数字,同[0-9] |
[:xdigit:] | 任意十六进制数字,同[a-fA-F0-9] |
[:blank:] | 空格和制表,同[\\t] |
[:space:] | 包括空格在内的任意空白字符,同[\\f\\n\\r\\t\\v] |
[:cntrl:] | ASCII控制字符(ASCII 0到31和127) |
[:print:] | 任意可打印字符 |
[:graph:] | 与[:print:] 相同,但不包括空格 |
[:punct:] | 既不在[:alnum:] ,也不在[:cntrl:] 中的任意字符 |
8.3 匹配多个实例
重复元字符
元字符 | 说明 |
---|---|
* | 0个或多个匹配 |
+ | 1个或多个匹配,等于{1,} |
? | 0个或1个匹配,等于{0, 1} |
{n} | 指定数目的匹配 |
{n,} | 不少于指定数目的匹配 |
{n, m} | 匹配数目的范围(m不超过255) |
SELECT prob_name
FROM products
-- \\( 匹配 (
-- [0-9] 匹配数字
-- ? 匹配一个或0个任意字符
-- \\) 匹配 )
WHERE prob_name REGEXP '\\([0-9] sticks?\\)'
SELECT prob_name
FROM products
-- [:digit:] 匹配任意数字
-- {4} 确切地要求前面的字符出现4次
WHERE prob_name REGEXP '[[:digit:]]{4}'
ORDER BY prob_name;
8.4 定位符
元字符 | 说明 |
---|---|
^ | 文本的开始 |
$ | 文本的结束 |
[[:<:]] | 词的开始 |
[[:>:]] | 词的结束 |
SELECT prod_name
FROM products
WHERE prod_name REGEXP '^[0-9\\.]' // 匹配以数字或小数点开头的字符串
^
在[]
中表示否定该集合,取该集合的反,如[^123]
;否则用来指串的开始处
九、计算字段
9.1 计算字段
- 可在SQL语句内完成的许多转换和格式化工作都可以直接在客户机应用程序内完成。但一般来说,在数据库服务器上完成这些操作比在客户机中完成要快得多,因为DBMS是设计来快速有效地完成这些处理的。
9.2 拼接字段
SELECT CONCAT('a', 'b', 'c'); -- 结果为’abc‘
多数DBMS使用+或||来实现拼接,MySQL则使用CONCAT()
函数来实现。
9.3 清除左右侧多余的空格
SELECT RTRIM('FADFA '); -- 清除右边的空格
SELECT LTRIM(' FADFA'); -- 清除左边的空格
SELECT TRIM('FADFA'); -- 清除所有的空格
9.4 使用别名
为了更好的引用某一列,可以对某一列取一个别名
SELECT CONCAT(RTRIM(vend_name), ' (', RTRIM(vend_country), ')') AS vend_title -- AS后面就是别名
FROM vendors;
十、函数
10.1 函数
- 函数没有SQL移植性强,几乎每种主要的DBMS的实现都支持其他实现不支持的函数,而且有时差异还很大。用了一些移植性不太好的函数之后,不利于动态的指定数据库管理系统。
10.2 文本处理函数
函数 | 说明 |
---|---|
Left() | 返回串左边的字符 |
Right() | 返回串右边的字符 |
Length() | 返回串的长度 |
Lower() | 将串转换为小写 |
Upper() | 将串转换为大写 |
LTrim() | 去除串左边的字符 |
RTrim() | 去除串右边的字符 |
Locate() | 找出串的一个字串 |
Soundex() | 返回串的SOUNDEX值 |
SubString() | 返回字串的字符 |
-- 从左开始数的,截取字符串前n个
SELECT LEFT('foobarbar', 5) -- -> 'fooba'
-- 从右开始数的,截取字符串后n个
SELECT RIGHT('foobarbar', 4) -- -> 'rbar'
-- 定位子字符串在字符串中的第一个位置,从1开始。如果找不到就返回0
SELECT LOCATE('bar', 'foobarbar') ---> 4
SELECT LOCATE('xbar', 'foobar') -- -> 0
SELECT LOCATE('bar', 'foobarbar', 5) -- -> 7
-- 子字符串,从第n个开始截取
SELECT SUBSTRING('Quadratically',5) -- -> 'ratically'
-- 子字符串,从第n个开始截取
SELECT SUBSTRING('foobarbar' FROM 4) -- -> 'barbar'
-- 子字符串,从第n个开始截取m个
SELECT SUBSTRING('Quadratically',5,6) -- -> 'ratica'
-- 子字符串,从倒数第n个开始截取至字符串尾
SELECT SUBSTRING('Sakila', -3) -- -> 'ila'
-- 子字符串,从倒数第n个开始,往前截取m个
SELECT SUBSTRING('Sakila', -5, 3) -- -> 'aki'
-- 子字符串,从倒数第n个开始,往前截取m个
SELECT SUBSTRING('Sakila' FROM -4 FOR 2) ---> 'ki'
SELECT SOUNDEX('Hello') -- -> 'H400'
SELECT SOUNDEX('Quadratically') ---> 'Q36324'
-- 查找匹配所有发音类似的字符串
SELECT cust_name, cust_contact
FROM customers
WHERE Soundex(cust_contact) = Soundex('Y Lie');
10.3 日期和时间处理函数
函数 | 说明 |
---|---|
CurDate() | 返回当前日期 |
CurTime() | 返回当前时间 |
Now() | 返回当前日期和时间 |
Date() | 返回日期时间的日期部分 |
Time() | 返回日期时间的时间部分 |
Year() | 返回日期时间的年份部分 |
Month() | 返回日期时间的月份部分 |
Day() | 返回日期时间的天数部分 |
Hour() | 返回日期时间的小时部分 |
Minute() | 返回日期时间的分钟部分 |
Second() | 返回日期时间的秒部分 |
AddDate() | 增加一个日期(天、周等) |
AddTime() | 增加一个时间(时、分等) |
DateDiff() | 返回两个日期之差 |
Date_Add() | 高度灵活的日期运算函数 |
Date_Format() | 返回一个格式化之后的日期或时间串 |
DayOfWeek() | 对于一个日期,返回对应的星期几 |
SELECT NOW() -- -> '2007-12-15 23:50:26'
SELECT NOW() + 0 -- -> 20071215235026.000000
SELECT ADDDATE('2008-01-02', 31) -- -> '2008-02-02'
SELECT ADDTIME('2007-12-31 23:59:59.999999', '1 1:1:1.000002') -- -> '2008-01-02 01:01:01.000001'
SELECT ADDTIME('01:00:00.999999', '02:00:00.999998') -- -> '03:00:01.999997'
SELECT DATEDIFF('2007-12-31 23:59:59','2007-12-30') -- -> 1
SELECT DATEDIFF('2010-11-30 23:59:59','2010-12-31') ---> -31
SELECT DAYOFWEEK('2007-02-03') -- -> 7
- 不管是插入或更新表值还是用
WHERE
子句进行过滤,日期必须为yyyy-mm-dd
格式。 - 但是,使用
WHERE order_date = '2005-09-01'
不可靠。因为order_time
的数据类型为datatime
,这种类型存储日期及时间值。默认是时间值为00:00:00
。因此,通过WHERE
筛选日期的时候,容易因为时间对不上而无法筛选出来。更安全的做法是:
SELECT cust_id, order_num
FROM orders
WHERE Date(order_date) = '2005-09-01';
- 如果要的是日期,请使用
Date()
,这是一个良好的习惯。
-- 筛选某个月份的行
SELECT cust_id, order_num
FROM orders
WHERE Date(order_date) BETWEEN '2005-09-01' AND '2005-09-30';
SELECT cust_id, order_num
FROM orders
WHERE Year(order_date) = 2005 AND Month(order_date) = 9;
10.4 数值处理函数
函数 | 说明 |
---|---|
Abs() | 返回绝对值 |
Sin() | 返回一个角度的正弦(弧度制) |
Cos() | 返回一个角度的余弦 |
Tan() | 返回一个角度的正切 |
Exp() | 返回一个角度的指数值 |
Mod() | 返回除操作的余数 |
Sqrt() | 返回一个数的平方根 |
Pi() | 返回圆周率 |
Rand() | 返回一个随机数 |
10.5 聚集函数
函数 | 说明 |
---|---|
COUNT() | 返回某列的行数 |
MAX() | 返回某列的最大值 |
MIN() | 返回某列的最小值 |
AVG() | 返回某列的平均值 |
SUM() | 返回某列值之和 |
- 当计算的时候,忽略值为
null
的行。 - 比如使用
COUNT()
函数时,如果使用COUNT(*)
,会对所有行进行计数,但是当使用COUNT(COLUMNS_NAME)
时,如果该列的某行为null
,则会被直接忽略。 - 使用
AVG()
函数时,如果某行为null
,则不会纳入计算的范围,既不占个数,也不对值产生影响,会被直接忽略。 DISTINCT
只计算包含不同的值。
SELECT COUNT(DISTINCT price)
FROM book_list;
SELECT AVG(DISTINCT price)
FROM book_list;
- 这些函数都是高效设计,它们返回结果的速度一般比客户机应用程序快得多。
十一、数据分组
11.1 分组计数
- 计算相同价格的有多少个。
SELECT price, COUNT(price)
FROM book_list
GROUP BY price;
GROUP BY
子句必须出现在WHERE
子句之后,ORDER BY
子句之前。即,先筛选,后分组,最后排序。
SELECT price, COUNT(price)
FROM book_list
GROUP BY price
ORDER BY price;
11.2 过滤分组
SELECT price, COUNT(price)
FROM book_list
GROUP BY price
HAVING COUNT(price) > 8;
HAVING
支持所有WHERE
操作符。所学到的有关WHERE
的所有这些技术和选项都适用于HAVING
。它们句式相同,只是关键字有差别。- 另一种理解方式,
WHERE
在数据分组前进行过滤,HAVING
在数据分组后进行过滤。这是一个重要的区别,WHERE
排除的行不包括在分组中。这可能会改变计算值,从而影响HAVING
子句中基于这些值过滤掉的分组。 - 一般在使用
GROUP BY
子句时,应该也给出ORDER BY
子句,这是保证数据正确排序的唯一办法。千万不要仅仅依赖GROUP BY
排序数据。 - 至今为止,
SELECT
语句中子句的顺序。
|子句|说明|是否必须使用|
|–|--|–|
|SELECT
|要返回的列或表达式|是|
|FROM
|从中检索数据的表|仅在从表选择数据时使用|
|WHERE
|行级过滤|否|
|GROUP BY
|分组说明|仅在按组计算聚集时使用|
|HAVING
|组级过滤|否|
|ORDER BY
|输出排序顺序|否|
|LIMIT
|要检索的行数|否|
十二、子查询
12.1 子查询
- 即嵌套在其他查询中的查询。
12.2 应用场景
- 两个表,一个表存储用户的信息,另一个表存储订单信息。两个表之间的联系通过用户ID进行。当需要查询指定订单中的用户ID对应的用户的信息,就需要通过子查询。
-- 查询所有购买`TN2`的用户的姓名
SELECT user_name
FROM user_info
WHERE id in (
SELECT id
FROM orders
WHERE prod_id = 'TN2'
);
- 子查询总是从内向外处理的,首先处理
in
括号里面的查询,然后处理外面的查询。 - 在
WHERE
子句中使用子查询能够编写出功能很强并且很灵活的SQL语句。但在实际使用时,由于性能的限制,不能嵌套太多的子查询 - 相关子查询:涉及外部查询的子查询。需要使用完全限定列名
WHERE orders.cust_id = customers.cust_id;
十三、联结
13.1 联结
- SQL最强大的功能之一,就是能在数据检索查询的执行中联结(
join
)表。 - 相同数据出现多次决不是一件好事,此因素是关系数据库设计的基础。关系表的设计就是要保证把信息分解成多个表,一类数据一个表。各表通过某些常用的值(即关系设计中的关系)互相关联。
- 一个表中每行有一个唯一的标识,此标识称为主键(primary key)。另一个表通过该标识与上一个表进行联结,在此表中称该标识为外键。
13.2 外键(foreign key)
- 外键为某表中的一列,它包含另一个主键值,定义了两个表之间的关系。
CREATE TABLE departments(
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(20) NOT NULL
);
CREATE TABLE employees (
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20) NOT NULL,
gender VARCHAR(10),
email VARCHAR(30),
dept_id INT,
CONSTRAINT fk_emp_dept -- 外键
FOREIGN KEY(dept_id) REFERENCES departments(dept_id)
);
13.3 创建联结
SELECT vend_name, prod_name, prod_price
FROM vendors, products
WHERE vendors.vend_id = products.vend_id
ORDER BY vend_name, prod_name;
- 这条语句的
FROM
子句列出了两个表,它们就是这条SELECT
语句联结的两个表的名字。两个表用WHERE
子句正常联结。 WHERE
作为联结之后的过滤条件,如果不使用WHERE
的话,就将第一个表的每个行与第二个表的每个行以组合的形式,一一出现。(笛卡尔积)- 笛卡尔积:由没有联结条件的表关系返回的结果为笛卡尔积,检索出的行的数目将是第一个表中的行数乘以第二个表中的行数。
13.4 内部联结
SELECT vend_name, prod_name, prod_price
FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id;
- 通过
INNER JOIN...ON...
的方法联结。 - ANSI SQL规范首选
INNER JOIN
语法。
13.5 等值联结
- 以上两种方法的效果完全一致,其实就是取两个表中由相同值的部分,没有相同值的部分就直接舍弃。(也就是交集)
13.6 联结多个表
SELECT prod_name, vend_name, prod_price, quantity
FROM orderitems, products, vendors
WHERE products.vend_id = vendors.vend_id
AND orderitems.prod_id = products.prod_id
AND order_name = 20005;
13.7 自联结
- 即自己与自己联结。通常用在,某行数据具有某个属性,需要在表中查找所有具有相同属性的数据。
- 可以用子查询实现,也可以通过自联结实现。
-- 子查询的方式
SELECT prod_id, prod_name
FROM products
WHERE vend_id = (SELECT vend_id
FROM products
WHERE prod_id = 'DTNTR'
);
-- 自联结的方式
SELECT p1.prod_id, p1.prod_name
FROM products AS p1, products AS p2
WHERE p1.vend_id = p2.vend_id
AND p2.prod_id = 'DTNDR';
- 用自联结而不用子查询:自联结通常作为外部语句用来代替从从相同表中检索数据时使用的子查询语句。虽然最终结果相同,但有时候处理联结远比处理子查询快得多。应该试一下两种方法,以确定哪一种的性能更好。
13.8 自然联结
- 应该至少有一个列出现在不止一个表中,标准的联结返回所有数据,甚至有相同的列多次出现,自然联结排除多次出现,使每个列只返回一次。
- 实现方式:一般通过对表使用通配符(
SELECT *
),对其他表的列使用明确的子集来完成。
SELECT c.*, o.order_num, o.order_date, oi.prod_id, oi.quantity, oi.item_price
FROM customers AS c, orders AS o, orderitems AS oi
WHERE c.cust_id = o.cust_id
AND oi.order_num = o.order_num
AND prod_id = 'FB';
13.9 外部联结
- 许多联结将一个表中的行与另一个表中的行相关联,但有时候会需要包含没有关联的那些行。(例如,一些顾客没有订单,但是在顾客名单里面存在,也需要显示出来)联结包含那些在相关表中没有关联行的行,这种类型的联结称为外部联结。
SELECT customers.cust_id, orders.order_num
FROM customers LEFT OUTER JOIN orders
ON customers.cust_id = orders.cust_id;
- 在使用
OUTER JOIN
的时候,必须用RIGHT
或LEFT
关键字指定包括其所有行的表。RIGHT
指出的是OUTER JOIN
右边的表,LEFT
指出的是左边的表。两种类型实际上效果是一样的,只是顺序不一样而已。 - 外部联结就是取
RIGHT
/LEFT
的所有与另一边的交集进行合并。另一边不存在的部分,就用null
来表示。
13.9 使用带聚集函数的联结
SELECT customers.cust_name, customers.cust_id, COUNT(orders.order_name) AS num_ord
FROM customers INNER JOIN orders
ON customers.cust_id = orders.cust_id
GROUP BY customers.cust_id;
十四、组合查询
- 组合查询:执行多次查询之后,通过并(
UNION
),实现多个查询结果的合并。
SELECT vend_id, prod_id, prod_price
FROM products
WHERE prod_price <= 5
UNION -- 组合的关键字
SELECT vend_id, prod_id, prod_price
FROM products
WHERE vend_id IN (1001, 1002);
14.1 UNION
规则:
- 必须由两条或两条以上的
SELECT
语句组成,语句之间用关键字UNION
分隔。 UNION
中的每个查询必须包含相同的列、表达式或聚集函数。(不过各个列之间不需要以相同的顺序出现。)- 列数据必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型。
- 在
UNION
中,重复的行被自动取消。如果不想去除重复行,那就使用UNION ALL
- 对组合查询结果进行排序:在最后添加上
ORDER BY
语句即可。
十五、全文本搜索
15.1 全文本搜索
- 并非所有的搜索引擎都支持全文本搜索。最常用的两个搜索引擎:MyISAM支持全文本搜索和InnoDB不支持全文本搜索。
- 为了进行全文本搜索,必须索引被索引的列,而且要随着数据的改变不断地重新索引。在对表列进行适当设计之后,MySQL会自动进行所有索引和重新索引。
15.2 创建表时启动全文本搜索
CREATE TABLE productnotes
(
note_id int NOT NULL AUTO_INCREMENT,
prod_id char(10) NOT NULL,
note_date datetime NOT NULL,
note_text text NULL,
PRIMARY KEY(note_id), -- 指定该表的主键
FULLTEXT(note_id) -- 全文本搜索索引
) ENGINE=MyISAM; -- 指定搜索引擎
- 在定义之后,MySQL自动维护该索引。在增加、更改、删除行时,索引随之自动更新。
- 不要在导入数据之前,开启全文本搜索。应该在导完数据之后,再开启全文本搜索。因为每导一次数据,就需要更新一次索引,分别更新每条数据索引的时间会大于更新所有数据索引的时间。
15.2 进行全文本搜索
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('rabbit'); -- 在note_text列搜索所有包含'rabbit'的列
- 搜索将不区分大小写。
- 全文本搜索返回以文本匹配的良好程度排序的数据。如果两个行都包含’rabbit’,那么第三个词出现’rabbit’的优先级会比第二十个词出现’rabbit’的优先级。
15.3 进行查询拓展
- 查询拓展用来设法放宽所返回的全文本搜索结果的范围。
- 首先,进行一个基本的全文本搜索,找出与搜索条件匹配的所有行。
- 其次,MySQL检查这些匹配行并选择所有有用的词。
- 再其次,MySQL再次进行全文本搜索,这次不仅使用原来的条件,而且还使用所有有用的词。
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('anvils' WITH QUERY EXPANSION);
15.4 布尔文本搜索
- 以布尔方式,可以提供关于以下内容的细节:
- 要匹配的词
- 要排斥的词
- 排列提示(指定某些词比其他词重要,更重要的词等级更高)
- 表达式分组
- 另外一些内容
- 即使没有
FULLTEXT
索引也可以使用,但这是一种非常缓慢的操作。
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('heavy' IN BOOLEAN MODE); -- 使用布尔文本搜索
SELECT note_text
FROM productnotes
WHERE Match(note_text) Aganist('heavy -rope*' IN BOOLEAN MODE); -- 匹配'heavy' 但不匹配包含'rope'开头的词的句子
布尔操作符 | 说明 |
---|---|
+ | 包含,词必须存在 |
- | 排除,词必须不出现 |
> | 包含,而且增加等级值 |
< | 包含,且减少等级值 |
() | 把词组成子表达式(允许这些子表达式作为一个组被包含、排除、排列等) |
~ | 取消一个词的排序值 |
* | 词尾的通配符 |
“” | 定义一个短语(与单个词的列表不一样,它匹配整个短语以便包含或排除这个短语) |
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('+rabbit +bait' IN BOOLEAN MODE); -- 包含词'rabbit'和'bait'的行(必须都有)
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('rabbit bait' IN BOOLEAN MODE); -- 包含词'rabbit'或'bait'的行(至少有一个)
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('"rabbit bait"' IN BOOLEAN MODE) -- 包含搜索匹配短语'rabbit bait'而不是匹配两个词'rabbit'和'bait'
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('>rabbit <carrot' IN BOOLEAN MODE) -- 包含rabbit或carrot,且增大rabbit的等级,减小carrot的等级
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('+safe +(<combination)' IN BOOLEAN MODE) -- 必须包含两个词,且降低'combination'的等级
十六、插入数据
16.1 插入数据
INSERT INTO customers
VALUES(NULL, 'Pep', '100', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL);
- 你不想给出一个值,但又不能忽略某列,那就可以给出
NULL
值。虽然这种语法很简单,但是并不安全,应该尽量避免使用。上面SQL高度依赖于表中列的定义次序,并且还依赖于其次序容易获得的信息,也不能保证下一次表结构变动后各个列保持完全相同的次序。 - 更安全的方法如下:
INSERT INTO customers(cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_eamil)
VALUES('Pep', '100', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL);
- 插入行时,MySQL中的第一个值对应于第一个指定的列名。第二个值对应于第二个列名,如此。
- 一般不要使用没有明确给出列的列表的
INSERT
语句,使用列的列表能使SQL代码继续发挥作用,即使表结构发生了变化。 - 如果对表中不允许NULL值且没有默认值的列不给出值,则MySQL将产生一条错误信息,并且相应的行插入不成功。
- 如果数据检索是最重要的,则可以通过在
INSERT
和INTO
之间添加关键字LOW_PRIORITY
,指示MySQL降低INSERT
语句的优先级(也适用于UPDATE
和DELETE
):
INSERT LOW_PRIORITY INTO
16.2 插入多行
- 单条
INSERT
处理多个插入比使用多条INSERT
语句快。
INSERT INTO customers(cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_eamil)
VALUES('Pep', '100', 'Los Angeles', 'CA', '90046', 'USA', NULL, NULL),
('M', '42', 'New York', 'NY', '11213', 'USA');
16.3 插入查询出来的结果
- 即将
SELECT
出来的结果插入表中
INSERT INTO customers(cust_id, cust_contact, cust_email, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country)
SELECT cust_id, cust_contact, cust_email, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country
FROM custnew;
INSERT SELECT
通过列位置的一一对应关系进行插入,列名不一定要一样。
十七、更新数据
17.1 UPDATE
组成部分
- 要更新的表
- 列名和它们的新值
- 确定要更新行的过滤条件
UPDATE customers
SET cust_email = 'elmer@fudd.com' -- 设置新值
WHERE cust_id = 1005; -- 筛选条件
UPDATE customers
SET cust_name = 'The Fudds', -- 设置多列
cust_email = 'elmer@fudd.com'
WHERE cust_id = 1005; -- 筛选条件
17.2 IGNORE
关键字
- 如果用
UPDATE
语句更新多行时一行或多行出现错误,也继续进行更新,可以使用IGNORE
关键字。
UPDATE IGNORE customers...
17.3 删除某列的某个值
- 可设置它为
NULL
UPDATE customers
SET cust_email = NULL -- 去除某列的值
WHERE cust_id = 10005;
十八、删除数据
18.1 删除某行
DELETE FROM customers
WHERE cust_id = 10006;
18.2 删除表中的所有行
DELETE FROM customers;
- 速度更快的方法,此方法直接删除整个表再创建一个新的表,且这个方法不能回滚:
TRUNCATE TABLE customers;
18.3 使用UPDATE
和DELETE
时所遵循的习惯:
- 除非确实打算更新或删除每一行,否则千万不要不带
WHERE
- 保证每个表都有主键,
WHERE
子句使用主键进行筛选 - 在
UPDATE
和DELETE
之前,使用SELECT
子句进行测试,保证筛选的数据是正确的。
十九、创建表和操纵表
19.1 创建表
- 每列之间用逗号间隔,每列的定义以列名开始,后面跟列的数据类型。
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;
- 列定义用了恰当的缩进,以便阅读和编辑。
- 在创建新表时,指定的表名必须不存在,否则将出错。为了防止出错,可以先删除,再创建。
DROP TABLE IF EXISTS customers;
- 可以在不存在表时才创建
CREATE TABLE customers IF NOT EXISTS
(
...
);
19.2 使用NULL
值
- 每个表列或者是
NULL
列,或者是NOT NULL
列,这种状态在创建时由表的定义规定。使用NOT NULL
的列不接受该列没有值的列,换句话说,在插入或更新行时,该列必须有值。 NULL
为默认设置,如果不指定NOT NULL
,则认为指定的是NULL
。
19.3 使用主键
- 主键值必须唯一。即,表中的每个行必须具有唯一的主键值。如果主键使用单个列,则它的值必须唯一。如果使用多个列,则这些列的组合值必须唯一。
PRIMARY KEY (vend_id)
PRIMARY KEY (order_num, order_item)
19.4 使用AUTO_INCREMENT
AUTO_INCREMENT
告诉MySQL,本列每当增加一行时,自动增量。每个表只允许一个AUTO_INCREMENT
列,而且它必须被索引。- 如果一个列被指定为
AUTO_INCREMENT
,则它可以简单地INSERT
指定一个值,只要它是唯一的。后续的增量将开始使用该手工插入的值。 - 下一个
AUTO_INCREMENT
的值
SELECT LAST_INSERT_ID();
19.5 指定默认值
CREATE TABLE orderitems
(
order_num int NOT NULL,
order_item int NOT NULL,
prod_id char(10) NOT NULL,
quantity int NOT NULL DEFAULT 1, -- 默认值为1
item_price decimal(8,2) NOT NULL,
PRIMARY KEY (order_num, order_item)
) ENGINE=InnoDB;
- 与大多数DBMS一样,MySQL不允许使用函数作为默认值,它只支持常量。
- 许多数据库开发人员使用默认值而不是
NULL
列,特别是用于计算或数据分组的列更是如此。
19.6 引擎类型
- InnoDB:是一个可靠的事务处理引擎,它不支持全文本搜索。
- MEMORY:功能等同于MyISAM,但由于数据存储在内存(不是磁盘),速度很快(特别适用于临时表)
- MyISAM是一个性能极高的引擎,它支持全文本搜索,但不支事务处理。
19.7 更新表
- 在理想状态下,当表中存储数据之后,该表就不应该再被更新。在表的设计过程中,需要花费大量时间来考虑,以便后期不对该表进行大的改动。
- 添加列
ALTER TABLE vendors
ADD vend_phone CHAR(20); --添加列
- 删除列
ALTER TABLE vendors
DROP COLUMN vend_phone; -- 删除列
- 定义外键
ALTER TABLE orderitems
ADD CONSTRAINT fk_orderitems_orders
FOREIGN KEY (order_num) REFERENCES orders (order_num);
复杂的表结构一般需要手动删除过程,它涉及以下步骤:
- 用新的列布局创建一个新表
- 使用
INSERT SELECT
语句从旧表复制数据到新表。 - 检验包含所需数据的新表
- 重命名/删除旧表
- 用旧表名字重命名新表
- 根据需要,重新创建触发器、存储过程、索引和外键。
19.8 删除表
DROP TABLE customers2;
19.9 重命名表
RENAME TABLE customers2 to customers;
RENAME TABLE customers2 to customers,
customers3 to customers2,
customers4 to customers3;
二十、使用视图
20.1 视图
- 是虚拟的表,与包含数据的表不一样,视图只包含使用时动态检索数据的查询。
20.2 视图常见应用
- 重用SQL语句
- 简化复杂的SQL操作。在编写查询后,可以方便地重用它而不必知道它的基本查询细节
- 使用表的组成部分而不是整个表
- 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限
- 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据
20.3 视图本身不包含数据
- 因此它们返回的数据是从其他表中检索出来的。在添加或更改这些表中的数据时,视图将返回改变过的数据。
20.4 视图的规则与限制
- 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造一个视图
ORDER BY
可以用在视图中,但如果从该视图检索数据的SELECT
语句中也含有ORDER BY
,那么该视图中的ORDER BY
将被覆盖- 视图不能索引,也不能有关联的触发器或默认值
- 视图可以和表一起使用。例如,编写一条联结表和视图的
SELECT
语句
20.5 使用视图
- 视图用
CREATE VIEW
语句来创建 - 使用
SHOW CREATE VIEW viewname
来查看创建视图的语句 DROP VIEW viewname
删除视图- 更新视图时,可以使用
DROP
再用CREATE
,也可以直接用CREATE OR REPLACE VIEW
CREATE VIEW productcustomers AS -- 创建视图
SELECT cust_name, cust_contact, prod_id
FROM customers, orders, orderitems
WHERE customers.cust_id = orders.cust_id --联结三个表
AND orderitems.order_num = orders.order_num;
- 使用
view
SELECT cust_name, cust_contact -- 列
FROM productcustomers -- VIEW
WHERE prod_id = 'TNT2'; -- 筛选条件
利用视图,可一次性编写基础的SQL,然后根据需要多次使用。
20.6 格式化检索出来的数据
CREATE VIEW vendorlocations AS -- 创建视图
SELECT Concat(RTrim(vend_name), '(', RTrim(vend_country), ')') -- 格式化检索出来的数据
FROM vendors
ORDER BY vend_name;
SELECT * -- 调用视图
FROM vendorlocations
20.7 过滤不想要的数据
CREATE VIEW customeremaillist AS
SELECT cust_id, cust_name, cust_email
FROM customers
WHERE cust_email IS NOT NULL;
20.8 使用计算字段
CREATE VIEW orderitemsexpanded AS
SELECT order_num, prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM orderitems;
20.9 更新视图
- 视图是可更新的,可以对它们使用
INSERT
、UPDATE
、DELETE
操作。如果对视图增加或删除行,实际上是对其基表的增加或删除行。 - 如果视图包含以下操作,则不能进行更新:分组、联结、子查询、并、聚集函数(
Min()
、Count()
、Sum()
等) DISTINCE
- 导出列
一般,应该将视图用于检索,而不用于更新
二十一、存储过程
21.1 存储过程
- 就是为以后的使用而保存的一条或多条MySQL语句的集合。可将其视为批文件,虽然它们的作用不限于批处理
21.2 使用存储过程的理由
- 通过把处理封装在容易使用的单元,简化复杂的操作
- 所有开发人员使用同一个存储过程,防止错误
- 简化对变动的管理。如果表名、列名或业务逻辑有变化,只需要更改存储过程的代码,使用它的人员甚至不需要知道这些变化(封装性)
- 提高性能。使用存储过程比使用单独的SQL语句要快
21.3 创建存储过程
CREATE PROCEDURE productpricing() -- 定义存储过程,如果接受参数,则在括号里面定义
BEGIN -- 限制过程体
SELECT Avg(prod_price) AS priceaverage
FROM products;
END;
- 但是,此操作会发生错误,因为
;
作为语句分隔符,内部也有;
,会造成过程体提前结束的误会。因此,需要临时修改语句分隔符。
DELIMITER // -- 临时修改分隔符为'//'
CREATE PROCEDURE productpricing()
BEGIN
SELECT Avg(prod_price) AS priceaverage
FROM products;
END //
DELIMITER ; -- 将分隔符修改回来
21.4 执行存储过程
CALL productpricing();
21.5 删除存储过程
DROP PROCEDURE productpricing;
DROP PROCEDURE IF EXISTS productpricing; -- 如果存在则删除
21.6 使用参数
CREATE PROCEDURE productpricing(
OUT pl DECIMAL(8,2), -- 输出变量
OUT ph DECIMAL(8,2),
OUT pa DECIMAL(8,2)
)
BEGIN
SELECT Min(prod_price)
INTO pl -- 将查询结果保存在pl中
FROM products;
SELECT Max(prod_price)
INTO ph
FROM products;
SELECT Avg(prod_price)
INTO pa
FROM products;
END;
- MySQL支持
IN
(传递给存储过程)、OUT
(从存储过程传出)和INOUT
(对存储过程传入和传出)类型的参数。
CALL productpricing(@pricelow, -- MySQL所有变量都必须以@开始
@pricehigh,
@priceage
);
SELECT @pricelow; -- 显示检索结果
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;
CALL ordertotal(20005. @total); -- 调用存储过程
SELECT @total; -- 查看结果
21.7 建立智能存储过程
CREATE PROCEDURE ordertotal(
IN onumber INT,
IN taxable BOOLEAN,
OUT ototal DECIMAL(8,2)
) COMMENT 'Obtain order total, optionally adding tax' -- 将在`SHOW PROCEDURE STATUS`上显示这句话
BEGIN
DECLARE total DECIMAL(8,2); -- 声明总量的变量
DECLARE taxrate INT DEFAULT 6; -- 声明税率百分比
SELECT Sum(item_price*quantity)
FROM orderitems
WHERE order_num = onumber
INTO total; -- 将计算出来的总量,存储在total变量中
IF taxable THEN -- 是否应税的
SELECT total+(total/100*taxrate) INTO total;
END IF;
SELECT total INTO ototal; -- 将结果保存在ototal上
END;
调用:
CALL ordertotal(20005, 0, @total); -- 调用存储过程
SELECT @total; -- 显示结果
21.8 检查存储过程
SHOW CREATE PROCEDURE ordertotal; -- 显示用来创建一个存储过程的CREATE语句
SHOW PROCEDURE STATUS; -- 将显示现存在的所有存储过程的信息,包括何时、由谁创建等详细信息
SHOW PROCEDURE STATUS LIKE 'search_user'; -- 通过通配符进行筛选
二十二、使用游标
22.1 游标
- 游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改。
22.2 使用游标
CREATE PROCEDURE processorders()
BEGIN
DECLARE ordernumbers CURSOR -- 声明游标
FOR
SELECT order_num FROM orders;
OPEN ordernumbers; -- 开启游标
CLOSE ordernumbers; -- 关闭游标
END;
22.4 使用游标数据
- 使用
FETCH
来检索当前行的数据(默认从第一行开始)
DECLARE o INT; -- 声明变量
OPEN ordernumbers; -- 开启游标
FETCH ordernumbers INTO o; -- 将当前行的数据获取并保存在变量o中
CLOSE ordernumbers; -- 关闭游标
- 循环检索
CREATE PROCEDURE processorders()
BEGIN
DECLARE done BOOLEAN DEFAULT 0; -- 声明局部变量,完成标志位
DECLARE o INT; -- 局部变量
DECLARE ordernumbers CURSOR -- 声明游标
FOR
SELECT order_num FROM orders;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1; -- 在满足条件的时候(SQLSTATE = '02000'的时候,设置done为1)
OPEN ordernumbers; -- 开启游标
-- 开启循环
REPEAT
FETCH ordernumbers INTO o; -- 将数据保存在局部变量
UNTIL done END REPEAT; -- 关闭循环条件(done=1)
CLOSE ordernumbers; -- 关闭游标
END;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
这句话定义了一个CONTINUE HANDLER
,它在条件出现时被执行的语句。这里,它指出当SQLSTATE '02000'
出现时,SET done=1
二十三、使用触发器
- 如果想要在某条语句(或某些语句)在事件发生时自动执行,那就需要使用触发器。
- 触发器在指定表发生更改的时候触发,针对的操作有
INSERT
、UPDATE
、DELETE
,在事件发生之前、之后都可以设置触发器。 - 创建触发器,需要给出4条信息
- 唯一的触发器名
- 触发器关联的表
- 触发器应该响应的活动(
INSERT
、UPDATE
、DELETE
) - 触发器何时执行(处理之前或之后)
23.1 创建触发器
CREATE TRIGGER newproduct AFTER INSERT ON products -- 触发器名为'newproduct',在触发之后执行,关联的表为'products'
FOR EACH ROW SELECT 'Product added'; -- 在每一行发生改变的时候,输出'Product added'
23.2 触发器仅支持表,不支持视图、临时表
23.3 删除触发器
DROP TRIGGER newproduct;
23.4 INSERT
触发器
- 在
INSERT
触发器代码内,可引用一个名为NEW
的虚拟表,访问被插入的行 - 在
BEFORE INSERT
触发器中,NEW
中的值也可以被更新(允许更新被插入的值,实现提前格式化插入数据) - 对于
AUTO_INCREMENT
列,NEW
在INSERT
执行之前包含0,在INSERT
执行之后包含新的自动生成值。CREATE TRIGGER neworder AFTER INSERT ON orders FOR EACH ROW SELECT NEW.order_num; -- 查询自动生成的值order_num,在insert之后
23.5 DELETE
触发器
- 在
DELETE
触发器代码内,可以引用一个名为OLD
的虚拟表,访问被删除的行。 OLD
中的值都是只读的,不能更新。
-- 使用OLD保存将要被删除的行到一个存档表中
CREATE TRIGGER deleteorder BEFORE DELETE ON orders -- 在删除之前
FOR EACH ROW
BEGIN
INSERT INTO achieve_orders(order_num, order_datae, cust_id) -- 将旧值保存在一个叫做'achieve_orders'的表中
VALUES(OLD.order_num, OLD.order_date, OLD.cust_id);
END;
使用BEFORE DELETE
而不使用AFTER DELETE
的优点是,防止由于某种原因,造成订单不能存档。
23.6 UPDATE
触发器
- 在
UPDATE
触发器代码中,可以引用NEW
访问新更新的值,也可以引用OLD
访问以前的值。 - 在
BEFORE UPDATE
触发器中,NEW
中的值可能也被更新(允许更新将要用于UPDATE
的值) OLD
的值全都是只读的,不能更新
CREATE TRIGGER updatevendor BEFORE UPDATE ON vendors
FOR EACH ROW SET NEW.vend_state = UPPER(NEW.vend_state); -- 在更新之前,将插入的内容全部变成大写
二十四、管理事务处理
24.1 事务处理
- 用来维护数据库的完整性,它保证成批的MySQL操作要么完全执行,要么完全不执行。
24.2 支持事务处理的引擎
- InnoDB
24.3 事务(transaction)
- 指一组SQL语句
24.4 回退(rollback)
- 指撤销指定SQL语句的过程
24.5 提交(commit)
- 指将未存储的SQL语句结果写入数据库表
24.6 保留点(savepoint)
- 指事务处理中设置的临时占位符(place-holder),可以对它发布回退
24.7 使用ROLLBACK
SELECT * FROM ordertotals;
START TRANSACTION; -- 开始事务
DELETE FROM ordertotals; -- 清空表
SELECT * FROM ordertotals; -- 查询表为空
ROLLBACK; -- 回滚
SELECT * FROM ordertotals; -- 发现表并不为空
ROLLBACK
可以实现撤销的操作,回滚到事务开启之前的状态。- 事务处理用来管理
INSERT
、DELETE
、UPDATE
(增删改)操作。不能回退CREATE
、DROP
操作。
24.8 使用COMMIT
START TRANSACTION;
DELETE FROM orderitems WHERE order_num = 20010;
DELETE FROM orders WHERE order_num = 20010;
COMMIT; -- 提交事务。实现仅当事务内都不出错的时候,才提交事务
24.9 使用保留点
为了实现部分回退。通过保留点,可以回退到某个占位符。
SAVEPOINT delete1; -- 保留点
ROLLBACK TO delete1; -- 回滚到指定保留点
- 保留点越多越好,保留点越多,越灵活
- 保留点在事务处理完成(执行一条
ROLLBACK
或COMMIT
)后自动释放。
24.10 更改默认的提交行为
- 默认MySQL行为是自动提交所有更改。为指示MySQL不自动提交更改,需要使用以下语句:
SET autocommit=0;
-- 开启事务
SET autocommit=0; -- 关闭自动提交
START TRANSACTION; -- 可省略
USE girls;
-- 事务
UPDATE boys
SET userCP=2000
WHERE id=7;
UPDATE boys
SET boyName='郭富城'
WHERE id=6;
COMMIT; -- 提交
-- rollback; -- 返回,则执行事务
二十五、全球化和本地化
25.1 字符集
- 为字母、符号的集合
25.2 编码
- 为某个字符集成员的内部表示
25.3 校对
- 为规定字符如何比较的指令
25.4 显示所有可用的字符集及每个字符集的描述和默认校对
SHOW CHARACTER SET;
25.5 查看所支持校对的完整列表
SHOW COLLATION;
25.6 指定字符集和校对
CREATE TABLE mytable
(
column1 INT,
column2 VARCHAR(10)
) DEFAULT CHARACTER SET hebrew, -- 设置字符集
COLLATE hebrew_general_ci; -- 设置校对
二十六、安全管理
26.1 安全基础
- 用户应该对他们需要的数据具有适当的访问权,既不能多,也不能少。例子:
- 多数用户只需要对表读和写,但少数用户需要创建和删除表
- 某些用户需要读表,但可能不需要更新表
- 允许某些用户能添加数据,但是不能删除数据
- 某些用户(管理员)需要处理用户账号的权限,但多数用户不需要
- 让用户通过存储过程访问数据,但不允许他们直接访问数据
- 根据用户登录的地点限制对某些功能的访问
26.2 管理用户
USE mysql;
SELECT user FROM user; -- 获得所有用户账号列表
- 用户账号和信息都存储在名为mysql的MySQL数据库中
26.3 创建用户账号
CREATE USER ben IDENTIFIED BY 'ben'; -- 创建一个新用户,名为ben,密码为ben
IDENTIFIED BY
将对指定的口令进行加密
26.4 重命名账号
RENAME USER ben TO ben1;
26.5 删除用户账号
DROP USER ben1;
26.6 设置访问权限
在创建用户账号之后,必须接着分配访问权限。新创建的用户账号没有访问权限。它们能登录MySQL,但不能看到数据,不能执行任何数据库操作。
查看用户账号的权限
SHOW GRANTS FOR ben;
设置权限,至少给出以下信息:
– 要授予的权限
– 要授予访问权限的数据库或表
– 用户名
GRANT SELECT ON crashcourse.* TO ben; -- 将crashcourse的SELECT权赋予给ben用户。即ben用户具有crashcourse数据库所有表的只读权利
REVOKE SELECT ON crashcourse.* FROM ben; -- 将取消赋予ben用户对于crashcourse数据库的只读权利
GRANT ALL ON bookstore.* TO ben; -- 将赋予ben在bookstore数据库上所有权利
REVOKE ALL ON bookstore.* FROM ben;
GRANT SELECT, INSERT ON bookstore.* TO ben; -- 多个权限用逗号分隔
26.7 更改口令
SET PASSWORD FOR ben = Password('ben'); -- 更改密码
SET PASSWORD = Password('ben'); -- 设置当前用户的登录口令
二十七、数据库维护
27.1 备份数据
- 使用命令行实用程序
mysqldump
转储所有数据库内容到某个外部文件 - 可用命令行实用程序
mysqlhotcopy
从一个数据库复制所有数据 - 使用MySQL的
BACKUP TABLE
或SELECT INTO OUTFILE
转储所有数据到某个外部文件
27.2 进行数据库维护
ANALYZE TABLE orders; -- 用来检查表键是否正确
CHECK TABLE orders, orderitems; -- 用来针对许多问题对表进行检查
27.3 诊断启动问题
- 常用的命令行选项
--help # 显示帮助
--safe-mode # 装载减去某些最佳配置的服务器
--verbose # 显示全文本消息
--version # 显示版本信息然后退出
27.4 查看日志文件
- 错误日志:它包括启动和关闭问题以及任意关键错误的细节。通常名为
hostname.err
,位于data目录中,此日志名可用--log-error
命令行选项更改。 - 查询日志:它记录所有MySQL活动。通常名为
hostname.log
,位于data目录中,此日志名可用--log
命令行选项更改。 - 二进制日志:记录更新过数据的所有语句。通常名为
hostname-bin
,位于data目录中,此日志名可用--log-bin
命令行选项更改。 - 缓慢查询日志:此日志记录执行缓慢的任何查询。通常名为
hostname-slow.log
,位于data目录中。此名字可以通过--log-slow-queries
命令行选项更改。
二十八、改善性能
- 关键的生产DBMS应该运行在自己的专用服务器上。
- 总是有不止一种方法编写同一条
SELECT
语句,应该试验联结、并、子查询等,找出最佳方法。 - 存储过程执行得比一条一条地执行其中的各条MySQL语句快
- 应该总是使用正确的数据类型
- 决不要检索比需求还要多的数据。即,不要用
SELECT *
- 在导入数据时,应该关闭自动提交。顺便删除索引,在导入完成之后再重建(以提高导入速度)
- 必须索引数据库表以改善数据库检索的性能。如果一个简单的
WHERE
子句返回结果所花的时间太长,则可以断定其中使用的列(或几个列)就是需要索引的对象。 SELECT
若有大量的OR
条件。通过使用多条SELECT
语句和连接它们的UNION
语句,可以看到极大的性能改善。- 索引改善数据检索的性能,但是损害数据插入、删除、更新的性能。如果一些表不经常被搜索,则有必要之前不要索引它们。
LIKE
很慢,一般来说,最好用FULLTEXT
而不是LIKE