MySQL技术内幕(第5版)

978-7-115-38844-5
作者: 【美】Paul DuBois
译者: 张雪平
编辑: 杨海玲
分类: MySQL

图书目录:

详情

本书是MySQL方面名副其实的圣经级著作,全面介绍MySQL的基础知识以及MySQL有别于其他数据库系统的独特功能,书中特别关注如何高效地使用和管理MySQL。 全书由四个部分组成:第一部分集中介绍与数据库使用相关的一些基本概念,第二部分重点关注的是自己如何动手编写使用MySQL的程序,第三部分主要是面向那些负责数据库管理的读者,第四部分提供了一些参考附录。

图书摘要

版权信息

书名:MySQL技术内幕(第5版)

ISBN:978-7-115-38844-5

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美]Paul DuBois

  译    张雪平 何莉莉 陶 虹

  责任编辑 杨海玲

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Authorized translation from the English language edition, entitled MySQL, Fifth Edition,9780321833877 by Paul DuBois, published by Pearson Education, Inc., publishing as Addison-Wesley. Copyright © 2013 by Pearson Education, Inc.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc.

CHINESE SIMPLIFIED language edition published by PEARSON EDUCATION ASIA LTD. and POSTS & TELECOM PRESS Copyright © 2015.

本书中文简体字版由Pearson Education Asia Ltd.授权人民邮电出版社独家出版。未经出版者书面许可,不得以任何方式复制或抄袭本书内容。

本书封面贴有Pearson Education(培生教育出版集团)激光防伪标签,无标签者不得销售。

版权所有,侵权必究。


本书是MySQL方面名副其实的经典著作,全面介绍MySQL的基础知识以及MySQL有别于其他数据库系统的独特功能,书中特别关注如何高效地使用和管理MySQL。

全书由4个部分组成:第一部分集中介绍与数据库使用相关的一些基本概念,第二部分重点关注的是自己如何动手编写和使用MySQL的程序,第三部分主要是面向那些负责数据库管理的读者,第四部分提供了一些参考附录。书中包含大量示例,详尽地演示了MySQL的各项功能特性。此外,本书还为使用C语言、PHP语言和Perl语言开发数据库应用的读者提供了相关内容。

本书不仅适合MySQL初学者阅读,也适合想要深入了解MySQL的数据库管理人员和开发人员参考。


MySQL具有性能高、成本低、可靠性好等优点,早已成为最流行的开源数据库。它被许多中小规模的企业或开发团队选作数据存储管理的解决方案,被广泛应用于各种类型的中小型网站。随着MySQL的不断成熟,它也逐渐被用于更多的大型网站,如维基百科、Google和Facebook等。

MySQL的所属也是一再变迁,从最初的MySQL AB公司,到后来的Sun公司,一直到现在Oracle公司。随着功能的扩展,其价值也逐渐被更大规模的公司所认可。为获得更好的发展和支持,提供更好的数据管理功能,其发展路线一直保持在商业支持和开源支持两个方向上,并获得了成功。

MySQL具有许多让人垂涎的特点,其中包括运行速度快、易于使用、查询语言支持、功能强大、连接类型多样、高安全性、可移植性、低成本和源码开放等。它支持许多优秀数据库所拥有的特性,如事务处理、外键支持、复制、子查询、存储过程、触发器和视图等。这些特性在MySQL发展过程中被逐步加入或得到增强。现在的MySQL毫不逊色于其他大型商业数据库管理系统。

熟悉MySQL的人对Paul DuBois一定不陌生。他是一名专职的MySQL作家,为MySQL的推广与发展作出了很多贡献。他对MySQL的掌握全面而深入,他编写的MySQL书籍对MySQL社区的影响持久而深远。本书是《MySQL技术内幕》的第5版,与之前的版本相比,书中的很多内容都随着MySQL的变化发展进行了调整。例如,默认存储引擎的变更是MySQL最近发生的最大变化之一,因此书中与之相关的内容都有所调整。

本书由4个部分组成,共计13章,外加6个附录。其中,第一部分集中介绍了与数据库使用相关的一些基本概念,第二部分重点关注的是自己如何动手编写使用MySQL的程序,第三部分主要是面向那些负责数据库管理的读者,第四部分提供了一些参考附录。

通过本书,读者将全面和深入地了解MySQL的相关知识,使数据库管理工作变得更有效率,运行的数据库管理系统变得更加安全。本书包含大量示例,详尽地演示了MySQL的各项功能特性。此外,本书还能为使用C语言、PHP语言和Perl语言开发自己的数据库应用的读者提供帮助。

由于本书内容很多,整个翻译工作也是异常繁重,从而导致翻译的交稿日期被一再拖延。在此,我要向出版社给予的理解和支持表示感谢。另外,何莉莉、陶虹等在负责翻译部分章节的同时,还完成了大量的校对工作。为此,我也要向他们的辛勤付出表示感谢。鉴于个人能力所限,书中错漏之处在所难免,对可能造成的误解深表歉意!

张雪平

2015年5月 于上海


张雪平,毕业于西南石油大学模式识别与智能系统专业,硕士研究生。拥有多年高校计算机相关课程的教学经验,热衷于计算机应用软件的开发与研究。现从事石油行业相关软件的开发工作。

何莉莉,2009年毕业于西南石油大学软件工程专业,本科。现就职于中石化西北油田分公司,从事信息管理工作。

陶虹,毕业于四川师范大学计算机专业,系统分析师。现从事城市管理信息系统开发设计工作。


关系数据库管理系统(Relational Database Management System,RDBMS)是一种极为重要的工具,其应用非常广泛,从商业、科研和教育领域里的应用,到基于因特网(Internet)的内容发布,无处不在。尽管良好的数据库系统对于信息资源的管理和访问来说至关重要,但是许多组织机构还是觉得自己财力不够,难以负担。在过去,数据库系统给人的印象一直都是价格昂贵:不管是软件本身还是后续的技术支持,服务商总是漫天要价。此外,为了获得令人满意的性能表现,数据库引擎对计算机硬件通常都有很高的要求,从而导致整个成本变得更高。

随着时间的推移,计算机在硬件和软件两个方面都有了长足发展。小型桌面系统和服务器的价格都变得更加低廉,但功能却变得更加强大。而且,还出现了一种致力于为其编写高性能操作系统的蓬勃运动。这些操作系统都可以通过因特网或价格低廉的CD免费获得。其中,包括多种BSD Unix派生系统和众多的Linux发行版本。

免费操作系统的发行总是伴随着免费开源(Open Source)工具的发展,这些工具包括GNU C语言编译器gcc、因特网上使用最广的Web服务器Apache,以及像Perl、PHP、Python和Ruby这样被广泛认可的通用型脚本语言。与此形成鲜明对比的则是那些专属方案,它们会将你封锁在服务商所提供的高价产品里,他们甚至还不提供源代码。

现在,获得数据库软件已不再是什么难事,而且很多开源数据库系统还是免费的。其中,最为重要的一个就是MySQL,这是一套源自Scandinavia的SQL“客户端/服务器”关系数据库管理系统。MySQL包含的组件有:SQL服务器、用于访问服务器的客户端程序、管理工具,以及可以让用户自行编写程序的编程接口。

MySQL起源于Michael Widenius(外号Monty)在1979年为瑞典公司TcX所创建的数据库工具UNIREG。1994年,TcX开始着手研究具有SQL接口的RDBMS,用于开发Web应用程序。他们发现所有测试过的商业服务器对于他们的庞大数据表都表现不佳,而且免费的mSQL还缺少多项他们所必需的功能。于是,Monty准备开发出一套新的服务器。

1995年,Detron HB公司的David Axmark开始积极说服TcX公司将MySQL发布到因特网上。David还编写了文档,并使用GNU的配置工具autotools来构建MySQL。适用于Linux和Solaris系统的MySQL 3.11.1的二进制发行版于1996年公诸于世。为提供MySQL的发行,以及商业化的服务,专门成立了MySQL AB公司。2008年,Sun Microsystems公司收购了MySQL AB公司,2010年,Oracle公司又收购了Sun Microsystems公司。如今,MySQL既有二进制版本,又有源代码版本,可以在更多的平台上工作。

早期,MySQL广受欢迎的原因在于它快速、简单。但也有人提出批评:说它缺少特性,如事务处理和外键支持。随着MySQL的不断发展,它不仅增加了这些特性,还增加了许多其他特性,如复制、子查询、存储例程、触发器和视图。

这些功能将MySQL带入了企业应用程序的领域。最终,那些曾经只将“大块头”数据库系统用于其应用程序的人们,现在也开始对在普通硬件一直到企业服务器上都可以运行的MySQL另眼相看了。MySQL的性能可以傲视其他任何竞争对手,而且它可以处理具有数亿行数据的大型数据库。在商业领域,MySQL继续让更多的公司了解到它能够满足他们的数据库需求,同时只需要花费他们为商业许可和支持所支付的费用中的一小部分。

MySQL展现在我们面前的是一片宽广的前景:免费的操作系统,运行在强大但价格低廉的硬件上;同时,在比以往任何时候都要广泛的系统上,将大量的处理能力交给企业和个人。计算经济壁垒的降低让更多(比过去任何时候都要多)的组织机构拥有了高性能RDBMS的强大能力,而且花费极少。这点也同样适用于个人。例如,我在我的苹果笔记本电脑上(运行的操作系统是Mac OS X)使用了MySQL、Perl、PHP和Apache,这使得我能随处工作。全部成本仅仅只有笔记本电脑这一项而已。

可供选用的免费或低成本的数据库管理系统有好几种,如MySQL、PostgreSQL或SQLite。当将MySQL与其他数据库系统进行比较时,需要考虑的重点是什么呢?性能、功能(如对SQL的兼容性或扩展性)、技术支持、许可情况和价格,所有这些都是要考虑的因素。就此而言,MySQL具有许多让人垂涎的特性。

MySQL的技术支持做得怎么样?这是个很不错的问题!没有技术支持的数据库系统毫无用处。事实上,本书也算是一种支持形式,希望它可以满足大家的需要。(本书已出版到第5版,这正好表明它实现了该目标。)此外还有很多其他开放资源可供使用,你会发现MySQL拥有很好的技术支持。

在MySQL社区,无论是开发人员还是非开发人员,对各种提问的回复都很积极。邮件列表上的问题,通常在几分钟之内即可得到答复。当有bug报告时,开发人员通常都能迅速修复它们,并定期发布新的发行版本。

如果你正在挑选数据库产品,那么MySQL就是一个理想的候选评估对象。你可以无风险地免费试用它。安装和设置时间都要少于许多其他数据库系统。如果遇到什么困难,你可以使用邮件列表寻求帮助。

或许,你当前正在运行另一套数据库系统,但感觉受到严重束缚:当前系统的性能让人揪心;它是专属产品,你不想被困在这点上;你想在当前系统不支持的硬件上运行;提供给你的软件是二进制只读格式的,但你想要获得源代码;或者只是嫌它太贵!所有这些都是你选择MySQL的理由。你可以先通过本书来了解MySQL的功能,然后与MySQL销售人员进行联系,在邮件列表里提问咨询,最后你会找到自己要的答案。

本书能让你学会:如何有效地使用MySQL提高工作效率、如何把信息录入到数据库,以及如何通过构建查询语句来向数据“提问”,并获得你想要的答案。

你不需要成为一名专业的SQL程序员。本书会向你展示它是如何工作的。不过,与了解SQL语法相比,明白如何正确使用数据库系统则更有意义。因此,本书着重强调的是MySQL的特有功能,并展示了如何使用它们。你还能看到MySQL是如何与其他工具集成的。本书展示了如何自己编写程序来访问MySQL数据库,以及如何将Perl、PHP与MySQL一起使用,根据数据库查询的结果生成动态网页。

对于负责MySQL安装的人员,可以通过本书来了解具体的职责,以及如何实施。本书还会介绍如何创建用户账号、备份数据库、设置复制,以及如何保证网站的安全。

本书由4个部分组成。第一部分,集中介绍与数据库使用相关的基本概念。第二部分,重点关注自己如何动手编写使用MySQL的程序。第三部分,主要面向那些肩负数据库管理职责的读者。第四部分,提供的是一些参考附录。

说明


附录G、H、I需要上网获取。可以访问,注册后可获取,也可以访问www.informit.com/title/9780672329388,注册后可获取,也可以访问www.kitebird.com/mysql-book来获取

不管你是从本书的什么地方开始阅读,都最好能练习一下阅读过程中遇到的示例。这意味着你要先做好两件事情。

附录A给出了获得所有这些必要组件的办法和步骤。

如果你对MySQL或SQL还不太熟悉,那么请从第1章开始。它提供了一个入门教程,让你先了解MySQL与SQL的基本概念,从而加快本书其余章节的阅读速度。接着,再进入到第2章、第3章和第4章,了解如何描述和操作你自己的数据,这样你就可以在自己的应用程序里使用MySQL的功能了。

即使你对SQL已有所了解,但是仍然可以从第2章和第3章开始阅读。SQL的实现各不相同,而你要首先弄清:是什么让MySQL的实现与其他你所熟悉的SQL实现相比显得很独特。如果你对MySQL已经非常熟悉,但是还想要进一步改善其运行性能,那么可以把本书当作参考书,根据实际需要查询相关的章节。书后的附录可以提供很多信息。

如果你想要自己编写能访问MySQL数据库的程序,那么请从第6章开始,阅读与API有关的章节。如果想要为你的数据库开发一个基于Web的前端,以便更好地访问它们,或者与之相反,想要为你的Web站点提供一个数据库后台,以便能让站点增添动态内容,那么请阅读第8章和第9章。

如果你的职责是管理MySQL的安装,那么请从第10章开始阅读。

如果你正在对MySQL进行评估,以便得出“与你当前的RDBMS相比,它表现如何”的结论,那么本书中有几个章节可供参考。如果你想要了解MySQL与你习惯的SQL版本相比有何差异,那么请阅读本书第一部分里与SQL语法和数据类型有关的章节。如果你需要开发自己的应用程序,那么请阅读第二部分中与编程有关的章节。如果你想要评估MySQL安装所需要的管理支持级别,那么请阅读第三部分中与管理有关的章节。如果你目前还没使用数据库,但是正在将MySQL与其他数据库系统一起进行对比分析,以便从中做出选择,那么这些信息也能派上用场。

本书第1版讲的是MySQL 3.22,并开始涉及MySQL 3.23。第2版则把讲解范围扩大到了MySQL 4.0,以及MySQL 4.1的第一个发行版本。第3版讲的是MySQL 4.1,以及MySQL 5.0的几个初始发行版本。第4版讲的是MySQL 5.0,以及MySQL 5.1的几个初始发行版本。

对于第5版,最低版本要求是MySQL 5.5。也就是说,本书讲解的内容是MySQL 5.5,以及MySQL 5.6的早期发行版本。如果你的版本比5.5还旧,那么这本书的大部分内容仍然适用,但针对旧版本的差异一般没有明确指出来。

MySQL 5.5系列已经达到了普遍适用(General Availability,GA)阶段,也就是说,它完全适用于生产环境。与5.5之前的发行版本相比,新版本已经发生了很多变化,因此请大家尽可能选用最新的版本(在我编写本书时,最新版本为5.5.30)。当前,MySQL 5.6系列还处在开发当中(还不适合用于生产用途),但很快就会达到GA阶段,而且很可能在你阅读本书时就已经达到了。

与旧版本有关的更多信息,请访问MySQL网站http://dev.mysql.com/doc,在那里可以访问到所有版本的参考手册。

每次用新的材料对本书的各个版本进行更新时,想要控制书的篇幅一直是件极具挑战性的事情。出于篇幅的考虑,我删除了一些在之前的版本里已有的信息。相比之前的版本,MySQL最大的变化在于:InnoDB现在成为了默认的存储引擎(不再是MyISAM了)。因此,本书为了迎合重点强调InnoDB的需要,减少了与MyISAM相关的内容。此外,对于不太重要的存储引擎(如FEDERATED和BLACKHOLE),则一笔带过。本书还删除了与libmysqld(嵌入式服务器)、mysqlhotcopymyisampack、空间数据类型和函数有关的信息,并且使用通用性的说明代替了详尽的安装材料。更多相关信息请阅读MySQL参考手册。

对于书中未曾涉及的以下几项内容,大家需要留意一下。

如果想要下载这些产品或者查看它们的文档,可以访问http://www.mysql.com/products/http://dev.mysql.com/doc/

对于本书中涉及的其他主要软件包,只要是最近的版本对于示例展示来说就应该足够了。下面表格中列出了在编写本书时各软件包的最新版本。

软件包

版 本

Perl DBI模块

1.623

Perl DBD::mysql模块

4.020

PHP

5.4.10

Apache

2.4.3

CGI.pm

3.63

本书所提到的所有软件都可以在网上下载到。附录A提供了将MySQL、Perl DBI、PHP、PDO、Apache和CGI.pm部署至系统里的相关帮助。该附录还包含了“如何获得全书所用示例数据库sampdb”的说明,以及在本书编程章节所开发的各个程序。

如果你使用的是Windows,那么本书会假设你的系统为Windows 2000或更高的版本。本书涉及的某些功能,如命名管道和Windows服务,在早期的版本里并不支持。

本书使用了下列印刷约定。

对于交互示例,则假设是在终端窗口或控制台窗口里输入各种命令。为反映出具体环境,命令示例里的提示符则用来表示运行你的命令所需要的程序环境。例如,在mysql客户端程序里执行的SQL语句,其前面会显示mysql>提示符。对于通过命令解释器执行的命令,其提示符通常为%。一般情况下,此提示符表示的是:这些命令可以同时在Unix系统和Windows系统下使用,只是你真正看到的提示符样子,取决于具体的命令解释器。(在Unix系统里,命令解释器指的是你登录所用的那个shell程序;在Windows系统里,则指的是cmd.exe。)另外还有两个比较特殊的命令行提示符,即#和C:\>,前者表示该命令需要在Unix系统上通过susudo命令以root用户身份来执行,后者表示该命令专门用于Windows系统。

下面的示例展示了一条应从命令解释器里输入的命令。%是提示符(不需要你输入)。为执行该命令,你需要输入如下所示的那些粗体字符,并将那个斜体字替换成你自己的用户名:

% mysql --user=user-name sampdb

在SQL语句里,SQL的关键字和函数名要用大写字母来表示。而数据库名、表名和列名则一般用小写字母来表示。

在语法描述里,方括号([])用于表示可选内容。备选列表则以竖线(|)进行分隔。包括在方括号内的列表是可选项,可以从中选择一项。包括在花括号{}内的列表是必选项,必须从中选择一项。

如果你在本书中未能找到某个问题的答案,该怎么办呢?下面的表格列出了一些文档资源,其中包括相关软件及其站点,希望对你有所帮助。

软件包

官方网站

MySQL

http://dev.mysql.com/doc

Perl DBI

http://dbi.perl.org

PHP

http://www.php.net

Apache

http://httpd.apache.org

CGI.pm

http://search.cpan.org/dist/CGI.pm

这些站点提供的信息包括参考手册、常见问题(Frequently Asked-Question,FAQ)和邮件列表。

不同邮件列表的订阅说明也各有不同。下面的表格列出了可以帮你找到必要信息的网址。

软件包

邮件列表说明

MySQL

http://lists.mysql.com

Perl DBI

http://dbi.perl.org/support

PHP

http://www.php.net/mailing-lists.php

Apache

http://httpd.apache.org/lists.html

Paul DuBois既是作家和数据库管理员,又是开源社区和MySQL社区的领导者。他为MySQL在线文档作出过很多贡献,其著作包括New Riders出版的《MySQL and Perl for the Web》,以及O'Reilly出版的《MySQL Cookbook》、《Using csh and tcsh》和《Software Portability with imake》。目前,他是Oracle公司MySQL文档团队的技术作家。

首先要感谢的是技术审稿人Stephen Frein,他为本书的改进提供了许多很好的看法和建议。另外,如果没有之前的几个版本作为基础,也就不会有这一版本的诞生,因此我还要继续感谢那些在之前版本里帮助过我的技术审稿人和耐心回答我问题的人。

Pearson公司参与本版出版工作的员工有:策划编辑Mark Taber、项目编辑Tonya Simpson、文字编辑Sarah Kearns、排版员Kim Scott、校对员Jess DeGabriele、索引员Heather McNeill,以及封面设计者Chuti Prasertsith。在此,也要对他们表示感谢。

最后要感谢我的妻子Karen。感谢她在本版编写过程中给予的最大支持和鼓励。

访问Pearson的网站,并在www.informit.com/register注册本书,可获得更多信息,其中包括本书提供的各种更新、下载资源或勘误。

 相应的中文译稿可在人民邮电出版社网站本书主页上免费注册下载。——编者注


本章主要介绍MySQL关系数据库管理系统(Relational Database Management System,RDBMS)和MySQL所使用的结构化查询语言(Structured Query Language,SQL)。其中,列出了应该掌握的基本术语和概念,描述了示例所要用到的样本数据库sampdb,并且提供了一个用于展示如何使用MySQL创建数据库并与之进行交互操作的教程。

如果你对数据库系统还不甚了解,或者还不是很肯定自己是否需要学习它,甚至还不肯定是否需要使用它,那么请从本章开始。如果你对MySQL和SQL还一无所知,并且需要一个入门指南,那么也请从本章开始。对MySQL或其他数据库系统已有一定经验的读者,则可以略读一下本章的内容。不过,为了你能对全书所用那个数据库sampdb的目的和内容更熟悉,希望可以阅读一下1.2节。

本节描述的是MySQL数据库系统的用途,具体描述了“MySQL可以干什么”,以及“它如何才能给你提供帮助”。如果你已经明白数据库的用途(也许你正在思考某个问题,到这里来只是想要找出“如何使用MySQL来解决它”的答案),那么可以直接翻阅到1.2节。

数据库系统在本质上是一种高效的管理大量列表信息的办法。这些信息的来源可能多种多样。它可能是研究数据、业务记录、客户需求、体育统计、销售报告、个人信息、人事档案、bug报告或者学生成绩。发挥数据库系统强大作用的时机在于:想要组织和管理的信息非常庞大或复杂,以至于所有记录采用手工处理会变得异常繁重。对于每天处理上百万条事务的大公司来说,数据库是必不可少的。不过,即使只有一个人的小公司,也可能会维护很多的信息,甚至多到需要用一个数据库来管理它。假设有下面这些情况。

上面这些场景所涉及的信息量有大有小。但它们都有一个共同的特点,即这些工作都可以通过手工来完成,但使用数据库系统来管理会更高效。

在使用像MySQL这样的数据库系统时,你希望从中获得哪些具体好处呢?这取决于你的特殊需要和需求,并且,如同上面示例所示,具体的好处都各不相同。不过,在一般情况下,适合于用数据库管理系统来处理任务的人群是那些不使用数据库管理系统就要使用文件柜的人。事实上,数据库系统就像一个内置了复杂文件系统的巨大文件柜。与手工管理记录的方式相比,以电子化方式来管理记录存在有许多优势。下面来看看前面描述过的那个牙科诊所的场景。在用于管理患者记录的文档系统能力方面,MySQL可以为你带来下面这些帮助。

(1)缩短记录归档时间。你不用在文件柜里挨个拉抽屉找存放新记录的地方。你只需将它提交给MySQL,MySQL会为你找到正确的存放这条记录地方。

(2)缩短记录检索时间。当进行记录查找时,不必为了想要找到的记录而亲自动手去逐个搜索。为了给那些最近一段时间没来做检查的患者发个提醒信息,你可以让MySQL来帮你找出这些记录。当然,这与让你告诉另一个人“请帮忙确认一下最近6个月哪些患者没来参加检查”的情况有所不同。事实上,你“念出的”是一段奇怪的“咒语”:

SELECT last_name, first_name, last_visit FROM patient
WHERE last_visit < DATE_SUB(CURDATE(), INTERVAL 6 MONTH);

如果你以前从没见过类似的内容,那么它初看起来可能会相当吓人。不过,它的效果却是相当吸引人:你不用再花费一小时来翻看你的记录,只需一两秒即可得到想要的结果。不管怎样,无需多久你就会习惯这种奇形怪状的表达。等阅读完本章,你便会明白它真正的含义。

(3)灵活的检索排序。不用严格按照记录存储的顺序(如按患者的名字排序)来检索它们。MySQL可以按任何你想要的顺序来提取记录,如按名字、保险公司名字、上次就诊时间等。

(4)灵活的输出格式。在找到感兴趣的记录之后,你无需手动复制这些信息,MySQL会为你生成一个列表。有时,你可能只是想打印这些信息;有时,你可能会想在另一个程序里使用它们。例如,在生成了最近逾期未能复诊的患者名单后,你便可以把这些资料输送到某个文字处理软件,让它打印出你想要发送给那些患者的通知单。或许,你只是对如同“选中记录统计”这样的汇总信息比较感兴趣。你不用亲自来统计记录,MySQL会替你生成汇总信息。

(5)多用户同时访问记录。对于纸质记录,如果有两个人同时想要查看某个记录,那么其中一个人就必须等待另一个人将这个记录归还之后才能查看。而MySQL则可以允许这两个人同时访问这个记录。

(6)记录的远程访问与电子传输。想用纸质记录,就得亲自跑到存放它们的地方,或者让人将它们复印之后再发送给你。电子记录则为远程访问记录或者电子传输记录提供了可能。如果你的牙科集团设有许多分支机构,那么这些机构里的人员便可以在当地访问到你的资料。你完全不用再通过快递来传送这些副本。如果有人需要记录,但却又没有与你一样的数据库软件,那么你可以选择那些所需的记录,将其中的内容通过网络发送给他。

如果你曾经用过数据库管理系统,那么肯定对刚才描述的种种好处深有体会,而且可能也在思索如何才能超越常规“代替文件柜”应用程序的限制。有许多组织机构将数据库与网站结合在一起使用,这就是一种很好的方式。假设贵公司有一个商品库存数据库,每当顾客打电话过来询问仓库里是否有货、其价格是多少时,服务台的员工便会用到它。这是一种比较传统的数据库使用方式。不过,如果贵公司搭建一个供顾客访问的网站,那么就可以提供一项新的服务:创建一个搜索页面,让顾客可以查看条目,从而确定价格、可用性以及商品的库存情况。如果支持在线订购,那么顾客甚至不用离开家门便能购买到你的产品。这让顾客获得了他们想要的信息,而数据库却是根据提问自动搜索商品库存信息来提供这些内容的。顾客立即获得了所关心的信息,不用一边听着恼人的录音,一边傻等着;也不用受限于服务台的上下班时间。并且,每当有一位顾客使用贵公司网站,就意味着会少一个电话,而这是需要由一个在服务台拿工资的人来处理的事情。如此看来,该网站或许可以为自己买单。

不过,你还可以更进一步地发挥数据库的作用。基于Web的库存搜索请求,不仅可以把信息提供给顾客,也能提供给贵公司。这些查询可以让你了解到顾客正在查找哪些商品,而查询结果则会让你知道是否能够满足他们的需求。在某种程度上,如果你没有顾客想要的东西,那么你可能会错失这笔生意。因此,记录下库存搜索信息非常有意义,通过它可以了解到:顾客正在找寻什么商品,而你是否还有存货。接着,就可以根据这些信息来调整库存,并向顾客提供更好的服务。

说了半天,那么MySQL是如何工作的呢?找到答案的最好方式就是自己动手体验一下。为此,我们需要有一个可以操作的数据库。

本节将描述本书所使用的示例数据库。在你学习使用MySQL的过程中,可以用这个数据库提供的示例进行尝试,该数据库是针对之前描述过的两种情况设计的。

下面,我们进一步分析一下这两个场景的需求。

在考虑“将什么放入数据库”之前考虑“要从数据库里获得什么样的内容”,这似乎有些本末倒置。毕竟,大家都是这么认为的:要先输入数据,然后才能检索它。不过,你使用数据库的方式取决于你的目标是什么。而且,与想要放入数据库的内容相比,想要从数据库检索的内容则与那些目标的关系更加密切。只有在计划今后使用这些信息之后,你才会想要花费时间和精力把它们放入数据库。

该项目的场景是:你是联盟秘书,眼下正使用某个文字处理文档维护那份成员资格名单。对于像“生成打印名录”这样的事情,处理起来当然没什么问题。但是,当你想要获得其他更多信息时,就会受到限制。你还有几个目标要实现。

数据库不是世界上最令人兴奋的工具,因此我不会鼓吹像“使用数据库能激发人的创造性思维”这样不切实际的话。不过,如果你不再把信息看成是一种累赘(如同你在使用文字处理文档时所想的那样),而是把它想成是可相对轻松处理的事情(如同你希望使用MySQL来处理一样),那么你自然会释放出自己的潜能,找到更多新的使用这些信息的方法。

好了!此时此刻,你所想到的数据库应用数量可能让你有些忘乎所以了。在稍息片刻之后,重新回到现实,你开始问一些比较实际的问题,例如:

用于开发数据库的操作系统则由你来选择。几乎所有我们将讨论的软件都能在UNIX(它指代了BSD UNIX、Linux、Mac OS X等)和Windows上运行。极少数的例外情况一般是UNIX或Windows特有的shell脚本或批处理脚本。

现在一起来看另一个要使用示例数据库的情况。该项目的场景是:你是一名负责成绩考评的教师。你想将成绩处理工作,从使用成绩册的手工操作方式转换成使用MySQL的电子表示方式。在这种情况下,从数据库获取信息的方式隐含在你目前使用成绩册的方式中。

最终目标是要避免手动排序、避免手动汇总分数和考勤情况。换句话说,你希望MySQL可以对分数排序,并在期末完成所有与每个学生的总分和缺席次数相关的计算。为实现这些目标,你需要班里学生的名单、每次考试和测验的分数,以及全部学生的缺勤日期。

如果你对这里的“美史联盟”和“成绩考评”两个项目都不感兴趣,那么你可能会想“还有什么场景一定适合你呢”。答案就是“无穷无尽”。其实,这两个项目可以说明你用MySQL和与之相关的工具可以做什么事情。稍微想象一下,你便能看到示例数据库查询是如何应用于

你所要解决的那些特定问题的。假设你正好就在我前面提到的那个牙科诊所里工作。虽然在这本书里不会看到很多与牙科学有关的查询,但是会看到这里所发现的很多查询都可以应用到患者记录维护、办公记载等那些工作中。例如,“确定联盟里哪些成员需要更新他们的成员资格”与“确定最近哪些患者未来复诊”这两个任务便非常相似。它们都是基于日期的查询,因此只要你学会了“成员资格更新”的查询,那么便可以将该技巧用于编写“久未复诊患者”的查询,从而带来更多的收获。

你可能已经注意到了,尽管这是一本关于数据库的书籍,但到目前为止,你还未遇到过多少晦涩难懂的专业技术术语。事实上,尽管我们对如何使用示例数据库有过大致描述,但关于数据库到底是什么样子,我却只字未提。可是,既然我们要设计数据库,并实现它,那么我们就不能再避而不谈有关的术语了。这正是本节的主要内容。本节所描述的术语都是本书要用到的,因此希望大家能够熟悉它们。庆幸的是,关系数据库的许多概念都很简单。人们之所以喜欢关系数据库,很大程度上便是因为其基本概念都简明易懂。

在数据库领域,MySQL被划分为关系数据库管理系统(RDBMS)。我们下面来拆解一下。

关系数据库是如何把数据组织到表里的呢?又是如何把不同表的信息关联在一起的呢?下面来看一个例子。假设你在经营一个网站,它有一项横幅广告服务。你与多家想要刊登广告的公司签订了合同,满足它们想要在人们访问你网站页面时显示其广告的需要。每当有访客点击其中的某个页面时,你就提供一个嵌有广告的页面发送给访客浏览器,这样你就能从刊登这条广告的公司那里获得一点费用。这就是所谓的广告“点击”。为了表示这些信息,你需要用到3个表(见图1-1)。第1个company表由这样几列构成:公司名称、编号、地址和电话号码。第2个ad表的构成列有:广告编号、“拥有”该广告的那家公司的编号,以及每点击一次的收费数目。第3个hit表需要记录广告点击量和广告点击日期。

图1-1 横幅广告的表

有些问题只用一个表便能回答。例如,想要知道与你签订了合同的公司有多少家,那么你只需统计一下company表共有多少行即可。同样地,想要了解在某段指定的时间里有多少点击量,则只需检查表hit即可。其他有些问题则可能比较复杂,需要查询多个表才能得到答案。例如,想要知道在7月14日那天Pickles公司(Pickles,Inc.)的每一条广告分别被点击了多少次,那么就需要像下面那样使用所有的3个表。

(1)在company表里查找公司名称(Pickles,Inc.),从而查出该公司的编号(14)。

(2)在ad表里,利用这个公司编号找出与之匹配的行,从而可以确定出所有相关广告的编号。最后找到两条广告,即编号48和101。

(3)对于在ad表里匹配到的每一行,使用该行里的广告编号在hit表里找出日期介于给定日期范围内的所有匹配行;然后,再统计这些匹配行的数量。最后查询出的结果是:48号广告有3个匹配;101号广告有2个匹配。

这些听起来好像很复杂!但这正是关系数据库系统最擅长做的事情。这种复杂性只是表面现象,因为刚才描述的每一步骤都是一个简单的匹配操作:将一个表的行值与另一个表的行值,通过匹配关联起来。这种简单的操作可以有多种变化,用于回答各式各样的问题,如各家公司分别投放了多少个不同的广告?哪家公司的广告最受欢迎?每个广告带来的收益是多少?在当前结算期内,各家公司应该支付你多少广告费?

有了之前讲述的这些关系数据库理论,你就能读懂本书后续的内容了;我们不必了解“第三范式”(Third Normal Form)、“实体联系图”(Entity-Relationship Diagram)等这些枯燥乏味的内容。(如果想了解它们,请阅读C. J. Date或E. F. Codd的著作。)

为了与MySQL交互,需要使用一种名为SQL的语言。所有主流数据库系统都支持SQL,但各个服务商的实现都各不相同。SQL支持许多不同的语句,可以让你用非常有趣和实用的方式与数据库进行交互。

与其他计算机语言一样,初次接触SQL的人往往会觉得它很奇怪。例如,在创建表时,你必须告诉MySQL该表的结构是什么样子的。很多人都会想到把表与图表或图片联系起来。但是MySQL不是这样子的,因此在创建表时,你必须告知MySQL一些类似下面那样的内容:

CREATE TABLE company
(
 company_name VARCHAR(30),
 company_num INT,
 address   VARCHAR(30),
 phone    VARCHAR(12)
);

如果对SQL还不太熟悉,那么可能会对这样的语句心生畏惧。不过请放心,你不用成为程序员就能学会如何熟练地使用SQL。随着对SQL的深入了解,你对CREATE TABLE的看法也会悄然地发生变化——它不再是一组怪诞的表达,而是一种有助于信息描述的强大工具。

当使用MySQL的时候,你实际上至少会用到两个程序,因为MySQL采用的是“客户端/服务器”体系结构。有一个程序是MySQL服务器,即mysqld。它运行在存放数据库的那台机器上,主要负责监听网络上的客户端请求,并根据这些请求去访问数据库内容,然后向客户端提供它们所查询的信息。另一个程序是MySQL客户端,它主要负责连接到数据库服务器,并发起查询,以便将自己想要的信息告知服务器。

MySQL的大部分发行版本都包括数据库服务器程序和客户端程序。(在Linux下使用RPM包时,会有一个单独的服务器RPM包和客户端RPM包,因此应该安装两种包。)请根据自己的实际情况使用合适的客户端程序。最常用的客户端程序是mysql,它是一个交互式客户端程序,你可以用它来发起查询并查看结果。另外还有两个主要用于管理的客户端程序,即mysqldumpmysqladmin。前者主要用来把表的内容导出到文件里;后者主要用来检查数据库服务器的工作状态,以及执行一些数据库管理相关的任务,如通知服务器关闭。MySQL发行版本里还包括一些其他的客户端程序。此外,MySQL还提供了一个客户端开发库,如果它自带的标准客户端程序无法满足你的应用需求,你可以利用这个库来编写自己的程序。这个开发库可以被C语言程序直接使用。如果你不习惯C语言,那么也可以选择适用于其他语言(如Perl、PHP、Python、Java和Ruby)的编程接口。

本书讨论的客户端程序都是基于命令行的。你也可以试试MySQL工作台(MySQL Workbench),这是一个基于图形用户界面(Graphical User Interface,GUI)的工具,提供了即点即击(point-and-click)功能。关于此工具的更多信息,请访问http://www.mysql.com/products/tools/

MySQL的“客户端/服务器”体系结构有以下几个好处。

MySQL与mysql之间的差异

为避免混淆,我们使用MySQL指代完整的MySQL RDBMS,而mysql指代特定客户端程序的名字。虽然它们的发音相同,但我们通过字母大小写和字体差异对它们进行了区分。

说到发音,MySQL被读作“my-ess-queue-ell”。具体细节可以在MySQL参考手册(MySQL Reference Manual)里查阅到。此外,SQL的读法有“sequel”和“ess-queue-ell”两种,具体是哪种由读它的人决定。本书假定SQL的发音为“ess-queue-ell”。

现在你已具备了所有的预备知识。下面来实际操作一下MySQL。

本节提供了一个带有多个示例的教程,其目的在于帮助你熟悉MySQL。主要内容包括创建一个示例数据库和多个表,通过对这些表里的信息执行插入、检索、删除和修改操作,练习与示例数据库的交互。在此过程中,你将学会以下内容。

本书将示例数据库命名为sampdb。如果在你的系统上已经有人占用了这个名字,或者你的MySQL管理员为你分配了另一个名字,那么你需要换一个名字。不管是何种情况,你都需要把本书示例中的sampdb替换为你实际使用的数据库名称。

即使你的系统里有多个用户,且他们都各自拥有自己的示例数据库,本节所有示例里的表名也都可以直接使用。在MySQL里,只要每个人都用自己的数据库,那么大家完全可以使用相同的表名,不会有任何问题。MySQL将表限制在各自的数据库里,防止了相互干扰。

本教程在好几个地方都要用到“示例数据库发行包”( 也称作“sampdb数据库的发行版”)。该发行包里包含了许多用来安装示例数据库的查询语句和数据。发行包的获取办法和安装步骤可以在附录A里查到。在发行包解压之后,它会自动创建一个名为sampdb的子目录,其中包含了你所需要的许多文件。顺便提个建议:每次你操作数据库里的示例时,最好都能切换到一个新的目录。

如果想要在任何目录里都可以方便地运行MySQL程序,那么你最好能把位于MySQL目录下包含着这些程序的bin目录,添加到命令解释器的搜索路径里。具体操作方法是,参考本书A.3.3节,把该目录的路径名称添加到环境变量PATH里。

为练习本教程里的各个示例,除获得示例数据库发行包外,还必须满足以下几项基本要求。

所需的软件包括MySQL客户端和MySQL服务器。客户端程序必须安装在你自己要用的机器里。服务器可以选择安装在你的机器上,这点不强求。只要你有权限连接它,那么服务器可以位于任何地方。如果你的因特网服务提供商(Internet Service Provider,ISP)提供了MySQL服务,那么也可以申请使用它。想要自己获得和安装MySQL,请参考附录A。

除了MySQL软件,还必须要有一个MySQL账户,这样才能连接到服务器,并创建示例数据库及其表。(如果你已有MySQL账户,则可以直接用它。不过,建议你另外建立一个专用于本书学习的账户。)

此时此刻,我们遇到了一个“是先有鸡,还是先有蛋”的难题:为了建立一个用于连接服务器的MySQL账户,你必须要先连接到该服务器。通常情况下,你需要在运行MySQL服务器的主机上,以root用户身份登录,然后执行CREATE USER语句和GRANT语句来创建新的MySQL账户,并为其分配数据库权限。如果你是在自己的机器上安装了MySQL服务器,并且正在运行,那么你可以像下面那样用root身份连接服务器,然后创建一个新的示例数据库管理员账户,其用户名为sampadm,密码为secret。你也可以把其中的名字和密码更改成自己想用的,但在本书后面用到它们的地方也要做相应的修改。

% mysql -p -u root
Enter password: ******
mysql> CREATE USER 'sampadm'@'localhost' IDENTIFIED BY 'secret';
Query OK, 0 rows affected (0.04 sec)
mysql> GRANT ALL ON sampdb.* TO 'sampadm'@'localhost';
Query OK, 0 rows affected (0.01 sec)

命令mysql有一个选项-p,它能让mysql提示输入root用户的MySQL密码。如上例所示,输入的密码会被显示为一串星号,即******。这里假设你已经为MySQL的root用户设置了密码。如果你还未设置,则请在Enter Password: 提示出现后直接按回车(Enter)键。不过,如果root用户无密码,那么这是个很大的安全漏洞,你应该尽快给它设置一个。想要了解更多关于CREATE USER语句、GRANT语句、设置MySQL用户账户以及修改密码的信息,请参考第13章。

在创建sampadm账户之后,请输入quit,并按回车键,然后退出mysql程序。

刚才展示那些语句适合于在运行服务器的那台机器上连接MySQL。它们让你可以通过用户名sampadm和密码secret来连接服务器,并让你拥有完全访问sampdb数据库的权限。不过,GRANT语句并不会创建数据库(你可以在创建数据库之前为它分配权限)。我们稍后会讨论与数据库创建相关的操作。

如果你打算通过网络从另一台主机连接到MySQL服务器,那么需要把示例中的localhost更改为你正使用的那台主机的名字。例如,你想要从主机boa.example.com连接到MySQL服务器,那么相应的语句则应类似这样:

mysql> CREATE USER 'sampadm'@'boa.example.com' IDENTIFIED BY 'secret';
mysql> GRANT ALL ON sampdb.* TO 'sampadm'@'boa.example.com';

如果你对服务器没有控制权限,无法创建用户,那么请求助MySQL管理员,让他为你建立一个账户。然后,将本书各示例里的sampadmsecretsampdb分别替换为管理员分配给你的用户名、密码和数据库名。

为连接到MySQL服务器,请在命令提示符(即Unix系统的shell提示符,或者Windows下的控制台窗口提示符)里调用mysql程序。命令如下:

% mysql options

本书使用%来表示命令提示符。它是Unix系统的其中一个标准提示符;另一个是$。在Windows系统里,你所看到的提示符有点像C:\>。当输入这些示例里所示的命令时,请不要输入提示符本身。

mysql命令行里的options部分可以为空。但下面这种命令形式更为常见:

% mysql -h host_name -p -u user_name

在执行mysql时,不用提供所有的选项,但通常至少要指定用户名和密码。下面是全部选项的具体含义。

% mysql -h host_name -p -u user_name
Enter password:

假设MySQL的用户名和密码分别是sampadmsecret。如果运行MySQL服务器的那台主机与你运行mysql程序的主机相同,那么你可以省略选项-h,此时mysql命令会像下面那样连接服务器:

% mysql -p -u sampadm
Enter password: ******

在输入完这条命令之后,mysql会显示Enter password:,提示你输入密码,然后你便可以输入它(输入的secret会在屏幕上显示为6个星号******)。

如果一切顺利,mysql将会显示出一条欢迎消息和一个mysql>提示符,此时表明它在等你发起查询命令。完整的启动过程近似如下:

C:> set USER=sampadm
% mysql -p -u sampadm
Enter password: ******
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 13762
Server version: 5.5.30-log

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

如果想要连接运行于另一台机器上的MySQL服务器,那么必须使用-h选项来指定其主机名。假设该主机名为cobra.example.com,那么相应的命令如下所示:

% mysql -h cobra.example.com -p -u sampadm

为简洁起见,在后面大部分显示mysql命令行的示例里,选项-h-u-p都会被省略,并且假定你会提供所有必要的选项。在运行其他MySQL程序(如mysqlshow)时,你也会用到这些选项。

在连接到MySQL服务器之后,你可以随时输入quit命令来终止会话:

mysql> quit
Bye

也可以输入exit\q来退出。在Unix系统里,可以按组合键Ctrl+D来退出。

在刚开始学习MySQL时,很多人都可能会对它的安全系统感到烦恼,因为它很难操作。(必须要有创建和访问数据库的权限;还有,不管何时连接服务器,都必须指定正确的用户名和密码。)不过,在练习完本书提供的示例数据库,并开始输入和使用自己的数据记录之后,你的看法便会迅速发生转变。到那时,你就会欣赏MySQL的这种方式,因为它可以防止他人窥视(或者更为糟糕的情况,破坏)你的信息。

如何设置工作环境,才能不用在每次运行mysql时都需要在命令行指定连接参数?关于这个问题请参考1.5节。简化服务器连接过程的最常见办法是,将连接参数存放到一个选项文件里。如果想现在就了解如何建立这样的文件,那么请直接跳转到1.5节。

在连接上服务器以后,你便可以发起SQL语句,让服务器执行它。本节将介绍一些与mysql进行交互的一般原则。

想要在mysql里输入语句,只需要直接输入即可。在语句的结尾,请输入一个分号(;),并按下回车键。该分号会告知mysql——语句到此结束。在输入完一条语句之后,mysql会将它发送到服务器执行。服务器接着处理它,并把结果发送回mysql,然后mysql会显示结果。

下面这个示例展示了一条用于查询当前日期和时间的简单语句:

mysql> SELECT NOW();
+---------------------+
| NOW()          |
+---------------------+
| 2013-01-08 17:42:33 |
+---------------------+
1 row in set (0.00 sec)

除使用分号外,还有另一种终止语句的方法,即使用\g(意思是go):

mysql> SELECT NOW()\g
+---------------------+
| NOW()          |
+---------------------+
| 2013-01-08 17:42:40 |
+---------------------+
1 row in set (0.00 sec)

也可以使用\G。它会以“垂直”方式显示结果,每行显示一个值:

mysql> SELECT NOW(), USER(), VERSION()\G
*************************** 1. row ***************************
  NOW(): 2013-01-08 17:54:24
  USER(): sampadm@localhost
VERSION(): 5.5.30-log
1 row in set (0.00 sec)

如果语句的输出行比较短,那么\G就没什么用处;但如果输出行很长,而且会在屏幕上回绕显示,那么使用\G便能让输出内容更易于阅读。

如上所示,mysql会显示语句结果和一行统计信息,其中包括该结果所包含的行数,以及语句执行所花费的时间。在后面的示例里,一般情况下将不再显示统计信息行。

因为mysql会等待语句结束符,所以你不用在单独一行里输入一条语句的全部内容。你可根据需要,将它分隔成多行进行输入:

mysql> SELECT NOW(), 
  -> USER(),
  -> VERSION()
  -> ;
+---------------------+-------------------+------------+
| NOW()           | USER()         | VERSION() |
+---------------------+-------------------+------------+
| 2013-01-08 17:54:56 | sampadm@localhost | 5.5.30-log |
+---------------------+-------------------+------------+

请注意,在输入该语句的第一行时,提示符从mysql>变为了->。这是在提醒你:mysql认为你还要继续输入语句内容。这是个很重要的反馈。如果你忘了在语句末尾加上分号,那么这个变化的提示符会提醒你注意:mysql仍在耐心等待你继续输入语句内容。否则,你会很不耐烦地等在一边,心里疑惑:为何MySQL执行你的语句要花这么长的时间。命令mysql还有另外几种提示符,附录F对它们进行了详细介绍。

如果你已经输入了一条多行语句,但突然决定不执行它了,那么可以输入\c来清除(即取消)它:

mysql> SELECT NOW(),
  -> VERSION(),
  -> \c
mysql>

请注意,提示符是如何变回mysql>的。这种变化表明mysql已准备接收一条新的语句。

与将一条语句分成多行输入相反的操作是,在单独一行里输入多条语句,两条语句中间用终止符隔开:

mysql> SELECT NOW();SELECT USER();SELECT VERSION();
+---------------------+
| NOW()         |
+---------------------+
| 2013-01-08 17:55:20 |
+---------------------+
+-------------------+
| USER()       |
+-------------------+
| sampadm@localhost |
+-------------------+
+------------+
| VERSION() |
+------------+
| 5.5.30-log |
+------------+

大多数情况下,输入语句使用大写、小写或者大小写混用都可以。例如,下面几条语句检索到的是相同的信息(虽然显示结果里列标题的大小写有所不同):

SELECT USER();
select user();
SeLeCt UsEr();

本书的所有示例都将用大写字母来表示SQL关键字和函数名,用小写字母来表示数据库、表和列的名字。

当在语句里调用函数时,请不要在函数名和后面的括号之间加入空格。如果有空格,则会导致语法错误。

将多条语句存储在一个文件里可以创建一个SQL脚本,然后让mysql从该文件(而不从键盘)读取语句。请使用shell的输入重定向功能来实现这种操作。例如,假设语句都存储在一个名为myfile.sql的文件里,那么我们可以使用下面这样的命令来执行其中的语句(请记得指定所有必需的连接参数选项):

% mysql < myscript.sql

你可以为该文件随便取一个名。我给它加上了一个“.sql”后缀,以表明里面存放的是SQL语句。

像这种调用mysql来执行文件里的语句的做法,会在1.4.7节再次用到,到时,我们会用这种办法来往sampdb数据库里输入数据。与逐条手工输入相比,让mysql从某个文件里读取多条INSERT语句来加载表要方便很多。

本教程的其余部分展示了很多SQL语句,你可以自行练习。在这些语句前面都有提示符mysql>作为标志,并且这些示例都提供了语句输出结果。如果你输入与示例显示一样的语句,那得到的输出结果也应该是相同的。前面未加提示符的语句主要是用来做说明用的,你不需要执行它们。当然,如果愿意,执行一下也无妨。提醒一下,请记得在每条语句的末尾加上一个分号作为结束符。

数据库的使用涉及以下几个步骤。

(1)创建一个数据库。

(2)在该数据库里创建多个表。

(3)对表里的数据执行插入、检索、修改或删除操作。

想要创建新的数据库,请先使用mysql连接到服务器。然后执行CREATE DATABASE语句,并指定新的数据库名字:

mysql> CREATE DATABASE sampdb;

在创建可进入或对其内容进行操作的表之前,必须先创建sampdb数据库。

你可能会希望在创建某个数据库的同时,让它成为默认(或当前)数据库。但这是行不通的。看看下面这条检查默认数据库的语句,你就会明白这一点:

mysql> SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| NULL     |
+------------+

NULL表示“未选择到数据库”。如果想要把sampdb设置为默认选择数据库,那么还需要执行一条USE语句:

mysql> USE sampdb;
mysql> SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| sampdb   |
+------------+

另一种选择默认数据库的办法是,调用mysql的时侯在命令行上给该数据库取一个名字:

% mysql sampdb

事实上,这就是在选择数据库时用得最多的办法。如果还需要使用连接参数,那么可以在命令行指定它们。例如,下面的命令可以让用户sampadm连接到本地主机(如果未指定主机名字,则默认是它)上的sampdb数据库:

% mysql -p -u sampadm sampdb

如果需要连接到运行于远程主机上的MySQL服务器,那么需要在命令行指定该主机:

% mysql -h cobra.example.com -p -u sampadm sampdb

如果无特别说明,后面所有的示例都将假定:当调用mysql时,命令行里指定的默认数据库就是sampdb。如果在调用mysql时忘记在命令行指定数据库,那么请在mysql>提示符处输入一条USE sampdb语句。

本节将创建示例数据库sampdb所需要的那些表。首先,创建“美史联盟”场景所需要的表,然后,再为“成绩考评”项目创建所需的表。有些数据库书讲到在此处便会开始讨论“数据库的分析与设计”、“实体联系图”、“规范化过程”(Normalization Procedure)等内容。有很多书专门讲解这些内容,所以本书在这里只想说明我们的数据库应该是个什么样子,具体来讲涉及这样几个方面:它应该包含哪些表、每个表都应包含什么内容,以及在决定如何表示这些数据时需要考虑哪些问题。

这里所选择的表示方式并不是绝对的。在其他场合,你可以选用不同的方式来表示相似的数据。到底选择哪一种,需要由应用程序和数据的具体用途来决定。

1.4.6.1 美史联盟表

美史联盟的表相当简单,包括以下两个表。

1.president

president表包含一些与美国历任总统生平相关的基本信息。

2. member

从每条记录都保存着单独某个人的个人资料这一角度来看,存放“美史联盟”成员列表的member表与刚才介绍的president表很相似。只是member表的每一行还包含了以下这些列。

3. 创建美史联盟表

现在,我们准备创建美史联盟表。为此,我们需要使用CREATE TABLE语句,该语句具有以下格式:

CREATE TABLE tbl_name (column_specs);

其中,tbl_name指的是你要给表指定的名字;column_specs指的是该表的各列。该语句还会包括各种索引的定义,如果有的话。索引能够加快查找速度,关于这一点请参考第5章的介绍。

下面是针对president表的CREATE TABLE语句:

CREATE TABLE president
(
 last_name VARCHAR(15) NOT NULL,
 first_name VARCHAR(15) NOT NULL,
 suffix   VARCHAR(5) NULL,
 city    VARCHAR(20) NOT NULL,
 state   VARCHAR(2) NOT NULL,
 birth   DATE NOT NULL,
 death   DATE NULL
);

执行此语句的方法有两种:可以手动输入,也可以使用sampdb发行包中create_president.sql文件里包含的预先编写好的语句。

如果想要自已输入这条语句,那么先调用mysql,同时将sampdb设置为默认数据库:

% mysql sampdb

接着,输入上面的CREATE TABLE语句。请记得在语句末尾输入分号,以便将这条语句的结束位置告知mysql程序。没有缩进格式也没有关系,你不用像上面那样换行。你完全可以在同一行输入整条语句。

如果想用预先编写好的描述来创建president表,那么可以使用sampdb发行包里的create_president.sql文件。当你解压发行包时,解压程序会自动创建一个sampdb目录,并将那个文件放在这个目录里。先进入该目录,然后执行下面这条命令:

% mysql sampdb < create_president.sql

无论你采用何种方式调用mysql,都请记得在命令行的命令名后面指定你可能需要的连接参数(其中包括主机名、用户名和密码)。

现在让我们来仔细看一下CREATE TABLE语句。该语句会为每一列指定列名、数据类型(列要存放的值的类型),以及可能有的某些列属性。

president表用到了两种数据类型:VARCHARDATEVARCHAR(n)表示的是:该列可以存放长度可变的字符型值,且最大长度为n个字符。也就是说,它们包含了长度不定的字符串,但其长度存在上限。那个n的值即表明了你所期望的数据长度。例如,将state定义为VARCHAR(2)类型,即表示我们需要以两个字母的缩写形式输入州名。而其他值为字符串类型列,其数据长度必须要再长一点才能容纳下更长的值。

我们用到的另一个数据类型是DATE。很显然,此类型表示该列用于保存日期值。不过,日期的表示格式可能会让你感到意外。MySQL要求将日期表示为'CCYY-MM-DD'格式,其中的CCYYMMDD分别代表了世纪、世界的年份、月和日。这也是SQL标准规定的日期表示格式(也叫做ISO 8601格式)。例如,想要在MySQL里指定“2013年7月18日”这样一个日期,则需要使用'2013-07-18',而不能用'07-18-2013''18-07-2013'

president表里,我们用到的列属性只有NULL(可以没有值)和NOT NULL(必须要有值)。大部分数据列的属性都为NOT NULL,因为我们要求它们必须要有值。可以有NULL属性的两列是:suffix(大部分总统的姓名都无后缀)和death(如果总统还健在,则无逝世日期)。

对于member表,其CREATE TABLE语句如下:

CREATE TABLE member
(
 member_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
 PRIMARY KEY (member_id),
 last_name VARCHAR(20) NOT NULL,
 first_name VARCHAR(20) NOT NULL,
 suffix   VARCHAR(5) NULL,
 expiration DATE NULL,
 email   VARCHAR(100) NULL,
 street   VARCHAR(50) NULL,
 city    VARCHAR(50) NULL,
 state   VARCHAR(2) NULL,
 zip    VARCHAR(10) NULL,
 phone   VARCHAR(20) NULL,
 interests VARCHAR(255) NULL
);

与之前一样,你可以手动将这些语句输入mysql中,也可以利用预先编写好的文件。发行包sampdb中的creat_member.sql文件包含了member表的CREATE TABLE语句。要使用它,需要执行以下命令:

% mysql sampdb < create_member.sql

就数据类型而言,member表里的大部分列(有两个例外)的类型都很普通,都是可变长度的字符串类型。列member_idexpiration是例外,它们分别保存的是序号和日期。

使用成员资格编号列member_id的主要考虑是:为了避免成员之间产生混淆,其中每一个值都应该是唯一的。此时,AUTO_INCREMENT列正好可以派上用场,因为当我们添加新成员时,MySQL便能为我们自动生成一个唯一编号。虽然member_id列只是包含数字,但其定义却包含有好几个部分。

PRIMARY KEY子句表示为member_id列创建索引,这样能加快查找速度。同时,它还设置了约束:要求该列里的所有值都必须唯一。后面这个属性正好符合对成员ID值的要求,因为它可以防止我们两次误用相同的ID。此外,MySQL要求AUTO_INCREMENT列必须有某种索引,如果没有索引,那么表的定义就是不合法的。(任何一个PRIMARY KEY列也必须是NOT NULL的,因此就算我们在member_id定义里省略了NOT NULL,MySQL也会自动加上。)

如果你还不太明白AUTO_INCREMENTPRIMARY KEY是怎么回事,那么可以把它们想象成一种能生成索引ID号的神奇魔法。这些值到底是什么并不重要,只要这些ID号对每位成员来说都是唯一的就可以了。(关于AUTO_INCREMENT列的更多信息请参考第3章。)

expiration列的数据类型是DATE。它允许值为NULL,且默认也为NULL,即表示可以不输入日期。正如前面所提到的,我们将使用这样一个约定:当expiration值为NULL时,表明该成员拥有终身成员资格。

到目前为止,你已经让MySQL创建了两个表,现在让我们来确认一下结果是否正确。在mysql里,调用下面这条命令可以查看president表的结构:

mysql> DESCRIBE president;
+------------+-------------+------+-----+---------+-------+
| Field    | Type     | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| last_name | varchar(15) | NO   |    | NULL  |    |
| first_name | varchar(15) | NO   |    | NULL  |    |
| suffix   | varchar(5) | YES  |    | NULL  |    |
| city    | varchar(20) | NO   |    | NULL  |    |
| state   | varchar(2) | NO   |    | NULL  |    |
| birth   | date     | NO   |    | NULL  |    |
| death   | date     | YES  |    | NULL  |    |
+------------+-------------+-------+-----+---------+-------+

调用DESCRIBE member语句,则可以看到有关member表的类似信息。(如果你想知道为什么未定义为允许NULLDefault列会显示NULL,那么我在这里告诉你,那是因为NULL也可用于表明该列没有显式的DEFAULT子句。)

如果你不记得一个表中的列名、想知道其数据类型,或者想了解其宽度是多少,那么可以使用DESCRIBE。你还可以利用它来查看MySQL在表行里存储各列的先后顺序。当你执行INSERTLOAD DATA语句时,这个顺序很重要,因为这些语句要求各列的值是以它们的默认列顺序列出的。

你也可以通过其他方式来获得DESCRIBE生成的信息。既可以是简写的DESC,也可以是EXPLAIN语句或SHOW语句。下面这些语句具有相同的作用:

DESCRIBE president;
DESC president;
EXPLAIN president;
SHOW COLUMNS FROM president;
SHOW FIELDS FROM president;

这些语句还允许对输出列加以限制。例如,可以在SHOW语句的末尾加上一个LIKE子句,这样便只能看到与给定模式相匹配的那些列的信息:

mysql> SHOW COLUMNS FROM president LIKE '%name';
+------------+-------------+------+-----+---------+-------+
| Field   | Type      | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| last_name | varchar(15) | NO  |    |      |    |
| first_name | varchar(15) | NO  |    |      |    |
+------------+-------------+------+-----+---------+-------+

使用DESCRIBE president '%name'也可以得到同样的结果。这里用到的百分号(%)是一个特殊的通配符,我们将在后面的1.4.9 .7节中介绍它。

SHOW FULL COLUMNSSHOW COLUMNS很像,不同之处在于它会显示附加的列信息。你可以现在试一下,看看结果。

SHOW语句还有其他几种形式,可用于从MySQL获得不同类型的信息。SHOW TABLES语句会列出默认数据库里的表。到目前为止,我们已在数据库sampdb里创建了两个表,因此执行该语句将得到如下输出:

mysql> SHOW TABLES;
+------------------+
| Tables_in_sampdb |
+------------------+
| member       |
| president     |
+------------------+

SHOW DATABASES语句会列出当前连接到的服务器上的所有数据库:

mysql> SHOW DATABASES;
+--------------------+
| Database       |
+--------------------+
| information_schema |
| mysql        |
| sampdb        |
| test          |
+--------------------+

这个列表可能会因服务器不同而有所差异,但是应该能看到information_schemasampdb。其中,information_schema是一个事先存在的特殊数据库;而sampdb则是刚才创建的。另外还有一个数据库test,它是MySQL在安装过程中创建的。如果你的访问权限足够高,你可能还会看到名为mysql的数据库,它是一个权限分配表。

客户端程序mysqlshow提供了一个命令行接口,通过它所获得的信息和使用SHOW语句所显示的一样。请记住:当你运行mysqlshow时,还需要提供正确的命令行选项,其中包括用户名、密码和主机名。这些选项与你在运行mysql时所用的一样。

当不带参数时,mysqlshow会显示出一个数据库列表:

% mysqlshow
+--------------------+
|   Databases    |
+--------------------+
| information_schema |
| mysql        |
| sampdb        |
| test         |
+--------------------+

在带上数据库名之后,mysqlshow会列出给定数据库里的所有表:

% mysqlshow sampdb
Database: sampdb
+-----------+
| Tables   |
+-----------+
| member   |
| president |
+-----------+

如果同时带上数据库名和表名,那么mysqlshow会显示出该表里各列的信息,这时等同于SHOW FULL COLUMNS语句。

1.4.6.2 成绩考评项目表

要想确定成绩考评项目需要用到哪些表,得先弄清在用纸质成绩册来记录考生成绩时可能会怎么做。图1-2展示了成绩册中一页的内容。该页的主体部分是用于记录分数的表格。该页里面还包含了其他一些让分数有意义的信息。学生的姓名和ID号列在表格的左侧。(为了简洁,这里只列出了4位学生。)考试或测验的举行日期则列在表格的顶部。从该表格可以看出:在9月的3、6、16、23日有测验;在9月9日和10月1日有考试。

图1-2 成绩册示例

为使用数据库持续跟踪这些信息,我们需要一个score(分数)表。那么,在这个表里应该包含哪些行呢?此问题不难回答。在每一行里,需要列出学生姓名、考试或测验日期,以及学生的考试分数。图1-3展示了成绩册里的部分分数在表里的表现形式。(其中,日期是按MySQL的方式来表现的,即'CCYY-MM-DD'格式。)

图1-3 最初的score

可惜的是,采用这种方式得到的表遗漏了一些信息。例如,请仔细看看图1-3中的各行,它还未清楚地表明该成绩是考试分数还是测验分数。一般来说,在评定的期末总成绩时,考试分数与测验分数的权重是有明显区别的,因此有必要将考分类别标识出来。当然,我们也可以根据某给定日期的分数范围(在数值上,测验分数通常要比考试分数低很多)来推测其类型,但这种不采用具体数据明确标识的做法会带来问题。

可以在每行记录里将各类考分区别开来,具体方法就是:给score表增加一列,用TQ分别表示考试(test)或测验(quiz),如图1-4所示。这种做法的优点是考分类别能直接体现在数据上;缺点是显得有些冗余。看看那些日期相同的行就能发现,考分类别栏里的值都是相同的。所有9月23日的考分都属于Q类,所有10月1日的考分都属于T类。没人会喜欢这个样子。如果按这种办法来记录学生们的考试分数,那么我们不仅要反复输入相同的日期,而且还要多次输入一个相同的考分类别。天啊!谁会愿意输入这么多的冗余信息呢?

图1-4 修改后包含分数类型的score

我们再来试试另一种表示方式。这次不把考分类别放到score表里,而是将它与考试日期对应起来。我们可以把考试日期列出来,然后用它来跟踪在各个日期发生过什么样的“考试事件”(包括测验和考试)。这样,我们就可以根据score表里的日期在grade_event表里查出当天的考试事件类型,从而得知某个分数是来自测验,还是来自考试。将score表里的日期与grade_event表进行匹配,便能获得考试的类别。图1-5列出了这个表的结构,并展示了它与score表之间是如何与9月23日那天关联起来的。通过将该行与grade_event表里的相应行相匹配,我们便能看出该分数是一次测验成绩。

图1-5 score表与grade_event表,通过日期链接

与通过推测来判断考分类别的做法相比,这种方法更好一些。因为现在能够从记录在数据库里的数据直接得出考试分数的类型。与将考分类别直接记录在score表里的做法相比,这种方法也更易让人接受。现在,我们只需记录一次考分类别,再也不用为每个考分都记录一次了。

不过,我们现在需要把多个表的信息进行组合。如果你是我,那么在第一次听说这种事的时候,或许会想:“嘿,这个主意真不错。但是,有这么多的表,想查什么东西会不会太费事?这会不会把事情搞得更复杂了?”

在某种程度上,这种担心是有道理的,它需要做更多的工作。记录两个表肯定会比记录一个表复杂。但是,请再仔细看看当初的成绩册(如图1-2所示),你不是已经在记录两组信息了吗?请看下面两个事实。

换句话说,尽管你可能还未意识到这一点,但你在成绩册里所做的事,与我把信息放到两个表里的做法并无多大差异。唯一的区别在于,纸质成绩册里的两类信息没有明确地分离开来。

成绩册里的页的概念体现了我们对信息的思维方式,同时这也表明:把信息妥善地放到数据库里并不是一件简单的事情。在日常生活中,人们习惯于把不同信息综合起来,然后把它们当作一个整体来考虑。但数据库无法那样工作,这也正是它们看起来不太自然的原因。我们习惯把信息统一起来,这也使得我们有时很难清楚地分辨出自己所拥有的信息是有多种类型,还是只有一种。正因如此,“以数据库系统的方式进行思考”,考虑如何表现数据才具有挑战性。

图1-5里的grade_event表还隐含了这样一个要求:所有的日期必须唯一,因为每个日期要用于链接scoregrade_event两个表里的各个行。换句话说,不能在同一天进行两场测验,或者一次测验加一次考试。如果这样做的话,那么对于同一个日期,在score表里将会出现两组记录,而在grade_event表里也会有两条记录,而且你也无法说清如何将score中的这些行与grade_event中的那两行进行匹配。

假如你每天最多只进行一场考试,那么这个问题就绝不会出现。但是,可否假设这一情况永远不会发生呢?似乎可行。毕竟,心地善良的你应该不会对学生过于苛刻,要对他们每天进行两场考试。不过,我还是会经常听到有人声称:对于他们的数据,“这种奇怪的情况永远不会发生”。然而,事实证明,这种奇怪的情况偶尔也会出现;而这时为了弥补这一奇怪情况所引发的各种问题,你便不得不重新设计相关表。

最好能防患于未然,事先想好如何处理这些问题。因此,我们现在假设:你有时也会需要在同一天记录两组分数。这一问题该如何解决呢?事实证明,这个问题并不是那么难以解决。只需对有关数据的布局结构作一点小小的改动,就能处理在同一天存在多个事件的情况了,如下所示。

(1)在grade_event表里增加一列,用它来为该表里的每一行分配一个唯一的编号。这样,每次事件就都拥有自己的ID编号了,我们可以将这一列命名为event_id。这种做法看起来有点奇怪,不过在图1-2的成绩册里其实已经隐式地用到了这个属性:这里的事件ID与成绩册分数表格里的列序号是相当的。虽说这个列序号未被显式地写出来,并标注为“event ID”,但它实际就是列序号。

(2)当把各个分数放到score表中的时候,记录事件ID,不记录日期。

完成上述改动之后,我们会得到图1-6所示的结果。现在要用event_id来链接score表和grade_event表,不再使用date了。使用grade_event表不仅能查出每个考分的类别,还能查出它具体发生的日期。还有,在grade_event表里,具有唯一性的不再是日期,而是事件ID。这意味着,在同一天可以进行多次考试和测验(在听到这个消息之后,你的学生肯定会欣喜若狂),而且你也能把它们直接记录下来。

图1-6  score表与grade_event 表,通过事件ID链接

必须承认,图1-6里的表结构不如前面的那几个看起来顺眼。score表变得更加抽象,因为它包含的列越来越让人看不懂。请看图1-4里的score表,里面既有考试日期又有考分类别,让人一眼即能看明白。但在图1-6所示的score表里,那两列都不见了,我们看到的是一个高度抽象化的信息表示形式。谁愿意看一个只包含“事件ID”的score表呢?它毫无意义。

此时此刻,我们到了一个十字路口。大家之前还对电子化的成绩考评系统充满希望,觉得很快就能从繁琐的评分工作中解脱出来。可是,在了解到“在数据库里分数信息实际上是如何表示的”之后,你却因这些信息表示起来相当抽象和分散而开始踌躇不前。

这自然会引出一个问题:“也许MySQL不适合我。不用数据库会不会好些?”想必大家已猜到了,我对此持反对意见。原因很简单,你看本书的厚度就知道了。但是,当你在考虑如何开始某项工作之前,多考虑几种情况,以及想一下“如果使用像MySQL这样的数据库系统,或者使用其他像电子表格程序那样的工具,事情是否会变得更好”,这些都是不错的做法。对比情况如下。

另一方面,如果你想只对一部分数据进行操作(如只统计测验分数,或者只统计考试分数),进行对比分析(如男生与女生的对比),或者想以灵活方式显示统计信息,那么情况就有所不同了。这些工作不是电子表格所擅长的,而关系数据库系统却能轻易地完成。

另外一个需要考虑的地方是:在关系数据库里表示具有抽象和分散特性的数据,也不是什么大问题。在数据库建立之初,仔细考虑好信息在数据库里的表示方式是很有必要的,这样你才能按照最符合你目标的方式来安排你的数据。不过,确定好如何表示信息之后,你便需要依赖数据库引擎来收集数据,并把它以一种对你来讲很有意义的方式呈现出来。这样,你所看到的就不会是一组分散的数据块了。

例如,当从score表检索分数时,你想要看考试日期,而不想看到事件ID。这点很容易做到。数据库将在grade_event表里根据事件ID查出考试日期,并显示在你面前。你可能还想要知道考试分数是属于测验的,还是属于考试的。这点也很容易做到。数据库可以采用同样的方式(根据事件ID)查出考分类别。别忘了,像MySQL这类的关系数据库系统最擅长的就是:将一个事物与另一样事物进行关联,从而在多个信息源里把你最想知道的信息提取出来。在成绩考评这个示例里,MySQL会负责考虑通过事件ID将信息汇集到一起,你不必去关心其中的细节。

现在,为了提前让大家了解到如何让MySQL实现这种事物之间的关联,假设你想要查看2012年9月23日的考试分数。下面这个查询可以将指定日期的考试分数查出来:

SELECT score.name, grade_event.date, score.score, grade_event.category
FROM score INNER JOIN grade_event
ON score.event_id = grade_event.event_id
WHERE grade_event.date = '2012-09-23';

相当可怕,哈?这个查询通过将score表中的各行和event表中的各行进行连接(关联),检索得出学生姓名、考试日期、考试分数和考分类别等信息。结果如下所示:

+--------+------------+-------+----------+
| name  | date     | score | category |
+--------+----------- +-------+----------+
| Billy | 2012-09-23 |  15 | Q     |
| Missy | 2012-09-23 |  14 | Q     |
| Johnny | 2012-09-23 |  17 | Q     |
| Jenny | 2012-09-23 |  19 | Q     |
+--------+------------+-------+----------+

是不是觉得上面这个表格有点面熟?你应该很熟悉,它与图1-4里的表格布局是一样的。你不必知道事件ID就能获得结果。你只要指定你感兴趣的那个日期,然后MySQL便会根据这个日期将考试分数找出来。因此,当从数据库里以某种对我们很意义的形式将信息提取出来的时候,如果你还在担心那种抽象和分散是否会让我们迷失,那么到这个时候你应该可以看到根本不会出现这种情况。

当然,在仔细查看该查询之后,你或许又会产生一些新的疑问。换句话说,这个查询看起来又长又复杂。只是要查出某一天的考试分数,就要写得这么复杂?没错,它是有点复杂。不过,在每次你想要调用某个查询时,也有很多方法可以避免输入占用很多行的SQL语句。一般情况下,在执行完某个类似这样的查询之后,你可以将它保存起来;然后,在必要时你便可以轻易地重复使用它。关于这一做法的更多内容请参考1.5节。

为了让大家对查询过程先有所了解,我提前展示了这个示例。事实上,与我们真正用来检索考试分数的查询相比,它算是简单的。因为我们还需要对表的结构再做一次较大的改动。首先,我们将score表里的学生姓名替换成具有唯一性的学生ID。(也就是说,我们将使用成绩册里“ID”栏的值,而不使用“名称”栏里的值。)我们还要另外新建一个名为student的表,其中包含学生姓名(name)和学号(student_id)两列(见图1-7)。

图1-7 score表、student表和grade_event表,通过学生ID和事件ID链接

为什么要做这样的改动呢?只为解决可能会出现的两名学生同名的情况。使用唯一的学生ID编号有助于把他们的分数区别开来。这与我们不用日期而是使用唯一的事件ID来区分同一天进行考试和测试的分数是一样的道理。在对表的结构做了上述改动之后,根据指定日期查询考试分数的语句又复杂了一点:

SELECT student.name, grade_event.date, score.score, grade_event.category
FROM grade_event INNER JOIN score INNER JOIN student
ON grade_event.event_id = score.event_id
AND score.student_id = student.student_id
WHERE grade_event.date = '2012-09-23';

如果你现在为看不懂这个查询命令而担心,那么请放松。大部分人都看不懂。在本教程的后半部分,我们还会遇到这个查询命令,不过前后的差异在于后面的那个版本会让你眼前一亮。真的,不开玩笑。

从图1-7里可以看到,student表里增加了一些在成绩册里没有的内容:它多了一个用于记录性别的列。可以利用这个列来统计班级里男女生的人数;也可以用它来做一些复杂的事情,如比较男女生的成绩。

至此,我们几乎完成了成绩考评项目所有表的创建。最后还需再增加一个表,用来记录考勤情况。这个表的内容相对比较简单,只包含一个学生ID和一个日期(见图1-8)。这个表的每一行都代表了一位在指定日期有缺席的学生。到期末的时候,我们将利用MySQL的统计功能来对表里的数据进行汇总,从而统计出每位学生的缺勤天数。

图1-8 absence

1.student

现在,我们知道了那些成绩考评表的样子,下面来创建它们。用于创建student表的CREATE TABLE语句如下:

CREATE TABLE student
(
 name    VARCHAR(20) NOT NULL,
 sex    ENUM('F','M') NOT NULL,
 student_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
 PRIMARY KEY (student_id)
) ENGINE = InnoDB;

请注意观察,我在CREATE TABLE语句里加入了一些新的内容(在末尾加上了ENGINE子句)。稍后我会解释它的用途。

你可以在mysql客户端程序里输入上面的CREATE TABLE语句,也可以在命令行里执行下列命令:

% mysql sampdb < create_student.sql

这条CREATE TABLE语句将创建一个名为student的表,其中包含三列:namesexstudent_id

name是一个长度可变的字符串列,它最多可以存储20个字符。这里的名字表示比“美史联盟”表里的简单,它只使用了一个单列,并没有将名和姓单独分开。这样做的原因在于:我事先知道成绩考评查询示例不需要用到多个列来表示名字。(没错,这是骗人的。我承认!实际上你可能需要使用多个列。)

sex用于表明某位学生是男生还是女生。这是一个ENUM(枚举)列,其取值只能是在该列的规范里列出的那些值当中的一个:'F'代表女生,'M'代表男生。当你想把某列的可取值限定在某个有限集合内时,ENUM会非常管用。当然,我们也可以把该列定义为CHAR(1),但ENUM可以让被允许的列值更加明确。如果你忘了它都有哪些可取值,就可以调用DESCRIBE命令来查看。MySQL会列出ENUM列的合法枚举值:

mysql> DESCRIBE student 'sex';
+-------+---------------+------+-----+---------+-------+
| Field | Type      | Null | Key | Default | Extra |
+-------+---------------+------+-----+---------+-------+
| sex  | enum('F','M') | NO   |   |      |    |
+-------+---------------+------+-----+---------+-------+

ENUM列的值不一定非得是单个字符。例如,还可以把sex列定义为:ENUM ( 'female', 'male')

student_id是一个整型列,用于保存唯一的学生ID编号。通常情况下,ID编号来源于某个权威机构,如学校办公室。但本书出于示例目的,将自己编造它们。我们将使用一个AUTO_INCREMENT列,其定义与前面创建member表时所用的member_id列相类似。

如果的确需要从学校办公室获得学生ID编号,而不能自动生成它们,那么在定义student_id列时请不要为它加上AUTO_INCREMENT属性。不过,需要保留PRIMARY KEY子句,其目的在于要避免ID值出现重复或为NULL

现在,CREATE TABLE语句末尾的ENGINE子句有什么作用呢?如果存在这个子句,那么它将在创建新表时, 为MySQL应该使用的存储引擎指定名字。“存储引擎”就是一种用来管理某种表的处理器。MySQL有好几种存储引擎,都各有特色。其中两个最常用的引擎是InnoDB(MySQL 5.5版本的默认引擎)和MyISAM(MySQL 5.5版本之前的默认引擎)。

关于这两个引擎之间的不同之处请参考2.6.1节。现在,只需说明成绩考评项目表的定义显式指定了InnoDB引擎就行了,因为我们需要InnoDB引擎所提供的称为“引用完整性(referential integrity)”的功能。该功能是通过使用外键(foreign key)来实现的。也就是说,我们可以使用MySQL将某些约束施加到两个表之间的相互关系上,这种做法对成绩考评项目的各个表来讲是很有必要的。

为了实施这些约束,我们需要建立一些外键关系。这里的“外”表示的意思是“在另一个表里”,而“外键”指的是必须与另一个表里的某个键值相匹配的键值。随着后面更多成绩考评项目表的创建,这些概念将会变得越来越清晰。

在早些时候,我们在创建美史联盟的表(presidentmember)时,并没有使用ENGINE子句,因此服务器会使用默认存储引擎来创建它们。如前所述,默认存储引擎是InnoDB(除非服务器被重新配置过)。student表的定义显式地包括了ENGINE = InnoDB,防止了服务器为其配置与此不同的默认值。

2. grade_event

grade_event表的定义如下所示:

CREATE TABLE grade_event
(
 date   DATE NOT NULL,
 category ENUM('T','Q') NOT NULL,
 event_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
 PRIMARY KEY (event_id)
) ENGINE = InnoDB;

要创建grade_event表,可以在mysql客户端程序里输入上述CREATE TABLE语句,也可以在命令行上执行下面这条命令:

% mysql sampdb < create_grade_event.sql

date列用于存放标准的MySQL的DATE(日期类型)值,格式为'CCYY-MM-DD'(年在前)。

category表示的是分数类别。与student表里的sex列一样,category也是一个枚举列。其允许的取值是'T''Q',分别代表测试(test)和测验(quiz)

event_id是一个AUTO_INCREMENT列,同时也被定义为了PRIMARY KEY。它与student表里的student_id列类似。利用AUTO_INCREMENT属性,我们能方便地生成唯一事件ID的值。与student表里的student_id列类似,其特定的值并不重要,重要的是它们必须唯一。

因为这些列必须都要有值,所以它们全部被定义成了NOT NULL

3.score

score表的创建语句如下所示:

CREATE TABLE score
(
 student_id INT UNSIGNED NOT NULL,
 event_id  INT UNSIGNED NOT NULL,
 score   INT NOT NULL,
 PRIMARY KEY (event_id, student_id),
 INDEX (student_id),
 FOREIGN KEY (event_id) REFERENCES grade_event (event_id),
 FOREIGN KEY (student_id) REFERENCES student (student_id)
) ENGINE = InnoDB;

这个表的定义又包含了新内容:FOREIGN KEY结构。我们稍后会讲到它。

要创建score表,可以在mysql客户端程序里输入上面的语句,也可以在命令行里执行下面的命令:

% mysql sampdb < create_score.sql

score是一个INT列,用于保存整型分数值。如果想要保存像58.5那样带有小数部分的分数,那么最好使用能表示它们的数据类型,如DECIMAL

student_id列和event_id列都是整型列,它们分别表示每一个考试分数所对应的学生和考试事件。通过它们与student表和grade_event表里的相应ID值链接起来,我们就能够查出学生姓名和考试日期。关于student_id列和event_id列有两个要点需要注意一下。

那个PRIMARY KEY定义可以确保我们不会创建重复的分数行。而FOREIGN KEY定义可以确保在我们的记录行不会有虚假的ID值,即要求它们必须存在于grade_event表和student表里。

为什么student_id列会有一个索引呢?这是因为,对于FOREIGN KEY定义里的任何列,都应该有一个关于它们的索引,或者它们应该是某个多列索引里被首先列出的列,这样能加快查找速度。对于event_id列的FOREIGN KEY,该列被优先列在PRIMARY KEY里。对于student_id列的FOREIGN KEY,则无法使用PRIMARY KEY,因为student_id列未被首先列出来。因此,我们需要在student_id列上单独创建一个索引。

如有必要,InnoDB存储引擎会自动为出现在外键定义里的那些列创建一个索引,但它使用的索引定义不一定是你所期望的(更多有关信息请参考2.13节)。显示地定义这个索引可以避免这一问题。

4.absence

absence表用于记录学生的考勤情况,其创建语句如下所示:

CREATE TABLE absence
(
 student_id INT UNSIGNED NOT NULL,
 date    DATE NOT NULL,
 PRIMARY KEY (student_id, date),
 FOREIGN KEY (student_id) REFERENCES student (student_id)
) ENGINE = InnoDB;

要创建absence表,可以在mysql客户端程序里输入上述语句,也可以在命令行上执行以下命令:

% mysql sampdb < create_absence.sql

student_id列和date列都定义为NOT NULL,这样可以防止有缺失值。为了避免出现重复行,我们将这两列的组合定义为一个主键(primary key)。在同一天统计两次学生缺勤情况肯定是不公平的,对吧?

absence表也包含有一个外键关系,其目的在于确保每一个student_id值都与student表里的一个student_id值相匹配。

在成绩考评项目的各个表里设置外键关系,可以在让这些约束条件在数据录入阶段发挥作用,如我们只想插入那些包含合法考试事件ID值和学生ID值的记录行。不过,外键关系还有另外一种效果。它们会形成某些依赖关系,让你按照一定的顺序来创建和删除表,如下所示。

至此,我们的数据库和表就都创建好了。接下来,我们需要往表里添加一些行。不过,在往表里放入某些内容之后,得能知道如何检查表里的内容,因此,尽管有关检索操作的详细介绍要在1.4.9节才会讲到,此时也至少应该知道下面这条语句是用来查看tbl_name表里的全部内容的:

SELECT * FROM tbl_name;

例如:

mysql> SELECT * FROM student;
Empty set (0.00 sec)

现在,mysql报告说该表为空,但在练习完本节的几个示例之后,你会看到不一样的结果。

往数据库里添加数据的办法有好几种。可以用INSERT语句将行手工插到表中,也可以利用文件把行添加到表里。该文件的内容既可以是一系列事先编写好的能直接提供给mysqlINSERT语句,也可以是通过LOAD DATA语句或mysqlimport客户端程序来加载的原始数据值。

本节将演示各种把记录插到表中的方法。大家应该多练习这些方法,熟悉和掌握它们的工作原理以及用法。在练习完这些方法之后,再转到1.4.8节,运行那里的命令。那些命令可以用来删除这些表,然后再重建它们,并将本书提供的数据加载到这些表里。这样,你的数据库所包含的内容就会与我在后面示例中用到的数据保持一致,而你在练习本书其他示例时也会看到相同的结果。如果你已知道如何插入行,那么可以直接跳过本节。

1.4.7.1 利用INSERT添加行

我们先使用INSERT语句来添加行,这是一条SQL语句,你可用它来指定要插入数据行的那个表,以及要插入的数据行和该行的各个列值。INSERT语句有多种格式。

(1)一次性指定全部列值。语法如下:

INSERT INTO tbl_name VALUES(value1, value2, ...);

例如:

mysql> INSERT INTO student VALUES('Kyle', 'M', NULL);
mysql> INSERT INTO grade_event VALUES('2012-09-03', 'Q', NULL);

在使用此语法时, VALUES列表必须包含表中每一列的值,并且值的顺序要与各列在表里的存储顺序保持一致。(通常情况下,该顺序就是各列在该表的CREATE TABLE语句里指定的顺序。)如果你不太确定列的顺序,那么可以利用DESCRIBE tbl_name语句来查明。

在MySQL里,你可以使用单引号或双引号将字符串和日期值括起来,不过使用单引号会更标准些。NULL值对应于student表和grade_event表里的AUTO_INCREMENT列。在AUTO_INCREMENT列里插入一个“缺失值”(missing value),可以让MySQL为该列自动生成下一个序号。

MySQL还支持使用一条INSERT语句,同时指定多个值列表的方式,将多个行插入一个表里:

INSERT INTO tbl_name VALUES(...),(...),... ;

例如:

mysql> INSERT INTO student VALUES('Avery','F',NULL),('Nathan','M',NULL);

与使用多条INSERT语句的方式相比,这种方式不仅能让你少打字,还能提高服务器的执行效率。请注意,将每行各列的值括起来的那对括号不可少。下列语句是非法的,因为它括号内包含的列值的个数不正确。

mysql> INSERT INTO student VALUES('Avery','F',NULL,'Nathan','M',NULL);
ERROR 1136 (21S01): Column count doesn't match value count at row 1

(2)命名赋值列,并列出它们的值。当你创建的行只有少数几列需要初始化时,这种方式特别有用。语法如下:

INSERT INTO tbl_name (col_name1,col_name2,...) VALUES(value1,value2,...);

例如:

mysql> INSERT INTO member (last_name,first_name) VALUES('Stein','Waldo');

这种形式的INSERT语句也可以一次插入多个值列表:

mysql> INSERT INTO student (name,sex) VALUES('Abby','F'),('Joseph','M');

对于没在INSERT语句中指定的列,将被赋予默认值。例如,上面两条语句都未给member_idevent_id赋值,因此MySQL会将默认值NULL赋给它们。又因为member_idevent_id都是AUTO_INCREMENT列,因此最后的结果是这两列都会被分别赋予各自的下一个序号,这与显式地将NULL赋给它们是一样的。

(3)使用一系列的“列/值”形式进行赋值。此语法使用SET子句实现,其中包含多个col_name= value的赋值形式,没有使用VALUES()列表的形式。

INSERT INTO tbl_name SET col_name1=value1, col_name2=value2, ... ;

例如:

mysql> INSERT INTO member SET last_name='Stein',first_name='Waldo';

对于没在SET子句里指定的列,将被赋予默认值。这种形式的INSERT语句无法用于一次插入多个行的情形。

既然已对INSERT语句的工作原理有所了解,那么现在便用它来检查一下,我们所建立的外键关系是否真的能够防止将不规范的行录入score表和absence表里。试着在grade_event表和student表里找几个不存在的ID值,然后插入几条分别包含这些值的行:

mysql> INSERT INTO score (event_id,student_id,score) VALUES(9999,9999,0);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key
constraint fails (`sampdb`.`score`, CONSTRAINT `score_ibfk_1` FOREIGN
KEY (`event_id`) REFERENCES `grade_event` (`event_id`))
mysql> INSERT INTO absence SET student_id=9999, date='2012-09-16';
ERROR 1452 (23000): Cannot add or update a child row: a foreign key
constraint fails (`sampdb`.`absence`, CONSTRAINT `absence_ibfk_1`
FOREIGN KEY (`student_id`) REFERENCES `student` (`student_id`))

有错误消息出现,即表明这些约束发挥了作用。

1.4.7.2 利用文件添加新行

另一种把行载入表中的方法是,直接从文件里读取它们。该文件可以包含INSERT语句或原始数据。例如,在sampdb发行版里就有一个名为insert_president.sql的文件,它包含一系列用于将新行添加到president表里的INSERT语句。如果你是在与该文件相同的目录里,那么你可以像下面那样直接执行这些语句:

% mysql Sampdb < insert_president.sql

如果你已经运行了mysql,那么可以用一条source命令来读取这个文件:

mysql> source insert_president.sql;

如果文件里存储的行不是INSERT语句而是原始数据,那么可以利用LOAD DATA语句或客户端程序mysqlimport来加载它们。

LOAD DATA语句是一个从文件里读取数据的批量加载程序。它需要在mysql里运行:

mysql> LOAD DATA LOCAL INFILE 'member.txt' INTO TABLE member;

假设member.txt文件位于客户端主机的当前目录里,那么上面这条语句会读取该文件,并将其内容发送至服务器,加载到member表里。member.txt文件可在sampdb发行版里找到。

默认情况下, LOAD DATA语句会假设各列的值是以制表符分隔的,各行末尾都是换行符。同时,假设这些值的顺序都与表里存储的列的顺序相同(文件里的\N值表示的是NULL)。你也可以用它来读取其他格式的文件,或者指定不同的列顺序。有关LOAD DATA的更多细节请参考附录E。

LOAD DATA语句里的关键字LOCAL会引发客户端程序(在本示例里,指的是mysql)读取数据文件,并把文件内容发送到服务器进行加载。如果省略了LOCAL,那么数据文件必须存在于服务器主机上,并且你需要拥有FILE服务器访问权限(大部分MySQL用户都没有这样的权限)。另外,你还要指定完整的文件路径,以便服务器能找到它。

如果在LOAD DATA语句里使用LOCAL时遇到以下错误信息,那么很可能是因为在默认情况下LOCAL功能被禁用了:

ERROR 1148 (42000): The used command is not allowed with this MySQL version

可以在mysql之后加上--local-infile选项再试一次。例如:

% mysql --local-infile sampdb
mysql> LOAD DATA LOCAL INFILE 'member.txt' INTO TABLE member;

如果这招也不管用,那么说明服务器在启动时需要带上--local-infile选项。

另一种加载数据文件的方法是在命令提示符里使用客户端程序mysqlimport。它会为你生成一条LOAD DATA语句:

% mysqlimport --local sampdb member.txt

与程序mysql的用法一样,请根据需要在命令行里指定连接参数,并把它们放置在那个数据库名字的前面,紧挨着它。

对于上面这条命令, mysqlimport程序将生成一条能将member.txt文件里的内容加载到member表里的LOAD DATA语句。这是因为mysqlimport程序是根据数据文件的名字来确定表名的,同时它会把文件名中第一个句号(.)之前的所有内容都当作表名。例如,mysqlimport会把名为member.txtpresident.txt的文件分别加载到member表和president表里。这意味着,你应该仔细挑选数据文件名,否则,mysqlimport将无法使用正确的表名。如果想要加载文件member1.txtmember2.txt,那么mysqlimport会认为是要把这两个文件分别加载到名为member1member2的表里去。如果你真的想将这两个文件加载到member表里,那么可以分别将它们命名为member.1.txtmember.2.txt,或者member.txt1member.txt2

在练习完成上面介绍的这几种添加行的方法之后,为了顺利进行后面的学习,你应该重新建立和加载sampdb数据库里的各个表,把整个数据库恢复为原样。请在包含sampdb发布版文件的目录下,运用mysql程序来执行下面这些语句:

% mysql sampdb
mysql> source create_member.sql;
mysql> source create_president.sql;
mysql> source insert_member.sql;
mysql> source insert_president.sql;
mysql> DROP TABLE IF EXISTS absence, score, grade_event, student;
mysql> source create_student.sql;
mysql> source create_grade_event.sql;
mysql> source create_score.sql;
mysql> source create_absence.sql;
mysql> source insert_student.sql;
mysql> source insert_grade_event.sql;
mysql> source insert_score.sql;
mysql> source insert_absence.sql;

如果不想单独输入这么多条语句,那么在Unix系统上,可以执行下面这条命令:

% sh init_all_tables.sh sampdb

而在Windows系统上,可以执行下面这条命令:

C:\> init_all_tables.bat sampdb

无论使用哪条命令,如果需要在命令行里指定连接参数,那么请把它们放到命令名的后面,且紧挨着它。

现在,我们的表都建好了,并且加载了数据。下面一起来看看如何使用这些数据。使用SELECT语句可以检索和显示表里的信息。你可以根据自己的需要以常规或特定的方式来检索信息。例如,可以把表里的所有内容都显示出来:

SELECT * FROM president;

也可以只显示很少的数据,如一行中的一列:

SELECT birth FROM president WHERE last_name = 'Eisenhower';

SELECT语句拥有几个子句,你可以根据需要组合它们,用于检索你感兴趣的信息。这些子句可以很简单,也可以很复杂,因此语句SELECT也会随之变得简单或者复杂。不过,本书中绝对没有整页长的、需要花费一个钟头才能搞明白的查询语句。当我遇到长长的(arm-length)查询语句时,通常会跳过它们,我想你也会这样做。

SELECT语句的简化语法如下:

SELECT what to retrieve
FROM table or tables
WHERE conditions that data must satisfy;

在写SELECT语句时,需要先指定检索的内容,然后再加上一些可选的子句。上面显示的两个子句(FROMWHERE)是最为常见的,尽管还可以指定其他子句,如GROUP BYORDER BYLIMIT。请记住,SQL语言对语句格式并没有严格的要求,因此写你自己的SELECT语句时,不必严格像本书示例那样换行排列。

FROM子句通常是不可少的,但当你不需要给出表名时,可以省略它。例如,下面这条查询语句只显示一些表达式的值。这些值的计算并未涉及任何表,因此这里不需要FROM子句:

mysql> SELECT 2+2, 'Hello, world', VERSION();
+-----+--------------+------------+
| 2+2 | Hello, world | VERSION() |
+-----+--------------+------------+
|  4 | Hello, world | 5.5.30-log |
+-----+--------------+------------+

当的确需要使用FROM子句来指定要从哪个表检索数据时,还需要指明要查看哪些列。SELECT语句最常见的一种形式是使用一个星号(*)作为列说明符,代表“所有列”。下面这条查询语句将显示student表里的所有列:

mysql> SELECT * FROM student;
+-----------+------+------------+
| name    | sex  | student_id |
+-----------+------+------------+
| Megan   | F   |     1  |
| Joseph  | M   |     2  |
| Kyle   | M   |     3  |
| Katie   | F   |     4  |
...

这些列将按它们在表里的存储顺序依次显示出来。这个顺序与你用DESCRIBE student语句看到的列顺序是一致的。(示例末尾处的省略号“...”表示该查询返回的行实际上有很多。)

也可以把自己想要查看的那些列的名字列出来。例如,只想查看学生姓名,则可以这样做:

mysql> SELECT name FROM student;
+-----------+
| name   |
+-----------+
| Megan   |
| Joseph  |
| Kyle   |
| Katie   |
...

如果要列出多个列名,那么需要使用逗号把它们分隔开。下面这条语句等价于SELECT * FROM student语句,但它把各列的名字明确地列了出来:

mysql> SELECT name, sex, student_id FROM student;
+-----------+-----+------------+
| name   | sex | student_id |
+-----------+-----+------------+
| Megan   | F  |     1 |
| Joseph  | M  |     2 |
| Kyle   | M  |     3 |
| Katie   | F  |     4 |
...

你可以按任意顺序列出各个列名:

SELECT name, student_id FROM student;
SELECT student_id, name FROM student;

只要你愿意,甚至还可以重复列出某一列的名字,只是这样做通常没什么意义。

另外,还可以从一个以上的表里选取列,即多表“连接”(join)。更多关于连接的信息请参考1.4.9.10节。

一方面,在MySQL里,列名不区分大小写,因此下面这些检索语句都是等价的:

SELECT name, student_id FROM student;
SELECT NAME, STUDENT_ID FROM student;
SELECT nAmE, sTuDeNt_Id FROM student;

另一方面,数据库名和表名可能是区分大小写的。具体情况取决于服务器主机所使用的文件系统,以及MySQL的配置。Windows系统的文件名不区分大小写,所以运行在它上面的服务器也不区分数据库名和表名的大小写。在Unix系统上,文件名通常都区分大小写,因此运行在它上面的服务器会区分数据库名和表名的大小写。Mac OS X系统的扩展文件系统比较特殊,它不区分大小写。

如果想让MySQL服务器不区分数据库名和表名的大小写,那么可以对它进行配置。更多详细信息请参考11.2.6节。

1.4.9.1 指定检索条件

要想限制SELECT语句检索出来的行数,可以使用WHERE子句,指定列值所必须满足的检索条件。例如,可以搜索某个范围内的数值:

mysql> SELECT * FROM score WHERE score > 95;
+------------+----------+-------+
| student_id | event_id | score |
+------------+----------+-------+
|     5  |    3  |  97 |
|     18  |    3  |  96 |
|     1  |    6  |  100 |
|     5  |    6  |  97 |
|     11  |    6  |  98 |
|     16  |    6  |  98 |
+------------+----------+-------+

可以查找包含字符数据的字符串值。对于默认的字符集和排序方式,字符串的比较操作通常不区分大小写:

mysql> SELECT last_name, first_name FROM president
  -> WHERE last_name='ROOSEVELT';
+-----------+-------------+
| last_name | first_name |
+-----------+-------------+
| Roosevelt | Theodore  |
| Roosevelt | Franklin D. |
+-----------+-------------+
mysql> SELECT last_name, first_name FROM president
  -> WHERE last_name='roosevelt';
+-----------+-------------+
| last_name | first_name |
+-----------+-------------+
| Roosevelt | Theodore  |
| Roosevelt | Franklin D. |
+-----------+-------------+

也可以查找日期:

mysql> SELECT last_name, first_name, birth FROM president
  -> WHERE birth < '1750-1-1';
+------------+------------+------------+
| last_name | first_name | birth   |
+------------+------------+------------+
| Washington | George   | 1732-02-22 |
| Adams    | John     | 1735-10-30 |
| Jefferson | Thomas    | 1743-04-13 |
+------------+------------+------------+

甚至还可以查找组合值:

mysql> SELECT last_name, first_name, birth, state FROM president
  -> WHERE birth < '1750-1-1' AND (state='VA' OR state='MA');
+------------+------------+------------+-------+
| last_name | first_name | birth   | state |
+------------+------------+------------+-------+
| Washington | George   | 1732-02-22 | VA  |
| Adams    | John     | 1735-10-30 | MA  |
| Jefferson  | Thomas   | 1743-04-13 | VA  |
+------------+------------+------------+-------+

WHERE子句里的表达式允许使用算术运算符(见表1-1)、比较运算符(见表1-2)和逻辑运算符(见表1-3)。在表达式里还可以使用括号。在运算时,可以使用常量、表列和函数调用。本教程的语句里会用到一些MySQL的函数,但由于函数比较多,这里无法一一列出。有关这些函数的详细信息请参考附录C。

表1-1 算术运算符

运算符

含  义

+

加法

-

减法

*

乘法

/

除法

DIV

整除

%

模运算(除法余数)

表1-2 比较运算符

运算符

含  义

<

小于

<=

小于等于(不大于)

=

等于

<=>

等于(可用于NULL值)

<>!=

不等于

>=

大于等于(不小于)

>

大于

表1-3 逻辑运算符

运算符

含  义

AND

逻辑与

OR

逻辑或

XOR

逻辑异或

NOT

逻辑非

当需要在查询语句里使用逻辑运算符时,千万要注意:逻辑运算符AND与人们日常生活中所说的“和”在含义上是不一样的。假设你想要找出“出生于弗吉尼亚州和马萨诸塞州的总统”。这里用到了“和”字,它似乎是在暗示你该编写如下查询语句:

mysql> SELECT last_name, first_name, state FROM president
  -> WHERE state='VA' AND state='MA';
Empty set (0.01 sec)

空的结果清楚表明,这条语句没起作用。为什么没起作用呢?因为这条查询语句的真正含义是“把同时出生于弗吉尼亚州和马萨诸塞州的总统”找出来,而这是不可能的。在日常生活里,你可以用“和”来表达你的查询条件;但在SQL里,必须使用OR来连接这两个条件:

mysql> SELECT last_name, first_name, state FROM president
  -> WHERE state='VA' OR state='MA';
+------------+-------------+-------+
| last_name | first_name  | state |
+------------+-------------+-------+
| Washington | George    | VA  |
| Adams    | John      | MA  |
| Jefferson | Thomas    | VA  |
| Madison  | James     | VA  |
| Monroe   | James     | VA  |
| Adams   | John Quincy  | MA  |
| Harrison  | William H.  | VA  |
| Tyler   | John      | VA  |
| Taylor   | Zachary    | VA  |
| Wilson   | Woodrow    | VA  |
| Kennedy  | John F.    | MA  |
| Bush    | George H.W.  | MA  |
+------------+-------------+-------+

请大家务必注意日常语言与SQL语言之间的差异,不只是在自己编写查询语句时要引起注意,在为其他人编写查询时也要注意。一定要仔细倾听别人对查询内容的描述,不能将他们的描述照搬成SQL的逻辑运算符。以刚描述的那个查询为例,与查询语句相当的自然语言表述应该是:“把出生于弗吉尼亚州或马萨诸塞州的总统找出来”。

当在组织可以查找到多个独立值的查询语句时,你可能会发现,使用IN()运算符更简洁。在使用IN()之后,前面的那个查询可以改写成下面这个样子:

SELECT last_name, first_name, state FROM president
WHERE state IN('VA','MA');

当把一个列与大量值进行比较时,使用IN()会特别方便。

1.4.9.2 NULL

NULL值很特殊。其含义是“无值”或“未知值”,所以不能采用两个“已知值”的比较方式,将它与“已知值”进行比较。如果试图将NULL与常规的算术比较运算符一起使用,那么其结果将是未定义的(undefined):

mysql> SELECT NULL < 0, NULL = 0, NULL <> 0, NULL > 0;
+----------+----------+-----------+----------+
| NULL < 0 | NULL = 0 | NULL <> 0 | NULL > 0 |
+----------+----------+-----------+----------+
|   NULL |   NULL  |   NULL |   NULL  |
+----------+----------+-----------+----------+

事实上,你也不能让NULL与其自身进行比较,因为两个“未知值”的比较结果是无法确定的:

mysql> SELECT NULL = NULL, NULL <> NULL;
+-------------+---------------+
| NULL = NULL | NULL <> NULL  |
+-------------+---------------+
|    NULL  |     NULL   |
+-------------+---------------+

如果需要测试多个NULL值是否相等,那么必须使用IS NULLIS NOT NULL,而不能使用=<>或者!=。例如,对于目前仍然健在的美国总统,其逝世日期在president表里表示为NULL。如果想要找到他们,可以使用下面这条查询语句:

mysql> SELECT last_name, first_name FROM president WHERE death IS NULL;
+-----------+-------------+
| last_name | first_name |
+-----------+-------------+
| Carter  | James E.  |
| Bush   | George H.W. |
| Clinton  | William J. |
| Bush   | George W.  |
| Obama   | Barack H.  |
+-----------+-------------+

IS NOT NULL可以用来查找非NULL值。下面这条查询语句可以找到那些具有后缀名的名字:

mysql> SELECT last_name, first_name, suffix
  -> FROM president WHERE suffix IS NOT NULL;
+-----------+------------+--------+
| last_name | first_name | suffix |
+-----------+------------+--------+
| Carter  | James E.   | Jr.  |
+-----------+------------+--------+

MySQL特有的<=>比较运算符可用于NULLNULL的比较。将前面两个查询语句改写成使用这个运算符的查询语句:

SELECT last_name, first_name FROM president WHERE death <=> NULL;

SELECT last_name, first_name, suffix
FROM president WHERE NOT (suffix <=> NULL);

1.4.9.3 对查询结果排序

每位MySQL用户最终都会注意到这样一种情况:在你创建一个表,并存入一些行之后,使用 “SELECT * FROM tbl_name” 语句查询出的行,其顺序通常与插入它们时的顺序一致。这很符合人们的思维习惯,人们自然会认定查询出的行的顺序与插入它们时的顺序是相同的。但实际情况并非这样。因为在表数据初始加载完之后,删除和插入行都会改变服务器返回行的顺序。

关于行检索顺序,请记住这样一条原则:服务器不会保证返回行的先后顺序,除非你自己指定顺序。要想结果有序,需要在查询语句后面增加一条ORDER BY子句。下面这条查询语句会按姓的字母顺序返回美国总统的姓名:

mysql> SELECT last_name, first_name FROM president
  -> ORDER BY last_name;
+------------+---------------+
| last_name | first_name  |
+------------+---------------+
| Adams   | John Quincy  |
| Adams   | John      |
| Arthur   | Chester A.  |
| Buchanan  | James     |
...

ORDER BY子句的默认排序方式是升序排列。在其中的列名后面加上关键字ASCDESC,可以指定是按照升序排列还是按照降序排列。例如,想让美国总统的姓名按姓的逆序(降序)排列显示,那么就要使用DESC关键字:

mysql> SELECT last_name, first_name FROM president
  -> ORDER BY last_name DESC;
+------------+---------------+
| last_name | first_name  |
+------------+---------------+
| Wilson   | Woodrow    |
| Washington | George    |
| Van Buren | Martin    |
| Tyler    | John      |
...

你可以对多列进行排序,而且每一列单独地按升序或降序排列。下面的查询语句用于检索president表里的行,先按出生地所在州进行逆序排列,然后在每一个相同的州里再按姓升序排列:

mysql> SELECT last_name, first_name, state FROM president
  -> ORDER BY state DESC, last_name ASC;
+------------+---------------+-------+
| last_name | first_name  | state |
+------------+---------------+-------+
| Arthur   | Chester A.  | VT  |
| Coolidge  | Calvin    | VT  |
| Harrison  | William H.  | VA  |
| Jefferson | Thomas    | VA  |
| Madison  | James     | VA  |
| Monroe   | James     | VA  |
| Taylor   | Zachary    | VA  |
| Tyler   | John      | VA  |
| Washington | George    | VA  |
| Wilson   | Woodrow    | VA  |
| Eisenhower | Dwight D.   | TX  |
| Johnson  | Lyndon B.   | TX  |
...

在一个列里,对于升序排列,NULL值总是出现在开头;而对于降序排列,它总是出现在末尾。为确保NULL值出现在指定排列顺序的末尾,需要额外增加一个可以区分NULL值和非NULL值的排序列。例如,想按逝世日期降序排列所有总统,那么当前健在的(逝世日期为NULL的)那些总统就应该出现在结果顺序的末尾。而如果想让他们出现在开头,就要使用下面这条查询语句:

mysql> SELECT last_name, first_name, death FROM president
  -> ORDER BY IF(death IS NULL,0,1), death DESC, last_name; 
+------------+---------------+------------+
| last_name | first_name   | death   |
+------------+---------------+------------+
| Bush    | George W.    | NULL    |
| Bush    | George H.W.   | NULL    |
| Carter   | James E.    | NULL    |
| Clinton  | William J.   | NULL    |
| Obama   | Barack H.    | NULL    |
| Ford    | Gerald R.    | 2006-12-26 |
| Reagan   | Ronald W.    | 2004-06-05 |
| Nixon   | Richard M.   | 1994-04-22 |
...
| Adams   | John      | 1826-07-04 |
| Jefferson | Thomas     | 1826-07-04 |
| Washington | George     | 1799-12-14 |
+------------+---------------+------------+

其中, IF()函数的作用是计算第一个参数给出的那个表达式的值,然后根据计算结果的真假来决定是返回第二个参数(为真),还是返回第三个参数(为假)。对于这条查询语句,当遇到NULL值时, IF()函数的计算结果为0;当遇到非NULL值时,它计算结果为1。最终结果会把所有的NULL值放到非NULL值的前面。同时,对于death值相同的行,将last_name作为辅助列按姓继续进行排序。

1.4.9.4 限制查询结果

查询结果往往有很多行,如果只想看到其中的一小部分,那么可以在查询命令里增加一条LIMIT子句。如果将它与ORDER BY子句联合使用,效果会特别好。MySQL允许限制查询输出的行数,只输出结果中前面的n行。下面的查询语句将把按出生日期排在前5位的总统列出来:

mysql> SELECT last_name, first_name, birth FROM president
  -> ORDER BY birth LIMIT 5;
+------------+------------+------------+
| last_name | first_name | birth   |
+------------+------------+------------+
| Washington | George   | 1732-02-22 |
| Adams     | John    | 1735-10-30 |
| Jefferson | Thomas   | 1743-04-13 |
| Madison  | James   | 1751-03-16 |
| Monroe   | James   | 1758-04-28 |
+------------+------------+------------+

如果用DESC来逆序排列查询结果,那么可以得到最晚出生的那5位总统:

mysql> SELECT last_name, first_name, birth FROM president
  -> ORDER BY birth DESC LIMIT 5;
+-----------+--------------+------------+
| last_name | first_name  | birth   |
+-----------+--------------+------------+
| Obama    | Barack H.   | 1961-08-04 |
| Clinton  | William J.  | 1946-08-19 |
| Bush   | George W.   | 1946-07-06 |
| Carter  | James E.   | 1924-10-01 |
| Bush   | George H.W.  | 1924-06-12 |
+-----------+--------------+------------+

LIMIT子句还允许从查询结果的中间抽出部分行。此时需要指定两个值:第一个,给出从查询结果的开头部分跳过的行数目;第二个,需要返回的行数目。下面这条查询语句与前面那条很相似,但它返回的是跳过前面10行之后的5行:

mysql> SELECT last_name, first_name, birth FROM president
  -> ORDER BY birth DESC LIMIT 10, 5;
+-----------+--------------+------------+
| last_name | first_name  | birth   |
+-----------+--------------+------------+
| Eisenhower| Dwight D.    | 1890-10-14 |
| Truman  | Harry S     | 1884-05-08 |
| Roosevelt | Franklin D.  | 1882-01-30 |
| Hoover  | Herbert C.   | 1874-08-10 |
| Coolidge | Calvin     | 1872-07-04 |
+-----------+--------------+------------+

如果想从某个表里随机抽取出一行或几行,那么可以联合使用LIMIT子句和ORDER BY RAND()子句:

mysql> SELECT last_name, first_name FROM president
  -> ORDER BY RAND() LIMIT 1;
+-----------+------------+
| last_name | first_name |
+-----------+------------+
| Johnson  | Lyndon B. |
+-----------+------------+
mysql> SELECT last_name, first_name FROM president
  -> ORDER BY RAND() LIMIT 3;
+-----------+-------------+
| last_name | first_name |
+-----------+-------------+
| Harding  | Warren G.  |
| Bush   | George H.W. |
| Jefferson | Thomas   |
+-----------+-------------+

1.4.9.5 对输出列进行计算和命名

到目前为止,大部分查询语句都是直接通过检索表中的值来获得输出结果。MySQL也支持根据表达式的结果计算输出值,引不引用表都可以。下面的这条查询语句计算了一个简单表达式(一个常量)和一个复杂表达式(它使用了几种算术运算和两个函数调用,这两个函数一个用于计算表达式的平方根,另外一个用于将结果格式化成保留3位小数):

mysql> SELECT 17, FORMAT(SQRT(25+13),3);
+----+-----------------------+
| 17 | FORMAT(SQRT(25+13),3) |
+----+-----------------------+
| 17 | 6.164            |
+----+-----------------------+

在表达式里也可以使用表列,如下所示:

mysql> SELECT CONCAT(first_name,' ',last_name), CONCAT(city,', ',state)
  -> FROM president;
+----------------------------------+-------------------------+
| CONCAT(first_name,' ',last_name) | CONCAT(city,', ',state) |
+----------------------------------+-------------------------+
| George Washington         | Wakefield, VA       |
| John Adams              | Braintree, MA       |
| Thomas Jefferson          | Albemarle County, VA   |
| James Madison           | Port Conway, VA      |
...

这个查询对总统的名字进行了格式化,将名和姓用空格连接成了一个字符串。对他们的出生地也进行了格式,将所在城市和州用逗号和空格连接成了一个字符串。

计算某列的值的表达式会成为该列的名字,并被用作输出结果的标题。如果表达式很长(如前面那个查询示例所示),那么它会使输出列的宽度变得很大。为使输出更具意义和可读性,你可以利用AS name结构为该列分配另一个名字(也称“别名”):

mysql> SELECT CONCAT(first_name,' ',last_name) AS Name,
  -> CONCAT(city,', ',state) AS Birthplace
  -> FROM president;
+-----------------------+-------------------------+
| Name           | Birthplace       |
+-----------------------+-------------------------+
| George Washington   | Wakefield, VA       |
| John Adams       | Braintree, MA       |
| Thomas Jefferson   | Albemarle County, VA   |
| James Madison     | Port Conway, VA       |
...

如果输出列的别名里包含空格,那么必须给它加上引号:

mysql> SELECT CONCAT(first_name,' ',last_name) AS 'President Name',
  -> CONCAT(city,', ',state) AS 'Place of Birth'
  -> FROM president;
+-----------------------+-------------------------+
| President Name     | Place of Birth     |
+-----------------------+-------------------------+
| George Washington   | Wakefield, VA      |
| John Adams       | Braintree, MA      |
| Thomas Jefferson   | Albemarle County, VA  |
| James Madison     | Port Conway, VA      |
...

在为列提供别名时,可以省略关键字AS

mysql> SELECT 1 one, 2 two, 3 three;
+---+-----+-------+
|one| two | three |
+---+-----+-------+
| 1 |  2 |   3 |
+---+-----+-------+

如果一个查询结果的列名有误,或者有列丢失,那么请检查在某两个列之间是否忘了加上逗号。如果真是这种情况,那么第二列会被当成第一列的别名。例如,你原打算编写一个查询语句,选取下列总统的姓名,可惜不小心漏掉了first_namelast_name两列之间的逗号。结果,first_name列被误命名为last_name,而列last_name列却不见了:

mysql> SELECT first_name last_name FROM president; +---------------+
| last_name   |
+---------------+
| George    |
| John     |
| Thomas    |
| James     |
...

1.4.9.6 处理日期

在MySQL里使用日期时,千万要记住的是年份总是在最前面。在写2012年7月27日这个日期时,请将其写成'2012-07-27'。不要像日常生活中那样,将它写成'07-27-2012''27-07-2012'。对于其他格式的输入值,或许能使用STR_TO_DATE()函数来进行转换。具体示例请参考3.2.6节。

MySQL支持多种类型的日期运算。

下面是一些与日期运算有关的查询示例。

为了通过确切值或通过另一日期的相对值,查询出特定的日期来,可以将某个DATE列与你感兴趣的那个日期值进行比较:

mysql> SELECT * FROM grade_event WHERE date = '2012-10-01';
+------------+----------+----------+
| date     | category | event_id |
+------------+----------+----------+
| 2012-10-01 | T     |    6 |
+------------+----------+----------+
mysql> SELECT last_name, first_name, death
  -> FROM president
  -> WHERE death >= '1970-01-01' AND death < '1980-01-01';
+-----------+------------+------------+
| last_name | first_name | death    |
+-----------+------------+------------+
| Truman  | Harry S   | 1972-12-26  |
| Johnson  | Lyndon B.  | 1973-01-22 |
+-----------+------------+-------------+

为测试或检索日期的各个部分,可以使用像YEAR()MONTH()DAYOFMONTH()这样的函数。例如,下面这个查询可以把生于3月的美国总统查找出来:

mysql> SELECT last_name, first_name, birth
  -> FROM president WHERE MONTH(birth) = 3;
+-----------+------------+------------+
| last_name | first_name | birth    |
+-----------+------------+------------+
| Madison  | James    | 1751-03-16 |
| Jackson  | Andrew    | 1767-03-15 |
| Tyler   | John      | 1790-03-29 |
| Cleveland | Grover     | 1837-03-18 |
+-----------+-------------+------------+

也可以用月份名称来改写这个查询:

mysql> SELECT last_name, first_name, birth
  -> FROM president WHERE MONTHNAME(birth) = 'March';
+-----------+------------+------------+
| last_name | first_name | birth    |
+-----------+------------+------------+
| Madison  | James   | 1751-03-16  |
| Jackson  | Andrew   | 1767-03-15  |
| Tyler   | John     | 1790-03-29 |
| Cleveland | Grover    | 1837-03-18 |
+-----------+------------+------------+

再进一步,把函数MONTH()DAYOFMONTH()结合起来使用,找出生于3月某一天的总统:

mysql> SELECT last_name, first_name, birth
  -> FROM president WHERE MONTH(birth) = 3 AND DAYOFMONTH(birth) = 29;
+-----------+------------+------------+
| last_name | first_name | birth   |
+-----------+------------+------------+
| Tyler   | John     | 1790-03-29 |
+-----------+------------+------------+

你偶尔会看到类似“今日名人”之类的信息,用上面的查询语句就能生成一份这样的名单。不过,如果你的查询与“当前日期”有关,那么大可不必像前面的例子那样插入一个具体的日期值。不管今日是一年里的哪一天,只要将各位总统的生日与CURDATE()函数(该函数总是返回当前日期)里的月和日进行比较,便可查出“今日出生的”总统,如下所示:

SELECT last_name, first_name, birth
FROM president WHERE MONTH(birth) = MONTH(CURDATE())
AND DAYOFMONTH(birth) = DAYOFMONTH(CURDATE());

如果想知道两个日期值之间的时间间隔,可以将它们相减。例如,想要知道哪位总统活得最久,那么可以用他们的逝世日期减去出生日期。此时,TIMESTAMPDIFF()函数就派上用场了,因为它有一个参数,可以指定计算结果的单位(在本例里为年,即YEAR):

mysql> SELECT last_name, first_name, birth, death,
  -> TIMESTAMPDIFF(YEAR, birth, death) AS age
  -> FROM president WHERE death IS NOT NULL
  -> ORDER BY age DESC LIMIT 5;
+-----------+------------+------------+------------+------+
| last_name | first_name | birth    | death   | age |
+-----------+------------+------------+------------+------+
| Reagan  | Ronald W. | 1911-02-06 | 2004-06-05 |  93 |
| Ford   | Gerald R. | 1913-07-14 | 2006-12-26 |  93 |
| Adams   | John    | 1735-10-30 | 1826-07-04 |  90 |
| Hoover  | Herbert C. | 1874-08-10 | 1964-10-20 |  90 |
| Truman  | Harry S  | 1884-05-08 | 1972-12-26 |  88 |
+-----------+------------+------------+------------+------+

如果想要计算相差的天数,则需要使用另一种计算两个日期之间时间间隔的方法:使用TO_DAYS()函数,将日期转换为天数。检测出与某个参考日期相差有多少天是该函数的一种主要应用。例如,为找出近期需要更新成员资格的“美史联盟”成员,可以将成员的有效日期与当前日期相减。如果其结果小于某个阈值,即表明该成员的资格快要到期了。下面这条查询语句可以将成员资格已过期的以及在60天内将到期的成员查找出来:

SELECT last_name, first_name, expiration FROM member
WHERE (TO_DAYS(expiration) - TO_DAYS(CURDATE())) < 60;

使用TIMESTAMPDIFF()函数的等效语句,如下所示:

SELECT last_name, first_name, expiration FROM member
WHERE TIMESTAMPDIFF(DAY, CURDATE(), expiration) < 60;

要根据某个日期计算出另一个日期,可以使用函数DATE_ADD()DATE_SUB()。这两个函数的参数都是一个日期值和一个时间间隔,然后返回一个新的日期值。例如:

mysql> SELECT DATE_ADD('1970-1-1', INTERVAL 10 YEAR);
+----------------------------------------+
| DATE_ADD('1970-1-1', INTERVAL 10 YEAR) |
+----------------------------------------+
| 1980-01-01                  |
+----------------------------------------+
mysql> SELECT DATE_SUB('1970-1-1', INTERVAL 10 YEAR);
+----------------------------------------+
| DATE_SUB('1970-1-1', INTERVAL 10 YEAR) |
+----------------------------------------+
| 1960-01-01                  |
+----------------------------------------+

在本节里的前面有一个查询,它选取那些逝世于20世纪70年代的美国总统,使用文字量型的日期值作为选取范围的结束点。可以重写该查询语句,使用一个文字量型的起点日期,然后在起点日期上加上一个时间间隔计算得出终点日期:

mysql> SELECT last_name, first_name, death
  -> FROM president
  -> WHERE death >= '1970-1-1'
  -> AND death < DATE_ADD('1970-1-1', INTERVAL 10 YEAR);
+-----------+------------+------------+
| last_name | first_name | death   |
+-----------+------------+------------+
| Truman  | Harry S   | 1972-12-26 |
| Johnson  | Lyndon B.  | 1973-01-22 |
+-----------+-------------+------------+

另外还有一种编写“成员资格更新”查询的方法,即使用DATE_ADD()函数:

SELECT last_name, first_name, expiration FROM member
WHERE expiration < DATE_ADD(CURDATE(), INTERVAL 60 DAY);

如果expiration列经过了索引,那么这个查询将会比前一个更有效率。具体原因请参考第5章。

大概在本章开头,有下面这样一个查询,用于找出“在牙医诊所的患者中,有哪些患者在到期后还未来复诊”:

SELECT last_name, first_name, last_visit FROM patient
WHERE last_visit < DATE_SUB(CURDATE(), INTERVAL 6 MONTH);

或许当时你还不太能看懂这个查询。那么现在是否已完全明白了呢?

1.4.9.7 模式匹配

MySQL支持模式匹配操作,这使我们能够在未给出精确比较值的情况下把行查出来。模式匹配需要使用像LIKENOT LIKE那样的运算符,并且需要指定一个包含通配字符的字符串。下划线“_”可以匹配任何的单个字符,百分号“%”则能匹配任何字符序列(其中包括空序列)。

下面这个模式能匹配到所有以字母Ww开头的姓:

mysql> SELECT last_name, first_name FROM president
  -> WHERE last_name LIKE 'W%';
+------------+------------+
| last_name | first_name |
+------------+------------+
| Washington | George   |
| Wilson   | Woodrow  |
+------------+------------+

下面这个查询展示了一种常见错误。这个“模式匹配”不会返回任何内容,因为它没有使用带LIKE的模式,而是使用了带算术比较运算符的模式。

mysql> SELECT last_name, first_name FROM president
  -> WHERE last_name = 'W%';
Empty set (0.00 sec)

上面这个比较表达式唯一成功的地方是该列正好包含了字符串'W%''w%'

下面这个模式将与那些包含有'W%''w%'(并不仅限于第一个字母)的姓相匹配:

mysql> SELECT last_name, first_name FROM president
  -> WHERE last_name LIKE '%W%';
+------------+------------+
| last_name | first_name |
+------------+------------+
| Washington | George   |
| Wilson   | Woodrow  |
| Eisenhower | Dwight D. |
+------------+------------+

下面这个模式将与恰好包含4个字母的姓相匹配,如下所示:

mysql> SELECT last_name, first_name FROM president
  -> WHERE last_name LIKE '____';
+-----------+-------------+
| last_name | first_name |
+-----------+-------------+
| Polk   | James K.  |
| Taft   | William H. |
| Ford   | Gerald R.  |
| Bush   | George H.W. |
| Bush   | George W.  |
+-----------+-------------+

MySQL还提供另一种基于正则表达式(regular expression)和REGEXP运算符的模式匹配。关于LIKEREGEXP的更多信息请参考3.5.1.1节和附录C。

1.4.9.8 设置和使用自定义变量

MySQL支持你定义自己的变量。这些变量可以被设置为查询结果,这使我们可以方便地把一些值存储起来以供今后的查询使用。假设想知道在Andrew Jackson总统之前出生的总统有哪些。可以先将他的出生日期检索出来,并存储到一个变量里,然后再将出生日期早于该变量值的其他总统查找出来:

mysql> SELECT @ Jackson _ birth := birth FROM president
  -> WHERE last_name = 'Jackson' AND first_name = 'Andrew';
+------------------------+
| @jackson _ birth := birth |
+------------------------+
| 1767-03-15       |
+------------------------+
mysql> SELECT last_name, first_name, birth FROM president
  -> WHERE birth < @jackson _ birth ORDER BY birth;
+------------+------------+------------+
| last_name | first_name | birth    |
+------------+------------+------------+
| Washington | George   | 1732-02-22 |
| Adams    | John    | 1735-10-30 |
| Jefferson | Thomas   | 1743-04-13 |
| Madison  | James   | 1751-03-16 |
| Monroe   | James   | 1758-04-28 |
+------------+------------+------------+

自定义变量的语法为“@变量名”。在SELECT语句里,赋值语法是形如“@变量名:=”的表达式。因此,上面的第一个查询主要负责把Andrew Jackson总统的出生日期查出来,并把它赋给一个名为@jackson_birth的变量。这条SELECT语句的查询结果仍会被显示出来;而将查询结果赋给变量的过程并不会阻碍该查询的输出显示。第二个查询会引用该变量,并在president表里找出birth值小于该变量值的那些行。

实际上,前面的那个问题可以通过一条使用连接或子查询的查询语句来解决,但这里不想对此有过多的讨论。有时,使用一个变量可能会更容易让人理解。更多关于子查询的信息请参考1.4.9.10节。

也可以使用SET语句来对变量进行赋值。此时,“=”和“:=”都可以用作赋值运算符:

mysql> SET @today = CURDATE();
mysql> SET @one_week_ago := DATE_SUB(@today, INTERVAL 7 DAY);
mysql> SELECT @today, @one_week_ago;
+------------+---------------+
| @today   | @one_week_ago |
+------------+---------------+
| 2012-04-21 | 2012-04-14  |
+------------+---------------+

1.4.9.9 生成统计信息

MySQL最有用的一项功能就是,能够对大量原始数据进行归纳和统计。大家都明白,单纯依靠人工手段来生成统计信息是一项既枯燥耗时,又易出错的工作。如果大家能学会使用MySQL来生成各种统计信息,那么它将会成为你的得力助手。

在一组值里把各个唯一值找出来,这是一项典型的统计工作。可以使用DISTINCT关键字清除查询结果里重复出现的行。例如,下面的查询可以将美国历任总统出生地所在的州不加重复地列举出来:

mysql> SELECT DISTINCT state FROM president ORDER BY state;
+-------+
| state |
+-------+
| AR  |
| CA  |
| CT  |
| GA  |
| HI  |
| IA  |
| IL  |
| KY  |
| MA  |
| MO  |
...

另一种形式的统计是计数,需要使用COUNT()函数。如果使用COUNT(*),那么计算出来的结果将是查询所选取到的行数。如果查询语句没有带WHERE子句,那么它会查询所有行,因此,COUNT(*)计算出来的结果就是表的行数。下面这个查询可以显示出“美史联盟”的member表里包含多少行:

mysql> SELECT COUNT(*) FROM member;
+----------+
| COUNT(*) |
+----------+
|   102 |
+----------+

如果查询语句带有WHERE子句,那么COUNT(*)计算出来的结果就是该子句匹配到了多少行。下面这个查询可以显示出“到目前为止,你的班级已进行了多少次测验”:

mysql> SELECT COUNT(*) FROM grade_event WHERE category = 'Q';
+----------+
| COUNT(*) |
+----------+
|    4 |
+----------+

COUNT(*)函数会统计所有被查询到的行数。与之相对的是,COUNT(列名)只会统计所有非NULL值的数目。下面这个查询展示了两个函数的区别:

mysql> SELECT COUNT(*), COUNT(email), COUNT(expiration) FROM member;
+----------+--------------+-------------------+
| COUNT(*) | COUNT(email) | COUNT(expiration) |
+----------+--------------+-------------------+
|   102  |      80  |        96  |
+----------+--------------+-------------------+

从上面的查询结果可以看出, member表目前共有102行,其中只有80行在email列里有值。它同时还显示出有6名成员具有终身成员资格。(expiration列里的NULL值表示具有终身成员资格,并且由于在102条记录里有96条不为NULL,因此剩下的6条必然属于终身成员。)

组合使用COUNT()DISTINCT,可以统计出在查询结果里有多少个不同的非NULL值。例如,想要知道美国共有多少个不同的州曾经诞生过总统,那么可以使用下面这条查询语句:

mysql> SELECT COUNT(DISTINCT state) FROM president;
+-----------------------+
| COUNT(DISTINCT state) |
+-----------------------+
|          21  |
+-----------------------+

你可以对某个数据列进行全面统计,也可以对该列进行分类统计。例如,使用下面这个查询,可以确定出班级里总共有多少名学生:

mysql> SELECT COUNT(*) FROM student;
+----------+
| COUNT(*) |
+----------+
|    31 |
+----------+

不过,班级里的男生和女生分别是多少呢?有一种办法可以找到答案,即按性别分别进行统计:

mysql> SELECT COUNT(*) FROM student WHERE sex='f';
+----------+
| COUNT(*) |
+----------+
|    15 |
+----------+
mysql> SELECT COUNT(*) FROM student WHERE sex='m';
+----------+
| COUNT(*) |
+----------+
|    16 |
+----------+

这个办法可行,但比较麻烦,而且很不适合于有多种不同列值的情形。假设如何采用这种方式来确定出生自美国各个州的总统人数。首先,你必须一个不少地把涉及的州全部找出来(使用查询语句SELECT DISTINCT state FROM president),然后,再针对各州执行查询语句SELECT COUNT(*)来统计出最终结果。显然这是你想避免的事情。

幸运的是,只用一个查询便可以统计出某一列里的不同值分别出现过多少次。对于那个学生列表,可以使用GROUP BY子句来分别统计男、女学生的人数,如下所示:

mysql> SELECT sex, COUNT(*) FROM student GROUP BY sex;
+-----+----------+
| sex | COUNT(*) |
+-----+----------+
| F  |    15  |
| M  |    16  |
+-----+----------+

使用同样的查询形式,可以分别统计出生自各州的总统人数,如下所示:

mysql> SELECT state, COUNT(*) FROM president GROUP BY state;
+-------+----------+
| state | COUNT(*) |
+-------+----------+
| AR  |    1 |
| CA  |    1 |
| CT  |    1 |
| GA  |    1 |
| HI  |    1 |
| IA  |    1 |
| IL  |    1 |
| KY  |    1 |
| MA  |    4 |
| MO  |    1 |
...

在采用这种方式进行分组统计时, GROUP BY子句会告知MySQL在统计之前应该如何对值进行分组。

与分别统计某列的不同值所出现次数的做法相比,将COUNT(*)函数与GROUP BY子句结合在一起用于进行分组统计的做法有很多优点。

前两项优点的重要性体现在:它们有助于简化查询语句的表达。第3项优点的重要性则体现在:它能以更灵活的方式显示查询结果。默认情况下,MySQL会根据GROUP BY子句里的列名来对查询结果进行排序,但你也可以用ORDER BY子句指定一个特定的排序顺序。例如,你想获得按出生地所在州分组后的总统人数,并按人数从多到少的顺序排列出来,于是可以多加一个ORDER BY子句,如下所示:

mysql> SELECT state, COUNT(*) AS count FROM president
  -> GROUP BY state ORDER BY count DESC;
+-------+-------+
| state | count |
+-------+-------+
| VA  |   8 |
| OH  |   7 |
| MA  |   4 |
| NY  |   4 |
| NC  |   2 |
| VT  |   2 |
| TX  |   2 |
| GA  |   1 |
| IL  |   1 |
| SC  |   1 |
...

如果用于排序的列是由某个汇总函数产生的,那么不能直接在ORDER BY子句里引用该函数。而是应该先为该列取一个别名,然后再在ORDER BY子句里引用这个别名。上面那个查询就是这样做的,其中的COUNT(*)列的别名为count

要使用GROUP BY子句来对某个计算列的结果进行分组,需要使用别名或列位置来引用它,具体实现方法与ORDER BY相类似。下面这个查询可以确定出在一年的每个月分别有多少位总统出生:

mysql> SELECT MONTH(birth) AS Month, MONTHNAME(birth) AS Name,
  -> COUNT(*) AS count
  -> FROM president GROUP BY Name ORDER BY Month;
+-------+----------+--------+
| Month | Name   | count  |
+-------+----------+-------+
|   1 | January  |   4 |
|   2 | February |   4 |
|   3 | March   |   4 |
|   4 | April   |   4 |
|   5 | May    |   2 |
|   6 | June   |   1 |
|   7 | July   |   4 |
|   8 | August  |   5 |
|   9 | September |   1 |
|  10 | October  |   6 |
|  11 | November |   5 |
|  12 | December |   3 |
+-------+-----------+-------+

COUNT()函数可以与ORDER BYLIMIT组合在一起使用。例如,想要在president表里找出哪4个州出生的总统最多,那么可以使用下面这条查询语句:

mysql> SELECT state, COUNT(*) AS count FROM president
  -> GROUP BY state ORDER BY count DESC LIMIT 4;
+-------+-------+
| state | count |
+-------+-------+
| VA  |   8 |
| OH  |   7 |
| MA  |   4 |
| NY  |   4 |
+-------+-------+

如果不是想用LIMIT子句来限制查询结果中的记录条数,而是想把COUNT()的某些特定值找出来,那么需要用到HAVING子句。该子句与WHERE相类似,它们都可用来设定输出行所必须满足的查询条件。与WHERE子句的不同之处在于,它可以引用像COUNT()那样的汇总函数输出的结果。下面这个查询会告诉你“哪些州曾经出现过两位及以上的总统”:

mysql> SELECT state, COUNT(*) AS count FROM president
  -> GROUP BY state HAVING count > 1 ORDER BY count DESC;
+-------+-------+
| state | count |
+-------+-------+
| VA  |   8 |
| OH  |   7 |
| MA  |   4 |
| NY  |   4 |
| NC  |   2 |
| VT  |   2 |
| TX  |   2 |
+-------+-------+

一般情况下,带有HAVING子句的查询语句,特别适合于查找在某个数据列里重复出现的值。也可用于查找不重复出现的值,此时使用HAVING count = 1即可。

COUNT()以外,还有其他几个汇总函数。函数MIN()MAX()SUM()AVG()可分别用于确定某个数据列的最小值、最大值、总计和平均值。你甚至可以同时在一个查询语句里使用它们。下面这个查询可以显示出已进行过的每次考试或测验的各种数值特征。它也会显示出有多少分数参与了各个值的计算。(可能有的学生缺勤或未被统计。)

mysql> SELECT
  -> event_id,
  -> MIN(score) AS minimum,
  -> MAX(score) AS maximum,
  -> MAX(score)-MIN(score)+1 AS span,
  -> SUM(score) AS total,
  -> AVG(score) AS average,
  -> COUNT(score) AS count
  -> FROM score
  -> GROUP BY event_id;

+----------+---------+---------+------+-------+---------+-------+
| event_id | minimum | maximum | span | total | average | count |
+----------+---------+---------+------+-------+---------+-------+
|    1 |    9 |   20 |  12 |  439 | 15.1379 |  29 |
|    2 |    8 |   19 |  12 |  425 | 14.1667 |  30 |
|    3 |   60 |   97 |  38 | 2425 | 78.2258 |  31 |
|    4 |    7 |   20 |  14 |  379 | 14.0370 |  27 |
|    5 |    8 |   20 |  13 |  383 | 14.1852 |  27 |
|    6 |   62 |   100 |  39 | 2325 | 80.1724 |  29 |
+----------+---------+---------+------+-------+---------+-------+

很明显,如果从中还能明确地知道event_id列的值是表示考试还是表示测验,那么这些信息会更具有意义。想要获得该信息,还需要查询grade_event表。关于此操作的更多细节请参考1.4.9.10节。

如果想要生成额外的输出行,显示出“统计结果的统计”,那么还需要增加一条WITH ROLLUP子句。它会让MySQL计算各分组行的“超集”(super-aggregate)值。这里有个简单的示例,它是基于先前那个按性别统计学生人数的示例改进的。WITH ROLLUP子句将生成另外一行,对两类性别的人数进行汇总:

mysql> SELECT sex, COUNT(*) FROM student GROUP BY sex WITH ROLLUP;
+-----+----------+
| sex | COUNT(*) |
+-----+----------+
| F  |    15 |
| M  |    16 |
| NULL|    31 |
+-----+----------+

分组列里的NULL表明,相应的计数结果就是其前面那些分组统计的汇总值。

WITH ROLLUP子句还可以与其他聚集函数搭配使用。下面这条语句,除了像前面几个段落那样可以对考试成绩进行了汇总以外,还可以产生一个额外的超集行:

mysql> SELECT
  -> event_id,
  -> MIN(score) AS minimum,
  -> MAX(score) AS maximum,
  -> MAX(score)-MIN(score)+1 AS span,
  -> SUM(score) AS total,
  -> AVG(score) AS average,
  -> COUNT(score) AS count
  -> FROM score
  -> GROUP BY event_id WITH ROLLUP; 
+----------+---------+---------+------+-------+---------+-------+
| event_id | minimum | maximum | span | total | average | count |
+----------+---------+---------+------+-------+---------+-------+
|    1 |    9 |   20 |  12 |  439 | 15.1379 |  29  |
|    2 |    8 |   19 |  12 |  425 | 14.1667 |  30  |
|    3 |   60 |   97 |  38 | 2425 | 78.2258 |  31  |
|    4 |    7 |   20 |  14 |  379 | 14.0370 |  27  |
|    5 |    8 |   20 |  13 |  383 | 14.1852 |  27  |
|    6 |   62 |   100 |  39 | 2325 | 80.1724 |  29  |
|   NULL |    7 |   100 |  94 | 6376 | 36.8555 |  173  |
+----------+---------+---------+------+-------+---------+--------+

在上面这个输出里,最后一行显示出了一些聚集值,它们都是根据其前面的全部分组统计值计算出来的。

WITH ROLLUP子句很有用,因为它可以让你不必为了获得一些额外的信息,而执行另外一条查询语句。只用一条查询语句就能达到目的,当然效率会更高,因为服务器无需对数据进行两次检查。如果GROUP BY子句指定了多列,那么WITH ROLLUP还会再生成其他的超集行,其中会包含更高层的汇总值。

1.4.9.10 从多个表里检索信息

到目前为止,我们查询出来的信息都是来自一个表。不过,MySQL的能耐远不止于此。前面说过,DBMS的威力在于它们可以把源自多个表的信息结合起来,从而解答那些只靠单个表而无法解答的问题。本节将介绍如何编写涉及多个表的查询语句。

在从多个表中查询信息时,有一种类型的操作叫连接(join)。之所以叫这个名字,是因为必须把一个表与另一个表中的信息连接起来才能得到结果。此操作是通过匹配多个表里的公共值实现的。另一种类型的多表操作是将一条SELECT语句嵌套在另一条SELECT语句里使用。这种嵌套的SELECT语句叫子查询(subquery)。本节将对这两种类型的操作进行介绍。

先一起来看一个关于连接的例子。1.4.6.2节给出了一个用来检索给定日期考试或测验分数的查询命令,但在那里并未对它进行解释。现在可以对它进行解释了。那条查询语句实际上涉及了一个三方的连接操作,因此我们将它分成两步来实现。第一步,构造一条可以查出给定日期的分数的查询语句:

mysql> SELECT student_id, date, score, category
  -> FROM grade_event INNER JOIN score
  -> ON grade_event.event_id = score.event_id
  -> WHERE date = '2012-09-23';
+-------------+-------------+--------+----------+
| student_id  | date      | score  | category |
+-------------+------------+---------+----------+
|     1  | 2012-09-23 |  15   | Q     |
|     2  | 2012-09-23 |  12   | Q      |
|     3  | 2012-09-23 |  11   | Q     |
|     5  | 2012-09-23 |  13   | Q     |
|     6  | 2012-09-23 |  18   | Q     |
...

这个查询先查出给定日期('2012-09-23' )的grade_event行,再利用此行里的事件ID把score表里拥有同一事件ID的分数都查询出来。对于grade_eventscore两个表里相匹配的每一个行组合,把其中的学生ID、分数、日期和事件类别都显示出来。

这个查询与之前介绍的查询语句在以下两个方面有着显著的区别。

FROM grade_event INNER JOIN score
ON grade_event.event_id = score.event_id

请注意,我们是如何通过grade_event.event_idscore.event_id来引用event_id列的,其语法形式为:tbl_name.col_name。这样,MySQL便能知道我们是在引用哪个表。因为这两个表都有event_id列,所以在不限定表名时,会产生二义性。这条查询语句里的其他列(datescorecategory)可以直接使用,不用限定表名,因为它们只存在于其中的一个表里,不会产生二义性。

在连接语句里,我个人比较喜欢在每个列的前面都加上表名,从而可以让每一列属于哪个表变得更加清楚。在后面的连接语句里,我将一直沿用这个习惯。在为每列加上完整的表名之后,这个查询现在变成了下面这个样子:

SELECT score.student_id, grade_event.date, score.score, grade_event.category
FROM grade_event INNER JOIN score
ON grade_event.event_id = score.event_id
WHERE grade_event.date = '2012-09-23';

在第一阶段的查询里,我们利用grade_event表将日期映射到了事件ID,并使用这个事件ID在score表里找到了与之匹配的分数。这个查询的输出只包含了student_id值,但是如果能把学生的姓名直接显示出来则会更具意义。第二阶段,我们将利用student表,把学生ID映射成他们的姓名。score表和student表都有student_id列,通过它可以将两个表的各个行链接起来,显示出学生的名字。最终的查询语句如下所示:

mysql> SELECT
  -> student.name, grade_event.date, score.score, grade_event.category
  -> FROM grade_event INNER JOIN score INNER JOIN student
  -> ON grade_event.event_id = score.event_id
  -> AND score.student_id = student.student_id
  -> WHERE grade_event.date = '2012-09-23';
+-----------+------------+-------+----------+
| name    | date    | score | category |
+-----------+------------+-------+----------+
| Megan   | 2012-09-23 |  15  | Q    |
| Joseph  | 2012-09-23 |  12  | Q    |
| Kyle   | 2012-09-23 |  11  | Q    |
| Abby   | 2012-09-23 |  13  | Q    |
| Nathan  | 2012-09-23 |  18  | Q    |
...

这个查询与以前介绍的查询命令在以下几方面有区别。

ON ... score.student_id = student.student_id

对于这个查询,只要插入任何日期,即可获得那天对应的分数、参加考试的学生姓名,以及考试的类别。你根本不用了解学生ID和事件ID,因为MySQL会自动查出有关的ID值并利用它们把你想要的信息找出来。

在前面的1.4.9.9节,我们运行了一个查询,它可以将score表里各类数据的数值特征统计出来。在那个查询的输出结果里,只列出了事件ID,而没有列出事件日期或类别,因为我们当时还不知道如何将score表连接到grade_event表上,将事件ID映射到考试日期和类别上去。现在,我们来实现这一效果。下面这个查询与前面那个相差无几,但它显示出来的是考试日期和类别,而不只是一些数字形式的事件ID:

mysql> SELECT
  -> grade_event.date,grade_event.category,
  -> MIN(score.score) AS minimum,
  -> MAX(score.score) AS maximum,
  -> MAX(score.score)-MIN(score.score)+1 AS span,
  -> SUM(score.score) AS total,
  -> AVG(score.score) AS average,
  -> COUNT(score.score) AS count
  -> FROM score INNER JOIN grade_event
  -> ON score.event_id = grade_event.event_id
  -> GROUP BY grade_event.date;
+------------+----------+---------+---------+------+-------+---------+-------+
| date     | category | minimum | maximum | span | total | average | count |
+------------+----------+---------+---------+------+-------+---------+-------+
| 2012-09-03 | Q     |    9 |   20  |  12 |  439 | 15.1379 |  29 |
| 2012-09-06 | Q     |    8 |   19  |  12 |  425 | 14.1667 |  30 |
| 2012-09-09 | T     |   60 |   97  |  38 | 2425 | 78.2258 |  31 |
| 2012-09-16 | Q     |    7 |   20  |  14 |  379 | 14.0370 |  27 |
| 2012-09-23 | Q     |    8 |   20  |  13 |  383 | 14.1852 |  27 |
| 2012-10-01 | T     |   62 |   100  |  39 | 2325 | 80.1724 |  29 |
+------------+----------+---------+---------+------+-------+---------+-------+

虽然GROUP BY列带有限定符,但对于这条查询语句来说并不是必需的。GROUP BY引用了多个输出列,不过名叫date的列只有一个,因此MySQL可以清楚地知道你所指的是哪一个。

即使有源自多个表的多个列,你也可以使用像COUNT()AVG()这样的函数,为它们生成汇总信息。下面这个查询可以为事件日期与学生性别的每种组合,确定出各分数的数目,以及平均分数:

mysql> SELECT grade_event.date, student.sex,
  -> COUNT(score.score) AS count, AVG(score.score) AS average
  -> FROM grade_event INNER JOIN score INNER JOIN student
  -> ON grade_event.event_id = score.event_id
  -> AND score.student_id = student.student_id
  -> GROUP BY grade_event.date, student.sex;
+------------+-----+-------+---------+
| date    | sex | count | average |
+------------+-----+-------+---------+
| 2012-09-03 | F  |  14 | 14.6429 |
| 2012-09-03 | M  |  15 | 15.6000 |
| 2012-09-06 | F  |  14 | 14.7143 |
| 2012-09-06 | M  |  16 | 13.6875 |
| 2012-09-09 | F  |  15 | 77.4000 |
| 2012-09-09 | M  |  16 | 79.0000 |
| 2012-09-16 | F  |  13 | 15.3077 |
| 2012-09-16 | M  |  14 | 12.8571 |
| 2012-09-23 | F  |  12 | 14.0833 |
| 2012-09-23 | M  |  15 | 14.2667 |
| 2012-10-01 | F  |  14 | 77.7857 |
| 2012-10-01 | M  |  15 | 82.4000 |
+------------+-----+-------+---------+

我们可以用一条类似的查询语句来完成成绩考评项目的其中一项任务,即在期末的时候计算每个学生的总成绩:

SELECT student.student_id, student.name,
SUM(score.score) AS total, COUNT(score.score) AS n
FROM grade_event INNER JOIN score INNER JOIN student
ON grade_event.event_id = score.event_id
AND score.student_id = student.student_id
GROUP BY score.student_id
ORDER BY total;

成绩考评项目的另一项任务是汇总所有学生的缺勤情况。所有缺勤情况都记录在absence表里,其中包括学生ID和日期。为获得学生的名字(不只是ID),我们必须基于student_id的值,将absence表连接到student表上。下面这个查询可以列出学生的ID号和姓名,以及缺勤情况:

mysql> SELECT student.student_id, student.name,
  -> COUNT(absence.date) AS absences
  -> FROM student INNER JOIN absence
  -> ON student.student_id = absence.student_id
  -> GROUP BY student.student_id;
+------------+-------+----------+
| student_id | name | absences |
+------------+-------+----------+
|     3  | Kyle |    1  |
|     5  | Abby |    1  |
|     10  | Peter |    2  |
|     17  | Will |    1  |
|     20  | Avery |    1  |
+------------+-------+-----------+

如果你只想知道有哪些学生缺勤,那么这个查询输出已能满足需要。如果想要把这个列表交到学校办公室,那么他们可能会问:“其他学生的情况怎么样呢?我们希望看到每位学生的情况。”这就是个有所不同的问题了。它既要求统计缺勤学生的数量,也要求统计无缺勤情况学生的数量。既然这个问题有所不同,那么回答这个问题的查询也就会有所不同。

使用内连接(inner join)来回答此问题并不合适,我们需要使用LEFT JOIN子句。该子句会告知MySQL,对于连接里的第一个表(即LEFT JOIN关键字左边的那个表),为从其里面查询出的每一行产生一个输出行。通过将student表指定为第一个表,我们将能获得每位学生的输出,其中甚至包括那些absence表里没有的学生。在编写此查询语句时,可以在FROM子句里的两个表之间使用LEFT JOIN(而不是用逗号把这两个表分开),然后使用ON子句说明如何匹配这两个表中的行。此查询如下所示:

mysql> SELECT student.student_id, student.name,
  -> COUNT(absence.date) AS absences
  -> FROM student LEFT JOIN absence
  -> ON student.student_id = absence.student_id
  -> GROUP BY student.student_id;
+------------+-----------+----------+
| student_id | name   | absences |
+------------+-----------+----------+
|     1  | Megan   |    0 |
|     2  | Joseph  |    0 |
|     3  | Kyle   |    1 |
|     4  | Katie   |    0 |
|     5  | Abby   |    1 |
|     6  | Nathan  |    0 |
|     7  | Liesl   |    0 |
...

连接操作并非只能用于两个不同的表。这乍听起来有点儿奇怪,但你完全可以把某个表与其自身连接起来。例如,想确定是否有某位总统与另一位总统出生在同一个城市,这时便需要检查每位总统的出生地与其他总统的出生地是否一致:

mysql> SELECT p1.last_name, p1.first_name, p1.city, p1.state
  -> FROM president AS p1 INNER JOIN president AS p2
  -> ON p1.city = p2.city AND p1.state = p2.state
  -> WHERE (p1.last_name <> p2.last_name OR p1.first_name <> p2.first_name)
  -> ORDER BY state, city, last_name;
+------------+--------------+------------+---------+
| last_name  | first_name  | city     | state   |
+------------+--------------+------------+---------+
| Adams    | John Quincy  | Braintree  | MA    |
| Adams    | John      | Braintree  | MA    |
+------------+---------------+------------+---------+

这条查询命令有以下两个地方需要特别注意。

用一个类似的查询可以查出在同月同日出生的总统。不过,如果直接比较某两位总统的出生日期,那么查询结果里就会缺少那些生于同月同日但不同年的总统。因此,我们必须用函数MONTH()DAYOFMONTH()来比较出生日期里的月和日:

mysql> SELECT p1.last_name, p1.first_name, p1.birth
  -> FROM president AS p1 INNER JOIN president AS p2
  -> WHERE MONTH(p1.birth) = MONTH(p2.birth)
  -> AND DAYOFMONTH(p1.birth) = DAYOFMONTH(p2.birth)
  -> AND (p1.last_name <> p2.last_name OR p1.first_name <> p2.first_name)
  -> ORDER BY p1.last_name;
+------------+------------+-------------+
| last_name  | first_name | birth    |
+------------+------------+-------------+
| Harding   | Warren G. | 1865-11-02  |
| Polk     | James K.  | 1795-11-02  |
+------------+-----------+--------------+

使用DAYOFYEAR()来代替MONTH()DAYOFMONTH()的组合,可以得到一个稍微简单一点儿的查询语句。但其查询结果却可能不正确,因为它没有考虑到出现闰年的情况。

另一种类型的多表检索操作是使用“子查询”,即把一条SELECT语句嵌套在另一条SELECT语句里。子查询有几种类型,详细内容将在2.9节进行讨论。我们现在只看两个示例。假设需要把全勤的学生都找出来。此要求等价于把没在absence表里出现过的学生都找出来,因此我们可以这样做:

mysql> SELECT * FROM student
  -> WHERE student_id NOT IN (SELECT student_id FROM absence);
+-----------+------+------------+
| name    | sex  | student_id |
+-----------+------+-------------+
| Megan    | F   |     1  |
| Joseph   | M   |     2  |
| Katie    | F   |     4  |
| Nathan   | M   |     6  |
| Liesl    | F   |     7  |
...

嵌套于内层的SELECT语句会确定出absence表里的student_id值集合,而外层的那个SELECT语句则会检索出student表里与该集合中的ID值都不匹配的那些行。

子查询还能为1.4.9.8节提出的那个问题(即有哪些总统出生在Andrew Jackson总统之前)提供一种单语句解决方案。当时的解决方案是使用两条语句和一个用户变量,而现在可以用一条子查询语句来解决,如下所示:

mysql> SELECT last_name, first_name, birth FROM president
  -> WHERE birth < (SELECT birth FROM president
  -> WHERE last_name = 'Jackson' AND first_name = 'Andrew');
+------------+------------+------------+
| last_name | first_name | birth    |
+------------+------------+------------+
| Washington | George   | 1732-02-22 |
| Adams    | John     | 1735-10-30 |
| Jefferson | Thomas   | 1743-04-13 |
| Madison  | James   | 1751-03-16 |
| Monroe   | James   | 1758-04-28 |
+------------+------------+------------+

内层的SELECT语句用来确定Andrew Jackson的出生日期,外层SELECT语句负责检索出生日期早于该日期的总统。

有时候,你会需要删除某些行,或者修改其内容。这时便需要用到DELETE语句和UPDATE语句。

DELETE语句的基本格式如下所示:

DELETE FROM tbl_name
WHERE whichrows to delete;

其中,WHERE子句是可选的,它用于指定需要删除掉哪些行。如果没有WHERE子句,那么将删除表里的所有行。这意味着,越简单的DELETE语句越危险,例如下面这条语句:

DELETE FROM tbl_name;

它会彻底删除表里的所有内容,因此请务必小心使用!如果只想删除特定的行,那么必须先使用WHERE子句把它们筛选出来。这种做法与在SELECT语句里使用WHERE子句来避免查询出整个表的做法相类似。例如,在president表里,只想将出生于Ohio的美国总统删除掉,那么可以使用下面这条查询语句:

mysql> DELETE FROM president WHERE state='OH';
Query OK, 7 rows affected (0.01 sec)

如果你不太清楚某条DELETE语句到底会删除哪些行,那么最好先把这条语句的WHERE子句放到一条SELECT语句里,看看这条SELECT语句能查出哪些行。这有助于你确认:它们的确是你想要删除的那些行,而且只有这些,不能多也不能少。假设想要删除Teddy Roosevelt总统对应的那一行。那么下面这条语句是否能完成此项工作呢?

DELETE FROM president WHERE last_name='Roosevelt';

这条语句确实能删除掉Teddy Roosevelt总统对应的那一行,但它同时也会删除掉Franklin Roosevelt总统对应的那一行。因此,出于安全的考虑,最好先把这个WHERE子句放到SELECT语句里检查一下,就像这样:

mysql> SELECT last_name, first_name FROM president
  -> WHERE last_name='Roosevelt';
+-----------+-------------+
| last_name | first_name |
+-----------+-------------+
| Roosevelt | Theodore   |
| Roosevelt | Franklin D. |
+-----------+-------------+

从上面的查询结果可以看出,还需要在条件里加上总统的名,从而让它更具体一点:

mysql> SELECT last_name, first_name FROM president
  -> WHERE last_name='Roosevelt' AND first_name='Theodore';
+-----------+------------+
| last_name | first_name |
+-----------+------------+
| Roosevelt | Theodore  |
+-----------+------------+

现在,你应该知道如何使用WHERE子句正确地标识那些想要删除的行了。下面是更正后的DELETE语句:

mysql> DELETE FROM president
  -> WHERE last_name='Roosevelt' AND first_name='Theodore';

表面看起来似乎是为了删除一行而输入了很多的内容,但事实上稳妥一点总是胜过事后来后悔。如果真的遇到这种情况,则可以通过复制粘贴或者输入行编辑(line-editing)技术,尽可能减少输入操作。更多相关信息请参考1.5节。

如果想修改已有记录行,则需要使用UPDATE语句,其基本格式如下:

UPDATE tbl_name
SET which columns to change
WHERE which rows to update;

这里WHERE子句的情况与DELETE语句里的相似。它是可选的,因此在没有指定它时,会更新表里的每一行。例如,下面这个查询会把每一位学生的名字都更改为George:

mysql> UPDATE student SET name='George';

很显然,必须谨慎对待这类查询,所以通常情况下都需要加上一个WHERE子句,用它来更具体地指出需要更新哪些行。假设你最近往“美史联盟”里新增了一名成员,但只填写了很少的几栏信息:

mysql> INSERT INTO member (last_name,first_name)
  -> VALUES('York','Jerome');

接着,你发现自己忘了为他设置成员资格有效日期。此时,可以用一条UPDATE语句来进行修复,其中包含一条WHERE子句,用以标识需要更新的那行:

mysql> UPDATE member
  -> SET expiration='2013-7-20'
  -> WHERE last_name='York' AND first_name='Jerome';

你可以在一条语句里同时更新多个列。下面这条UPDATE语句将更新Jerome的电子邮件地址和邮箱地址:

mysql> UPDATE member
  -> SET email='jeromey@aol.com', street='123 Elm St',
  -> city='Anytown', state='NY', zip='01003'
  -> WHERE last_name='York' AND first_name='Jerome';

你也可以将某列的值设置为NULL(如果该列允许使用NULL值的话),从而使它处于“未设置”状态。例如,Jerome在后来的某个时候支付了一大笔成员资格更新费用,足以让他成为终身成员。那么,这时你可以通过将其有效日期设置为NULL,把他的那一行标记为“永不过期”:

mysql> UPDATE member
  -> SET expiration=NULL
  -> WHERE last_name='York' AND first_name='Jerome';

对于UPDATE语句,与DELETE语句一样,为了确保能准确地把你想要更新的行全都筛选出来,最好先把它的WHERE子句放到SELECT语句里去确认一下。否则,如果检索条件过于严格或过于宽松,便会出现少更新行或多更新行的情况。

如果你练习过本节这些语句,那么你肯定已在sampdb数据库的许多表里,删除或者更新了某些行。在进入下一节之前,你应该恢复这些修改。请参考1.4.8节中的说明,使用之前提供的那些命令重新加载所有表。

本节将介绍一些与客户端程序mysql进行交互的技巧,这些技巧能帮助我们更有效率地使用它,减少文字输入。此外,还会描述“怎样更加方便地连接到服务器”,以及“如何在不经手工输入的情况下输入语句”。

在启动mysql程序时,通常都需要设定某些连接参数,如主机名、用户名或密码。如果在每次启动mysql程序时都输入这么多的内容,那么你很快就会感到厌烦。其实,在连接MySQL服务器时,有好几种办法可以减少打字输入的需要。

1.5.1.1 使用选项文件

MySQL允许把连接参数存储到一个选项文件里。这样,你就不用在每次启动mysql时都输入这些参数了。它们的使用效果与在命令行上输入它们是一样的。这样做的好处在于,其他MySQL客户端程序(如mysqlimportmysqlshow)也能使用这些参数。换句话说,选项文件不仅能简化mysql的启动过程,也可以让许多其他程序受益。本节对“如何设置客户端程序所用的选项文件”进行了简要介绍。有关更多详细信息请参考F.2.2节。

在Unix系统上,可以创建一个名为~/.my.cnf的文件(即在你的主目录下创建一个名为.my.cnf的文件)来作为选项文件。在Windows系统上,可以在MySQL安装目录下或者在C盘根目录下,创建一个名为my.ini的选项文件(如果在C盘根目录下,就是C:\my.ini)。选项文件是一个纯文本文件,因此可以使用任何一种文本编辑器来创建。选项文件的内容近似如下:

[client]
host=server_host
user=your_name
password=your_pass

其中,[client]行是client选项组的开始标记。MySQL客户端程序会由该行开始读入各行,一直到文件末尾或遇到一个不一样的选项组起始标记为止,从而获得各个选项值。请把其中的server_hostyour_nameyour_pass分别替换为你在连接MySQL服务器时所用的主机名、用户名和密码。例如,服务器运行在cobra.example.com主机上,MySQL的用户名和密码分别是sampadmsecret,那么请在.my.cnf文件里放入下面几行内容:

[client]
host=cobra.example.com
user=sampadm
password=secret

那个[client]行是必需的,用于定义选项组的起始位置。不过,那些用来定义参数值的文本行都是可选的,你可以只列出所需要的那些参数值。例如,你使用的是Unix系统,并且MySQL用户名就是Unix的登录名,那么可以不用包含user那一行。默认的主机是localhost,因此如果你要连接的服务器运行在本地主机上,那么host那一行也可以不要。

如果是在Unix系统上,那么在创建选项文件之后还需要为它设置访问权限,从而保证其他人不能读取和修改它。下面两条命令都可以实现让选项文件仅限你本人访问:

% chmod 600 .my.cnf
% chmod u=rw,go-rwx .my.cnf

1.5.1.2 利用shell的历史命令功能

有些shell程序,如tcshbash,能把你在命令行上输入过的命令记录在一个历史清单里,从而可以让你重复其中的命令。如果你使用的是这类shell程序,那么这个历史清单可以帮你避免输入整条的命令。例如,在你刚使用过mysql之后,你可以像下面那样来执行它:

% !my

感叹号字符(!)的作用是告知shell程序,让它在命令历史清单里,把你最近执行过的以my开头的命令找出来,再执行一次,就好像你在命令行上再次自己输入了它一样。有些shell程序还支持使用键盘上的上下箭头键(或组合键Ctrl+P和Ctrl+N)在命令历史清单里前后移动。这时,在当中选择一条命令之后,按Enter键即可执行它。tcshbash具有这样的功能,其他shell可能也有。如果你想了解更多如何使用历史命令列表的信息,可以查阅你所用的shell程序的帮助文档。

1.5.1.3 利用shell别名和脚本

如果shell程序支持别名机制,那么你可以将一个较短的命令名映射到一条较长的命令上。例如,在cshtcsh里,你就可以像下面这样用alias命令来创建一个名为sampdb的别名:

alias sampdb 'mysql -h cobra.example.com -p -u sampadm sampdb'

bash的语法稍有不同:

alias sampdb='mysql -h cobra.example.com -p -u sampadm sampdb'

利用别名定义之后,下面两条命令完全等价:

% sampdb
% mysql -h cobra.example.com -p -u sampadm sampdb

很明显,第一条命令比第二条简短很多。如果想让这个别名在你每次登录系统时都能生效,那么还需要把alias命令放到shell程序的启动文件里(例如,tcsh的启动文件是.tcshrc,而bash下的启动文件是.bashrc.bash_profile)。

在Windows系统上,也有类似的技巧,即可以先创建一个快捷方式指向mysql程序,然后再编辑该快捷方式的属性,让它包含相关的连接参数。

另一种能让你在调用命令时少打字的方法是,创建一个脚本,让它带着恰当的选项执行mysql。在Unix系统上,一个shell脚本与刚才定义的命令别名sampdb是等价的:

#!/bin/sh
exec mysql -h cobra.example.com -p -u sampadm sampdb

如果把这个脚本命名为sampdb,并将其设置为可执行的(使用chmod +x sampdb命令),那么在命令提示符里输入sampdb,即可启动mysql,并连接到sampdb数据库。

在Windows系统上,可以使用批处理文件来完成同样的事情。先创建一个名为sampdb.bat的批处理文件,然后再把下面的内容放入其中:

mysql -h cobra.example.com -p -u sampadm sampdb

执行这个批处理文件的办法有两种:一种是在控制台窗口的提示符处输入sampdb;另一种是双击这个批处理文件的Windows图标。

如果需要访问多个数据库或连接多个主机,那么你可以多定义几个别名、快捷方式或者脚本,每一个都以不同的选项参数来调用mysql程序。


强烈建议大家不要把这类脚本安装在公共区域里,因为它们在执行时不会对参数进行安全检查,因而很容易遭遇SQL注入攻击。假设有人调用类似下面的脚本发起攻击:

此脚本的后果是会有一条DROP DATABASE语句被注入脚本语句中,成为mysql的输入内容。如此一来,这条命令会真的被执行,其后果可想而知。

从对数据库进行交互式查询的角度讲,mysql是一个极其有用的程序,但其操作界面却主要适合于输入短小的单行查询命令。虽然mysql自身并不关心输入的查询命令是否会延续多少行,但输入一条长长的查询命令却不是件让人高兴的事。如果因为语法错误而不得不重新输入一遍,那么肯定会让人更郁闷不已。有几种技术可以帮助我们减少不必要的录入工作。

  % ./interests.sh "Jefferson';DROP DATABASE sampdb;"

1.5.2.1 利用mysql的输入行编辑器

mysql程序支持输入行编辑。你可以编辑当前正在输入的那一行,也可以把以前的输入行调出来,直接再次输入它们,或者在进一步修改之后输入它们。当在输入的命令行中有打字错误的时候,使用这个功能会很方便:在按Enter键之前,可以把光标移到出错位置,对问题进行修正。如果你输入了一个有错误的查询命令,那么可以把它重新调出来,编辑修正问题,然后再次提交。(如果你是在一行里输入了整条查询命令,那么修改起来是最容易的。)

表1-4列出了一些适用于Unix系统的按键组合。

表1-4 mysql程序的输入编辑命令

按键组合

含  义

向上键或Ctrl+P

调出前一个输入行

向下键或Ctrl+N

调出后一个输入行

向左键或Ctrl+B

向左移动光标

向右键或Ctrl+F

向右移动光标

Esc b

把光标向后移动一个单词

Esc f

把光标向前移动一个单词

Ctrl+A

把光标移到输入行的开头

Ctrl+E

把光标移到输入行的末尾

Ctrl+D

删除光标位置上的那个字符

Delete

删除光标前面(左侧)的那个字符

Esc D

删除单词

Esc Backspace

删除光标前面(左侧)的那个单词

Ctrl+K

删除从光标位置到输入行末尾的所有内容

在Windows平台上,mysql程序并没有提供行编辑功能。不过,Windows自身支持许多编辑命令,如表1-5所示,因此mysql程序可以直接使用它们。

表1-5 Windows的输入编辑命令

按键组合

含  义

向上键

调出前一行

向下键

调出后一行

向左键

光标左移一个字符(后退)

向右键

光标右移一个字符(前进)

Ctrl + 向左键

光标左移一个单词

Ctrl + 向右键

光标右移一个单词

Home

光标移动到行首

End

光标移动到行尾

Delete

删除光标处的字符

Backspace

删除光标左边的字符

Esc

删除整行

Page Up

调出最早输入的命令

Page Down

调出最后输入的命令

F3

调出最后输入的命令

F7

弹出命令菜单,用向上键/向下键选择

F9

弹出命令菜单,用命令编号选择

F8,F5

循环显示命令列表

下面的示例演示了一个输入行编辑功能的简单用法。假设你在mysql程序里输入了如下所示的查询命令:

mysql> SHOW COLUMNS FROM persident;

在按Enter键之前,你突然发现自己把president错拼成了persident,那么可以像下面这样修改这条查询命令。

(1)先按几次向左键,把光标左移到字符s的位置上。

(2)按两次Delete键或Backspace键(这两个键都可以删除光标左侧的字符),删除er

(3)再重新输入re,修正错误。

(4)然后按下Enter键来执行该查询命令。

如果在按Enter键之后才发现打错了字,那么也不要紧。等到mysql显示出错信息之后,按向上键调出刚才输入的那条查询命令,然后像刚才描述的那样将其修改好即可。

1.5.2.2 利用复制和粘贴来执行语句

如果你使用的是窗口化操作环境,那么可以把你认为有用的那些语句保存到某个文件里,其后通过复制和粘贴操作再次调用它们。

(1)在某个终端或控制台窗口里启动mysql程序。

(2)在文档窗口里打开那个用来存放语句的文件。(例如,我在Unix系统上用vi,在Windows系统上用gvim。)

(3)在这个文件里找到你想要执行的语句,选取并复制它。然后切换到终端窗口,把刚才复制的那条语句粘贴到mysql程序里。

这一过程看起来比较繁琐,但在掌握之后却相当快捷。它提供了一种无需打字,即可快速输入命令的方式。稍加练习,即可熟练掌握。

你也可以将复制和粘贴的顺序反过来使用(把终端窗口里的语句复制粘贴到你的文件里)。在Unix系统上,当在mysql里输入语句时,这些语句会被保存到位于主目录下的一个名为.mysql_history的文件里。如果你手工输入了一条语句之后,想把它保存起来以供今后使用,那么可以这样做:先退出mysql,再用一个编辑器打开.mysql_history,然后将.mysql_history里的语句复制粘贴到你的那个语句存档文件里。

1.5.2.3 利用mysql执行脚本文件

mysql程序并非只能在交互模式下运行,它也可以在非交互模式(即批处理)下从某个文件里读取输入。如果你有一些需要定期运行的语句,那么此方法会特别有用,因为你不用每次都来重新输入它们。只要在一个文件中保存一次这些语句,就可以根据需要反复地让mysql执行它们。

假设你有一条查询命令,它可以通过member表里的interests列,将那些对某个特定时期的美国历史感兴趣的“美史联盟”成员找出来。例如,想要将那些对Great Depression(美国20世纪30年代的那个大萧条时期)感兴趣的成员找出来,那么可以编写出类似下面这样的查询命令:

SELECT last_name, first_name, email, interests FROM member
WHERE interests LIKE '%depression%'
ORDER BY last_name, first_name;

把这条查询命令保存在interests.sql文件里,然后像下面这样将它提供给mysql程序执行:

% mysql sampdb < interests.sql

默认情况下,当以批处理模式运行时,mysql程序产生的输出内容是以制表符分隔的。如果想得到交互方式运行mysql时输出的格式效果,可以加上一个-t选项:

% mysql -t sampdb < interests.sql

如果想把输出结果保存起来,那么可以将它重定向至一个文件:

% mysql -t sampdb < interests.sql > interests.out

如果已经运行了mysql,那么可以通过source命令来执行指定文件里的内容:

mysql> source interests.sql

如果需要将那些对Thomas Jefferson总统的生平感兴趣的成员查找出来,那么可以先编辑这个文件,把其中的depression改为Jefferson,然后再次运行mysql即可。不过,这个方法只有在你不会频繁地使用这条查询命令时才会显现出优势来。如果要频繁地运行某条查询命令,那么需要另谋出路。在Unix系统上,有一种让查询命令变得更加灵活的方法:先把它保存为一个可以接受脚本命令行参数的shell脚本;然后,用这些命令行参数来更改查询命令的文本内容。这种参数化查询命令的方式,让你可以在运行脚本时指定interests值。通过下面这个shell脚本interests.sh,可以看出其中的工作原理:

#!/bin/sh
# interests.sh - find USHL members with particular interests
if [ $# -ne 1 ]; then echo 'Please specify one keyword'; exit; fi
mysql -t sampdb <<QUERY_INPUT
SELECT last_name, first_name, email, interests FROM member
WHERE interests LIKE '%$1%'
ORDER BY last_name, first_name;
QUERY_INPUT

其中的第3行用于确保命令行参数只有一个;否则,它就会输出一条简短的出错信息,然后退出执行。在<<QUERY_INPUT和最后那个QUERY_INPUT之间的所有内容,都会成为mysql的输入。shell程序会把这段查询命令文本里的脚本变量$1替换为源自命令行的那个参数(在shell程序里,变量$1$2...依次对应于命令行里的各个参数)。这样,在运行这个脚本时,命令行的参数将成为这条查询命令中的检索关键字。

在运行该脚本之前,还必须把它设置为可执行的:

% chmod +x interests.sh

现在,你不用在每次运行这个脚本时都去编辑它了。只需通过命令行参数,告诉它你想查找什么东西即可:

% ./interests.sh depression
% ./interests.sh Jefferson

你可以在sampdb发行版的misc目录里找到这个interests.sh脚本。另外,在那里还提供了一个与之等效的Windows批处理文件interests.bat

说明

通过本章的学习,相信你对MySQL的使用已有所了解。你可以创建数据库和表,向表中插入行,用各种方式检索这些行,修改它们,或者删除它们。但本章的这个教程只能算是“蜻蜓点水”,还有很多内容并未讲到。请仔细想想,sampdb数据库当前到什么状态了呢?我们把它和它的表创建好了,并且还使用了一些初始数据来填充其中的各个表。在这个过程中,我们还编写了一些查询命令,用于回答那些与数据库信息有关的问题。不过,还有很多事情在等着我们去完成。例如,到目前为止,我们还没有一种简便的交互方式,用于为成绩考评项目输入新的分数行,或者为“美史联盟”添加新的成员。我们还没有可以方便地修改已有行的方式。而且,我们还不能以打印版和在线版两种形式生成“美史联盟”的成员名录。在后面的章节(尤其是第8章和第9章)中,我们将继续完成这些任务。

接下来你想要跳转到本书的哪一章节呢?这要取决于你的兴趣。如果你只想知道如何完成“美史联盟”和“成绩考评项目”里的各项任务,那么可以直接跳转到本书的第二部分,那里会讲到如何编写MySQL应用程序。如果你打算成为网站的MySQL数据库管理员,那么可以直接跳转到本书的第三部分,那里会对数据库的管理工作进行探讨。不过,建议大家先按部就班地读完第一部分,多积累一些MySQL在使用方面的背景知识。这些内容将帮助大家进一步了解SQL语句的语法和用法,弄清MySQL是如何处理数据的,怎样才能让查询执行得更快。无论你是想要运行mysql,想要自己编写这样的程序,还是想要成为一名数据库管理员,牢牢掌握这些内容总会有收获。


本章讲述为何要自己编写基于MySQL的程序,而不使用MySQL发行版里包含的那个标准化客户端程序的原因。另外,本章还对随后几章将会涉及的3种程序设计语言(C、Perl和PHP)的编程接口做了概念性的介绍,并对选择程序设计语言时需要考虑的因素进行了讨论。

MySQL发行版中有一组与服务器进行通信的客户端程序。例如,mysqldump可用于导出表的定义和内容;mysqldamin可用于管理操作;mysql可用于执行任意的SQL语句。

这些标准客户端程序足以处理MySQL用户日常遇到的绝大多数问题,但有些应用的需求则远远超出这些程序的能力范围。为解决此类问题,MySQL提供了一个客户端应用程序编程接口(Application Programming Interface,API),用于满足应用程序可能会有的任何特殊要求。客户端API提供了对MySQL服务器进行访问的能力,其开放程度完全超出你的想象。

本章将讨论,如何编写基于MySQL的可对数据库进行访问的程序。为让大家明白这样做的好处,下面把你自己编写程序所能完成的任务,与mysql客户端程序的能力和其访问MySQL服务器的便捷接口进行对比。

在第1章,我们列举了几个与样本数据库sampdb有关的目标,其中即包含了编写程序与MySQL服务器进行交互的要求。下面列出的是其中几个目标。

还有一个需要我们深入思考的问题,即如何把MySQL的各项功能集成到Web环境中。MySQL不直接支持Web应用程序,但是,将MySQL与某些合适的工具结合在一起,你便可以代表客户端用户从你的Web服务器上执行查询,并把结果发送给用户浏览器。这样一来,便可以很容易地通过Web访问数据库。

下面从彼此互补的观点来看待MySQL和Web的结合。

这两个观点并不矛盾。例如,在“美史联盟”示例里,我们把Web当作是这样一种方式:把所有成员名录都发布到网上,让成员能够方便地访问到成员名录的内容。这就是利用Web来提供对数据库的访问功能。与此同时,把名录内容发布到联盟的网站上,也会提升网站对成员的价值。而这就是使用数据库来增强网站的服务水平。

无论如何看待MySQL与Web的结合,具体的实现都是相似的。你要以Web服务器为桥梁,把前端的Web站点与后端的MySQL连接在一起。Web服务器负责从客户端用户那里收集信息,并把它以查询形式发送到MySQL服务器,然后再把检索出来的结果返回给客户端的浏览器,以供用户查看。

当然,你完全可以不把数据发布到网上。但是,与通过标准MySQL客户端程序来访问数据的方式相比,把数据发布到网上通常会有以下几点好处。

任何一种编程语言都可用于编写基于Web的应用程序,但是相比之下,有些语言则更为适合。在6.3节会讨论到这个问题。

为支持应用程序的开发,MySQL提供了一个用C语言编写的客户端开发库。通过它,我们可以在任何的C语言程序里访问MySQL数据库。这个客户端开发库实现了一个API,其中包含一组数据结构和函数。

其他编程语言的MySQL接口,可以把这个C语言客户端开发库链接到语言处理器里。因此,这个客户端开发库提供了一种MySQL绑定方式,让其他编程语言可以构建在这个C语言API之上。Perl、PHP、Python、Ruby、C++、Tcl以及其他语言,都有这种类型的接口。

每一种编程语言绑定都定义了其自己的接口,用于指定访问MySQL的规则。MySQL提供了多种API。我们这里主要介绍3种最为流行的API。

本章后面对这3种API做了概要性的对比:首先,描述了它们的一般特点;然后,想让你对“在某些特定的应用程序里,为何要选这个,而不选另一个”这一做法的原因有所了解。接下来的三章会详细讨论它们。

当然,把自己锁定在某个API里是毫无理由的。对多个API有所了解,并用这些知识把你自己武装起来,可以让你在它们之间做出明智的选择。对于包含多个组成部分的大项目,可以选用多种API,并根据工作的具体要求选择最合适的语言,编写各个部分。

如果你还缺少使用某个API所必需的软件,则可以参考附录A。

C语言API主要用在编译过的C语言程序的上下文里。它是一个客户端开发库,提供了与MySQL服务器进行对话的接口,让你能够与服务器建立连接,并进行通信。

在MySQL发行版里提供了多个C语言客户端程序,它们都是基于这个API开发的。C语言客户端开发库也是其他编程语言实现大部分MySQL绑定的基础。例如,Perl的DBI模块里的那个MySQL驱动程序,便是通过链接适用于MySQL的C语言客户端开发库的代码,才实现了对MySQL的访问。

在Perl脚本语言里,DBI API用于编写数据库应用程序。这套API一边要尽可能多地处理数据库服务器,一边又要把服务器有关的细节隐藏起来,不让脚本的编写者看到。为实现这一目标,DBI使用了多个Perl模块。这些模块分属在两层架构里,协同工作(见图6-1)。

图6-1 DBI架构

DBI架构可以让你以相对通用的方式编写应用程序。在编写DBI脚本时,可以使用一套标准的数据库访问调用。DBI层会在DBD层调用正确的驱动程序来处理你的请求,并且驱动程序会处理,与你所用的特定数据库服务器进行通信时所产生的问题。DBD层会把服务器返回的数据传递回DBI层,而DBI层会把数据呈现给应用程序。于是,不管数据来自哪一个数据库系统,数据的形式都是统一的。

从应用程序开发人员的角度来看,DBI接口不仅掩盖了数据库服务器之间的差异,而且还能处理很多种不同的服务器(与用于它们的驱动程序的种类相当)。DBI提供的统一客户端接口改善了程序的可移植性,因为我们可以用一种统一的方式去访问各个数据库服务器。

在脚本编写方面,只有一种情况与特定服务器密切相关,即当你连接某个数据库服务器的时候,因为,这时你必须指明选用哪种驱动程序来建立连接。例如,想要使用MySQL数据库,可以这样连接:

$dbh = DBI->connect ("DBI:mysql:...");

如果要使用PostgreSQL或Oracle,则可以这样连接:

$dbh = DBI->connect ("DBI:Pg:...");
$dbh = DBI->connect ("DBI:Oracle:...");

在连接建立之后,就不用再引用任何特定的驱动程序了。DBI和驱动程序自己会把与特定数据库有关的全部细节处理好。不管怎样,理论上应该如此。不过,在DBI脚本的可移植性方面,有两个因素需要特别注意。

尽管上述两个因素会潜在影响到DBI脚本的可移植性,但是以抽象方式提供数据库访问的DBI机制,仍然是提高可移植性的有效途径。现在轮到你自己来决定要使用多少个不可移植的功能。在第8章你会发现,我基本没怎么避免使用这些MySQL DBD提供的、专属于MySQL的功能。这是因为你应该知道这些功能是什么,只有这样,你才能决定是否要使用它们。更多相关信息请参考附录H。

与Perl语言一样,PHP也是一种脚本语言。与Perl语言不一样的是,PHP的设计目标不是要成为一种通用语言,而是要成为一种专门编写Web应用程序的语言。PHP的API主要用于把可执行脚本嵌入Web页面里。这样,Web开发人员便可以很容易地编写出带有动态生成内容的页面。当某个客户浏览器向Web服务器发送一个PHP页面请求时,PHP将执行在该页面里找到的所有脚本,并把它们替换为该脚本的输出内容。最终的结果被发送到浏览器。如此一来,实际上显示在浏览器里的页面,会根据页面请求所发生的具体环境产生变化。例如,当把下面这段简短的PHP脚本嵌入某个Web页面时,它便会显示请求该页面的那台客户端主机的IP地址:

<?php echo $_SERVER["REMOTE_ADDR"]; ?>

举一个不太重要但比较让人感兴趣的例子,你可以用一个脚本,把数据库内容的最新信息提供给访问者。下面这个示例显示了一个简单的脚本,可以用在“美史联盟”的Web站点上。这个脚本调用一个查询,用以确定当前的联盟成员人数,并且会把它报告给网站的访问者。

<html>
<head>
<title>U.S. Historical League</title>
</head>
<body bgcolor="white">
<h2>U.S. Historical League</h2>
<p>Welcome to the U.S. Historical League Web Site.</p>
<?php
# USHL 主页

try
{
 $dbh = new PDO("mysql:host=localhost;dbname=sampdb", "sampadm", "secret");
 $dbh->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 $sth = $dbh->query ("SELECT COUNT(*) FROM member");
 $count = $sth->fetchColumn (0);
 print ("<p>The League currently has $count members.</p>");
 $dbh = NULL; # 关闭连接
}
catch (PDOException $e) { } # 清空处理器(捕捉并忽略错误)
?>
</body>
</html>

PHP脚本一般看上去很像HTML页面,只是在标签<?php?>之间嵌入了一些可执行代码。一个页面可以包含任意多个代码段。这提供了一种极其灵活的脚本开发方法。例如,可以像编写普通HTML页面那样,先编写PHP脚本,搭建起页面的基本框架,然后再添加代码,生成页面的动态部分。

PHP实际上有多种类型的数据库接口。这些接口中包含的是一组底层的函数库,每一组函数库都对应一种数据库服务器——这与DBI接口的做法有所不同,它并没有费尽心思去统一针对不同服务器的接口。实际上,各个服务器的接口,看起来更像是为该服务器实现了底层API的那个C语言库所对应的接口。例如,在PHP脚本里,用来访问MySQL数据库的那个底层函数的名字,与MySQL中C语言客户端开发库里实现该功能的那个函数的名字非常相似。

在PHP里,比较像DBI的方法是由PHP数据对象(PHP Data Object,PDO)扩展模块提供的。这个扩展模块利用与DBI相类似的两层架构,提供了更加抽象的数据库系统接口。第9章的PHP脚本将使用PDO扩展模块来访问数据库。

本节提供的通用指南,可以帮助你为各种类型的应用选择合适的API。其中,将对C、DBI和PHP中的API功能进行对比,以便让你对它们的优点和缺点都有所了解,并且还会指明何时选择何种更合适。

虽然我有自己偏好的语言,但我不会偏重于这三种语言中的任何一种语言。你可能会像本书的技术评审那样,也拥有自己的偏好。其中一位评审觉得,我应该着重强调使用C语言编写MySQL程序的重要性,而另一位却认为,我应该不鼓励使用这种语言。请自己权衡本节所讨论的各种因素,得出自己的结论。

在评估哪种API最适合某项特定任务时,值得考虑的因素有很多。

请注意,这其中的一些因素是相互影响的。例如,你很想让编写的应用程序具有很高的性能,但是,选用一种能让你快速开发应用程序的编程语言,可能也同样重要,哪怕应用程序的性能没那么高。

在编写应用程序时,都要考虑其使用环境。例如,它可能是一个可以从shell启动的报表生成程序,也可能是一个在每个月底以cron作业方式运行的应付账款汇总程序。不管是从shell启动的命令,还是从cron作业运行的命令,它们通常都比较独立,对执行环境并无过多的要求。另一种情况是,你可能正在编写一个供Web服务器调用的应用程序。这类程序往往需要从其执行环境提取很多非常特定的信息,如客户端使用的是什么浏览器?输入到邮件列表订阅申请表里的参数是什么?客户端是否提供了访问个人信息所需要的正确密码?

每种API语言所适合编写的应用程序,随环境的不同而不同。

综上所述,C语言和Perl语言都是编写独立应用程序的最佳候选语言。Perl语言或PHP语言则最适合于编写Web应用程序。如果你需要同时编写这两种类型的应用程序,而你对这些语言又都不太熟悉,想稍加学习就能编程,那么Perl语言将是最佳选择。

在同等条件下,我们当然希望应用程序能够以最快的速度运行。但是,性能的实际重要性往往与程序的使用频率有关。对于每月只会在午夜运行一次的cron作业,其性能问题可能无关紧要。但是,如果某个程序在一个访问量极大的Web站点上,每秒运行多次,那么其效率的微小变化即可造成巨大的差异。在后一种情况里,性能对提高网站的可用性和吸引力有着显著影响。不管网站提供的内容是什么,一个反应迟钝的网站肯定会让访问者感到厌烦。如果你的收入来源依赖这个网站,那么性能的低劣将直接导致利润的减少。如果你的网站不能同时支持许多连接,那么厌烦等待的访问者便会放弃访问,再去寻找其他网站。

性能评估是一个复杂的问题。如果想要知道“某个具体API编写出来的应用程序的性能如何”,那么最好是用这个API实际编写一个程序,然后测试一下。最好的对比测试是,用不同的API实现出多个不同的版本,再对它们一一比较。当然,实际开发通常不会这么做。很多时候,都是先写出程序。在它可以工作之后,再去考虑优化它,看看它是否还可以运行得更快、占用的内存更少,或者是否还有其他可以改进的地方。不过,在以相对一致的方式对性能产生影响方面,至少有两个基本因素值得考虑。

刚才讨论的因素会对应用程序的性能产生影响,但是单纯地追求执行效率不应该是唯一的目标。你的时间以及编程工作的难易程度都很重要,因此在为MySQL应用程序选择API时,另一个需要考虑的因素是,需要多长时间能完成开发工作。如果为完成相同的功能,编写Perl或PHP脚本所花费的时间只有开发C语言程序所用时间的一半,那么即使最终的程序并没有运行得那么快,你仍有可能不会选用C语言API。少关注应用程序的执行时间,多关注编写它所花费的时间,这通常是合情合理的。尤其是对于那些执行频率并不高的应用程序,更是如此。你的一小时比计算机的一小时更加宝贵!

一般来讲,脚本语言可以让你更快地完成程序的开发,尤其是在进行原型设计的时候。至少有两个因素可用于说明其中的原因。首先,与编译型语言相比,脚本语言提供的语法结构通常更加高级。这使你能够在更高的抽象层上思考问题,从而可以把注意力集中在方向性的“应该做什么”上面,而不是去关注细节性的“如何去做”上面。例如,在处理涉及“键/值”关系(如“学生ID/学生姓名”)的数据时,PHP中的关联数组和Perl中的散列都可以帮助程序员节约很多时间。C语言并没有提供任何类似的功能。如果使用C语言来实现,那么你必须要编写代码,用于处理诸如内存管理之类的底层细节,并且还需要对它们进行调试。这些事情都需要花费时间。Perl和PHP的另一个优势是,它们可进行模式匹配和文本处理。在这些方面,C语言的处理都很初级。

其次,与使用编译型语言相比,使用脚本语言的程序开发周期更少。如果使用C语言,那么程序开发周期将是“编辑-编译-测试”。程序每修改一次,都要重新编译一次,然后才能测试。如果使用Perl或PHP,那么程序开发周期将简化成“编辑-测试”,脚本修改后,无需编译即可测试。另一方面,C语言编译器会对编写的程序实施更多的约束,即执行更严格的类型检查。编译器对代码的严格检查,有助于避免那些在“宽松”语言(如Perl和PHP)里难以捕捉到的错误。在C语言里,如果某个变量名拼错了,那么编译器会向你发警告。PHP和Perl则不会这样做,除非你要求它们这么做。当应用程序变得越来越大,并且越来越难以维护时,这种严格的约束将会变得更为重要。

一般情况下,在编译型语言和解释型语言之间,需要权衡的是开发时间和性能。你是想使用编译型语言开发程序,获得更快的执行速度,花费更少的编程时间呢?还是想把程序编写成一个脚本,在最短时间内让它运行起来,提高执行速度呢?

可移植性指的是,把某个为了使用MySQL而编写的程序,修改成可以使用另外一种数据库系统。你可能从未关心过这个问题。不过,除非你能预知未来,否则说出像“我绝不会把这个程序用在除MySQL以外的任何数据库上”这样的话,就有些冒险。假设你刚换了个新工作,并打算继续使用原有的程序,但是你的新老板使用的是另一种数据库系统,这时你要怎么办?或者,还能怎么做呢?如果需要优先考虑可移植性,那么必须弄清这些不同API之间的差异。

当你需要在同一个应用程序里访问多种类型的数据库系统时,以数据库无关的形式实现可移植性尤为重要。例如,需要把数据从一个数据库系统导出,并导入另外一个系统里;或者需要把源自多个数据库系统的信息整合在一起生成一份报告。


随着MySQL能力的增强,其复杂性也在不断增加。不过在各种数据库系统中,MySQL还算是相对容易使用的,其安装和使用都不难。MySQL的这种简单性使得它非常受欢迎,特别是在那些不是系统管理员、也不想成为系统管理员的人群中更是如此。如果你是一位训练有素的计算机专业人员,对于成功运行MySQL自然会有很大帮助,不过那不是必要条件。

尽管如此,不管你的技术水平如何,MySQL都不会自己运行起来。必须要有人盯着它,才能保证它平稳有效地运行,而且必须要有人知道当问题发生时该怎么办。如果你的工作是保证MySQL在网站上正常运行,那么请继续往下读。

本书的第三部分将讨论MySQL管理员的职责。本章简要说明了MySQL安装管理所涉及的职责。随后的几章提供的是更为详细的实施指导。

如果你是一名新的或者毫无经验的MySQL管理员,那么请不要被本章列出的一长串职责所吓倒。下面几节列出的每一项任务都很重要,但是你不需要立刻把它们全部学会。只要你愿意,完全可以把本书这部分的各章当作参考,当你真的需要了解它们的时候,再回过头来查询相关内容。

如果你有管理其他数据库系统的经验,那么你会发现,MySQL的安装与它们在很多方面都是类似的,并且你的经验也完全能够派上用场。但是,MySQL的管理工作也有其自己的特定要求。本书的这部分将帮助你熟悉这些内容。

MySQL数据库系统由多个组件构成。你应该对这些组件及其用途有所了解,以便你能真正了解自己所管理的这个系统的本质,以及那些能帮助你完成任务的工具。如果多花些时间了解一下自己管理的具体内容,那么你的工作将变得更加简单。为此,你应该对MySQL的下列几个方面有所了解。

(1)MySQL服务器。服务器主程序mysqld,是MySQL数据库系统的核心。它负责管理所有数据库和表。在Unix系统里,有几个相关的脚本用于辅助服务器的启动。mysqld_safe程序用于启动和监控服务器,在服务器意外停机时会重新启动它。对于Unix版本的MySQL,当使用运行级目录来启动各项系统服务时,可以使用mysql.server脚本来启动。如果想在同一台主机上运行多个服务器实例,那么mysqld_multi可以帮助你轻松地管理它们。在Windows系统上,可以选择从命令行运行服务器,也可以选择以Windows服务的方式运行它。

(2)MySQL客户端程序和实用工具。在与服务器进行通信时,可以使用的MySQL程序很多。就系统管理任务而言,下面列出的几个程序是最重要的。

(3)服务器语言——SQL。你应该可以用服务器自己的语言与它进行对话。举个简单的例子,你可能需要找出用户权限为何没按你所希望的方式工作的具体原因。如果没有能够进入并与服务器直接进行通信的替代程序,那么你可以使用客户端程序mysql来执行SQL语句,检查授权表。

如果你对SQL还不太了解,那么一定要对其有个基本的了解。对SQL不熟悉只会对你的管理任务造成影响,如果能多花点时间学习一下它,那么会有事半功倍的效果。想要真正掌握SQL需要花费较长的时间,但是基本技能很快便可掌握。有关SQL的指导信息,以及命令行客户端程序mysql的使用,请参考第1章。

(4)MySQL数据目录。服务器会把数据库和状态文件存储在数据目录里。了解数据目录的结构和内容很重要,你能知道服务器是如何使用文件系统来表示数据库和表的,还能知道服务器日志位于何处,以及它们所包含的内容。出于对储存分配和性能的考虑,你还应该了解一下与磁盘分配有关的选项。

MySQL的常规管理主要包括:操作MySQL服务器程序mysqld,管理访问服务器的用户。在履行此项职责时,最为重要的工作有以下几个。

(1)服务器启动和关闭。当系统需要启动和停止时,你应该知道如何通过命令行手动启动和停止服务器,以及如何自动启动和关闭。如果服务器崩溃或者不能正常启动,那么要如何做才能使其重新工作——了解这一点也很重要。

(2)用户账户维护。你应该了解MySQL账户和Unix或Windows登录账户之间的差别。应该知道如何设置MySQL账户,其中包括:指定哪些用户可以连接到服务器,他们可以从哪里连接到服务器,以及他们能做些什么。你还需要知道如何重置已忘记的密码。

(3)日志维护。你应该了解:哪些类型的日志可用,哪些日志对你有用,以及执行日志文件维护的时机和方法。日志轮转和过期对于防止日志填满文件系统来说至关重要。

(4)服务器配置和优化。MySQL服务器可配置程度很高。可控制的操作特性包括服务器支持的存储引擎、默认字符集和默认时区。

另一个配置问题是服务器优化。用户都希望服务器能以最佳状态运行。提高服务器运行速度最简便的方法是,购置更多的内存或者换一块运行速度更快的磁盘。但是这些增强措施并不能帮你了解服务器的工作。你应当知道有哪些参数可用于调整服务器的操作,以及在实际应用中如何使用它们。在某些站点,查询的工作主要是检索;而在其他站点,主要工作可能是插入和修改。要修改哪些参数,取决于你如何看待自己站点的查询工作情况。

(5)管理多个服务器。在某些情况下,可能需要在同一台机器上运行多个服务器实例。你可能需要测试某个新发行的MySQL版本,而同时希望保持现有的生产服务器不动;或者希望通过让每组都拥有其自己的服务器的方式来更好地保护不同用户组的隐私。(后者与因特网服务提供商密切相关。)对于这些情况,你应该知道如何设置多个实例服务器。

(6)更新MySQL软件。MySQL的版本在不断更新。你应该知道,如何通过更新到最新版本来获得服务器的错误修复和新功能。你要知道在什么情况下应推迟更新,并且也要知道如何在发行的稳定版本和开发版本之间进行选择。

当你负责MySQL的安装时,需要确保用户托付给数据库的信息绝对安全——这一点非常重要。MySQL管理员有责任控制好对数据目录及服务器的访问,并且应该对以下问题有所了解。

(1)文件系统安全性。在一台计算机里可能会有多个与MySQL的管理任务无关的用户账户。重要的是需要保证这些账户不能访问数据目录。这样可以防止他们在文件系统级对数据造成破坏,如复制或删除数据库的表、读取可能包含有敏感信息的日志。你应该了解:如何设置用于运行MySQL服务器的用户账户,如何设置数据目录使得它隶属于该用户,以及如何使用该用户的权限来启动运行服务器。

(2)MySQL服务器安全性。你必须了解MySQL安全系统的工作原理,以便在创建用户账户时,能为其授予合适的MySQL服务器访问权限。对于那些通过网络连接至服务器的用户,他们应该只做期望他们做的事情。你肯定不想出现因错误理解安全系统而不小心把过高的访问权限授予用户的问题!

每一个MySQL数据库管理员都希望能避免处理损坏或者遭破坏的表。但是,希望并不能阻止问题的发生。你应该采取措施降低这种风险,并且应该学会在意外发生时要如何应对。

(1)预防性维护。你应该有一套定期的预防性维护计划,用于降低数据库出现故障或遭到破坏的可能。当然,备份数据库是必不可少的,但是预防性维护可以减少你求助于备份的概率。

(2)数据库备份。当发生严重的服务器系统崩溃事件时,数据库备份能起到关键的作用。在数据库崩溃之后,你肯定会希望把数据库恢复到崩溃前的状态,同时尽可能减少数据损失。请注意,数据库备份与常规的系统备份(如在Unix系统上使用dump进行备份)并不相同。在进行系统备份时,表所对应的文件很可能因服务器的活动而处于变化状态,因此恢复那些文件并不能保证表的一致性。对于数据库恢复而言,mysqldump程序生成的备份文件会更有用。它可以让你在无需关闭服务器的情况下创建备份。此外,在硬盘空间用尽的时候,你可能会想要把数据库迁移到另一个地方。

(3)崩溃恢复。如果尽了最大努力但仍然出现了意外,那么你应该知道如何修复或恢复表。用到崩溃恢复的情况应该会很少,可是一旦需要,那么这将是一件非常痛苦且高度紧张的事情(尤其是在你正忙得不可开交、电话响、门铃也响的时候)。尽管如此,你必须要知道如何处理,否则,你的用户会极度不满!你必须熟练掌握用于检查和修复MySQL表的那些程序。一定要知道如何利用备份文件来恢复数据,以及如何利用二进制日志恢复最近一次备份后所发生的那些更改。

(4)数据库迁移。如果你打算把现有的MySQL迁移到一台速度更快的主机上,那么需要把数据库复制到另一台机器上。你应该熟悉整个操作的流程,以及产生的要求。数据库文件的具体内容可能依赖于具体的机器。如果真是如此,则不能简单地把它们从一个系统复制到另一个系统。

(5)数据库复制。对数据库进行备份或制作副本,实际上相当于在某个特定的时间点对其状态拍一个快照。另一种可选的办法是使用复制(replication),即建立两个相互合作的服务器,让一个服务器所管理的数据库产生的更改,持续传播到另一个服务器所管理的与之相应的那个数据库。

要使用复制,应该知道:如何把服务器设置为主复制服务器,如何设置用来同步复制主服务器的从服务器。如果出现了问题,导致复制工作停止,那么你必须要知道到什么地方去查找问题,并让复制工作重新开始。

以上几节简要描述了一名MySQL管理员应该承担的职责。接下来的几章将对它们进行详细讨论,并且会描述应该遵循的操作流程,以便于大家能有效地履行这些职责。我们首先会讨论MySQL的数据目录——它是你要维护的主要资源,你应该熟悉它的结构和内容。然后,我们将依次讨论常规管理任务、MySQL的安全系统以及数据库的维护和备份。


本附录介绍的主要内容是如何获得sampdb发行包——它主要用于建立本书的各个示例所使用到的那个样本数据库。为使用这个发行包,必须先要让MySQL运行起来。为此,本附录还将介绍如何获得和安装MySQL及其相关的软件,如Perl的DBI模块和CGI.pm模块、PHP以及Apache。本附录还将对MySQL的安装和必需的Perl与PHP软件的安装提供简要说明,并对如何配置Apache,让其可以连同PHP一起使用提供指导。

对于安装的任何软件包,都需要仔细阅读软件包附带的各种指导说明,并在必要时查阅其他的资源。例如,MySQL的手册里就有一章讲的是各个安装步骤。

访问http://www.kitebird.com/mysql-book/可以获得sampdb发行包。该发行包中包含了建立和访问样本数据库sampdb所需要的各种文件。该发行包的压缩格式有tarZIP两种。为解压tar格式的发行包,可以用下面两条命令中的任何一条(如果你的tar版本不支持z选项,则需要使用第二条命令):

% tar zxf sampdb.tar.gz
% gunzip < sampdb.tar.gz | tar xf -

为解压ZIP格式的发行包,可以使用类似WinZippkunzipunzip这样的工具。

在解压sampdb发行包时,会创建一个名为sampdb的目录,其中会包含以下几个文件和子目录。

sampdb目录中还包含有其他几个目录,其中包含的是本书其他章节所提到的文件。想要了解更多信息,请查看README.txt文件。

如果想要利用好本书,你必须安装一套MySQL(如果还没安装的话)。根据计划使用的MySQL访问方式的不同,你可能还需要安装其他软件。

对于那些基于Web的脚本,本书使用的是Apache服务器,当然,使用其他服务器也没有任何问题。

如果你在某个提供有MySQL服务的因特网服务提供者那里拥有账户,那么它很可能已安装好所有需要的软件包。此时,你可以直接使用它们。否则,请查看下表,找到每个软件包的主要分发站点。

软件包

网站

MySQL

http://dev.mysql.com/

Perl模块

http://cpan.perl.org/

PHP

http://www.php.net/

Apache

http://httpd.apache.org/

具体的软件包安装版本视具体需要而定。

这些软件包的分发站点会指明哪些是稳定版本、哪些是开发版本。它们还会提供每个版本的功能更改列表,帮助你决定哪一个发行版本最合适你。

当前,MySQL的稳定版本是5.5系列,而开发版本是5.6系列。本书使用的MySQL最低版本为5.5,不过在编写本书时由5.6的早期版本所提供的某些功能也有所涉及。对于用于生产目的的站点,本书推荐使用稳定版本,而不使用开发版本,因此对大部分读者的建议是使用MySQL 5.5。如果要体验新功能,可以使用MySQL 5.6。

许多安装包都是以预编译二进制文件形式提供的。二进制发行包通常都是以平台里原生的安装包格式提供的,如Linux里的RPM包,或者Mac OS X里的DMG包。也有发行包是采用其他更为通用的格式提供的,如Unix里的tar文件和Windows里的Zip文档。如果你想要根据源代码来编译软件,或者在你的平台里没有可用的二进制发行包,那么你会需要一个C语言编译器(对于MySQL,则需要C++ 编译器)。

有些平台有自己的软件包系统。在Unix系统里和像Linux这样的类Unix系统里,这种情况比较常见。此时,你可以使用这种软件包系统来安装所需的所有软件。例如,你可以在FreeBSD里使用port系统,在Gentoo Linux里使用emerge,在Red Hat Linux里使用yum,或者在Debian Linux里使用apt-get

本附录的剩余部分提供的是一些安装说明,其主要针对的内容是MySQL以及所需的Perl和PHP软件。

MySQL的安装涉及以下几个步骤。

(1)如果想在Unix系统里安装服务器,那么需要先决定好用于运行MySQL的登录账户是哪一个,并根据需要创建该账户。

(2)获得想要安装的发行包,并进行解压。如果你使用的是源代码,则需要编译并安装它。

(3)设置环境变量PATH,让其包括MySQL安装目录下的bin目录。这样可以使得对各个MySQL程序的调用变得更加容易,而不需要输入完整的路径名。

(4)如果你不是要运行服务器,而只打算使用MySQL的客户端程序来访问其他人维护的服务器,则可以跳过剩下的几个步骤。

(5)初始化数据目录和mysql数据库里的权限表。有些类型的安装包会替你做这一步。其中包括Linux里的RPM包和Mac OS X里的DMG包。在Windows里,没有必要初始化数据目录或授权表,因为它们已包含在发行版的预初始化操作里。

(6)启动服务器。

(7)初始化mysql数据库里的其他表。

接下来的几节提供的是对这几个步骤的详细讲解。

在MySQL安装完成之后,你可能还需要执行一些附加操作。

在第12章,可以找到针对这些操作和其他管理过程的指导说明。尤其应该阅读一下与密码分配、服务器启动和关闭,以及使用非特权用户账户运行服务器相关的章节。

这一步操作只有在想要运行MySQL服务器时才会需要。如果只是想使用MySQL客户端软件,则可以跳过这一步。

在Unix系统里,可以使用你系统中的任何一个账户来运行MySQL服务器。但是出于安全和管理方面的考虑,请不要以root身份来运行服务器。推荐你另外创建一个独立的账户,并把它用于MySQL的管理和运行。这样,你便可以使用这个账户进行登录,并拥有对数据目录的所有权限,从而可以完成维护和故障排查等操作。在MySQL里使用root之外的其他账户能带来很多好处,与之相关的更多讨论请参考12.2.1.1节。

本书使用mysql作为MySQL管理账户的Unix用户名和用户组名。如果安装MySQL只是为了自己使用,则完全可以用你自己的账户来运行它。这里,你需要把在这本书里看到的所有被用作用户名或用户组名的mysql替换为自己的登录名和用户组名。

创建用户账户的过程因系统而异。想要获得具体细节,请查询你自己的文档。(如果你使用的是Linux里的RPM包,那么在安装服务器包时会自动创建一个名为mysql的用户作为登录账户。)

在创建用于运行MySQL的账户之前,应该先检查你的系统,看看是否已存在这样的账户。许多Unix系统都会在其标准账户组里包括一个用户名和用户组名都为mysql的账户。例如,Mac OS X里会包括一个mysql账户(这样便能满足Mac OS X里的那些DMG软件包所做的假设,即假设已存在有一个名为mysql的登录账户)。

当在Windows上安装MySQL时,我推荐你使用MySQL安装程序。这个安装程序会引导你完成安装MySQL所需要的各个步骤。

在Unix里,MySQL发行包包含一个或多个下列组件。

有些软件包格式可能会把一个完整的发行包分割成多个组件,因此一定要把你需要的软件包都装上。例如,RPM包可能把发行包划分为服务器、客户端和开发包。如果你只是要连接到运行在另一台机器上的服务器,则不需要安装服务器。不过,客户端软件应该总是需要安装,这样你才能够连接到你所使用的服务器。如果你想要自己编写程序,则还会需要安装开发包。

你也可以通过源代码发行包来构建MySQL。与通过二进制(预构建)发行包来安装的方式相比,采用这种方式会涉及更多的内容,因为你需要编译该软件。不过,这种方式带来的好处是,你可以控制更多的配置参数。例如,可以编译发行包,让其只包含想要的那些存储引擎。

当你在命令提示符下输入命令时,shell(命令解释器)程序会使用环境变量PATH来确定应该到哪里查找程序。如果设置PATH环境变量,让其包含位于MySQL安装目录下的bin目录,则调用各个MySQL程序会变得更加方便。这时,只输入命令名即可,不需要指定其完整的路径名。

在Unix里,PATH变量通常是在shell的某个启动文件里设置的,如tcsh.tcshrc文件、bash.bashrc.bash_profile文件。例如,使用的是tcsh,则.tcshrc文件里便可能会有下面这样一行内容:

setenv PATH /bin:/usr/bin:/usr/local/bin

如果MySQL程序都安装在/usr/local/mysql/bin目录里,那么可以像下面这样更改PATH的值:

setenv PATH /usr/local/mysql/bin:/bin:/usr/bin:/usr/local/bin

如果使用的是bash,则它的某个启动文件里应该会包含下面这样的内容:

PATH=/bin:/usr/bin:/usr/local/bin

可以把这个设置更改为下面这样:

PATH=/usr/local/mysql/bin:/bin:/usr/bin:/usr/local/bin

在完成对shell启动文件的修改之后,这些新的设置将在下次登录时生效。

在Windows里,设置PATH值的操作步骤如下。

(1)在桌面上右击“我的电脑”,选择“属性”。

(2)选择“高级”选项卡,并单击“环境变量”。

(3)从系统变量列表中选择“路径”,然后单击“编辑”。

(4)把适当的bin目录添加到这个路径里,在前面加上一个分号,将它与路径里的最后一个目录分隔开。例如,MySQL安装在C:\mysql里,则可以把;C:\mysql\bin添加这个路径里。

重新打开一个新的控制台窗口(或者重启Windows),便可以看到这个更改生效。

在刚安装好的MySQL可以使用之前,必须对数据目录和mysql数据库里的权限表进行初始化。这些权限表控制着对服务器的访问权限。这一步操作只有在你想要运行MySQL服务器时才需要。如果只是想使用MySQL客户端软件,则可以跳过这一步。如果运行环境是Windows系统,则此步操作也可以跳过,因为这些权限表都包含在Windows发行包的预初始化动作里。

在接下来的指导说明里,DATADIR代表的是数据目录的路径名。它通常位于MySQL安装的基本目录里,名字为data或者var。一般情况下,本节里的命令都需要以root用户身份来执行。如果以MySQL账户身份(如mysql)登录,或者因为只想自己使用而以自己的账户来安装MySQL,那么不用root身份也可以执行这些命令,并且应该忽略--user选项。这时,可以跳过chownchgrp命令。

如果想对数据目录、mysql数据库和默认权限表进行初始化,可以先把当前路径切换到MySQL的安装目录,然后执行mysql_install_db脚本。(如果通过RPM包或Mac OS X包来安装,则不需要这样做,因为安装过程会自动执行mysql_install_db脚本。)例如,如果想把MySQL软件安装在/usr/local/mysql目录下,则需要执行下面这两条命令:

cd /usr/local/mysqlscripts/mysql_install_db --user=mysql

在运行mysql_install_db之后,需要更改数据目录里所有文件的所有权(包括用户和用户组)和访问模式。假设,用户和用户组的名字都是mysql,则相关命令如下所示:

chown -R mysql DATADIRchgrp -R mysql DATADIRchmod -R go-rwx DATADIR

命令chownchgrp会把所有权更改为MySQL登录账户的用户和用户组,而chmod命令会把数据目录的访问模式设置为只允许该用户访问。

最后几步实际上是更为综合的锁定过程中的一部分,详细内容请参考13.1.2节。查看该节,可以获得更多让安装变得更安全的指导。

如果mysql_install_db执行失败,可以查阅MySQL参考手册里与安装有关的章节,看看它对你所遇到的问题是如何描述的。如果mysql_install_db没有完全执行成功,则很可能它所创建的权限表也不完整。这时,应该将它们完全删除,因为mysql_install_db在发现这些权限表存在时不会再去重新创建它们。使用下面这条命令可以删除整个mysql数据库:

rm -rf DATADIR/mysql

在安装好MySQL之后,便可以启动服务器。这个过程在Unix和Windows上会有所不同。

A.3.5.1 在Unix上启动服务器

本节的命令都需要在MySQL的安装目录里执行。一般情况下,这些命令都需要以root用户身份来执行,并且需要使用--user选项来告知服务器以mysql用户身份来运行。不过,如果你是以MySQL用户身份(如mysql)登录的,或者是以你自己的账户来运行MySQL的,则可以省略这个--user选项。

先把当前路径切换到MySQL的安装基本目录(如/usr/local/mysql),然后使用mysqld_safe来启动服务器:

cd /usr/local/mysqlbin/mysqld_safe --user=mysql &

你既可以在命令行指定其他选项,也可以在选项文件里指定选项。相关信息请参考12.2.3节。

A.3.5.2 在Windows上启动服务器

如果你未把MySQL安装到安装程序选定的默认位置,则必须在服务器在启动时会读取的某个选项文件里加上一个[mysqld]选项组,以便服务器能够确定安装目录和数据目录的具体位置。最常用的选项文件是MySQL安装目录里的my.ini文件或C:\my.ini。例如,如果MySQL安装在C:\mysql下,则这个选项组将类似下面这个样子(请注意,路径名里使用的是斜线“/”,而不是反斜线“\”):

[mysqld]
basedir=C:/mysql
datadir=C:/mysql/data

如果选择了不同的安装目录,则一定要记得更改这个选项文件里的路径名。

为从命令行手动启动服务器,可以把目录位置更改到服务器安装的地方,然后输入下面这条命令:

C:\> mysqld

可以在命令行或选项文件里指定其他选项。更多信息请参考12.2.3节。

如果想以控制台模式来运行服务器,以便它能在控制台窗口显示出错信息,可以在启用它时带上--console选项:

C:\> mysqld --console

当以控制台模式运行服务器时,可以在命令行的--console选项之后,或者在选项文件里指定其他选项。

如果通过命令行来启动服务器,则只有等到服务器退出时才能看到下一个命令提示符。这时,只需要另外打开一个控制台窗口,即可运行MySQL客户端程序。

另一种在Windows上运行服务器的方法是,为MySQL创建一个服务,让其可以随着Windows一起自动启动。更多详细信息请参考12.2.2.2节。

在启动服务器后,还可以选择性地对mysql数据库里的其他系统表(如时区表和服务器端的帮助表)进行设置。

时区表可用于指定所要使用的时区。要启用它们,可参考12.6.1节的操作指导。

命令行客户端程序mysql可以通过help命令访问服务器端的帮助信息。为启用此命令,必须在mysql数据库里设置帮助表。大部分的安装方法在首次安装时都会自动完成这个设置,并且现在Windows系统里的MySQL发行包也包含预初始化好的帮助表。因此,通常不需要手动设置这些帮助表。

如果你使用的安装方法没有设置帮助表,则可以手动加载它们。操作方法是,先确保服务器正在运行。然后,找到fill_help_tables.sql文件,其中包含了用于创建和加载帮助表的SQL语句。这个文件可能的存放位置包括/usr/share/mysql、MySQL安装目录下的share目录和源代码发行包里的scripts目录。找到这个文件之后,便可以在它所在的目录里执行以下命令:

% mysql –p –u root mysql < fill_help_tables.sql

上面这条命令会提示你输入MySQL的root账户密码。如果还没有设置密码,则需要省略-p选项。

如果想要编写访问MySQL数据库的Perl脚本,则必须要有可用的DBI软件。如果在你的系统里不存在下面这些内容,则必须安装它们。

为找出某个给定的Perl模块是否已经安装,可以使用perldoc命令:

% perldoc DBI
% perldoc DBD::mysql
% perldoc CGI

如果模块已安装,那么perldoc会显示其文档。否则,可以访问http://cpan.perl.org,获得所需的模块。

本书在编写Web服务器端所使用的脚本时,使用的是带数据库访问扩展“PHP数据对象”(PDO)的PHP。假设,使用的是Apache Web服务器(也可以将其替换为其他服务器)。如果要使用PDO,则必须安装PHP。如果你还没有安装PHP,则可以访问http://www.php.net,获得相应的发行包。

MySQL的PDO驱动程序需要链接到MySQL的C语言客户端开发库(即libmysqlclient库,详见附录G)或mysqlnd库。mysqlnd是一个本机驱动,它实现了与libmysqlclient一样的通信协议,并可用于替代它。如果不使用mysqlnd,则除了安装PDO库以外,还必须安装MySQL的客户端开发库。(这个库应该在MySQL的安装过程中便安装好。)

PDO在PHP 5.0及以上的版本里都可以使用,但是本附录假定使用的最低版本为PHP 5.1,因为这是PDO第一次与PHP捆绑发行的版本。当把PDO与mysqlnd一起使用时,至少需要使用PHP 5.3版本。更多相关信息请参考http://www.php.net/pdo

如果通过源代码来编译PHP,则一定要使用包含MySQL和PDO支持的配置选项。对于Windows,PHP的二进制发行包有两种格式:Zip压缩包和.msi安装程序。如果使用Zip压缩包格式,则需要将其解压缩到你想要安装PHP的位置。使用.msi包会更加方便,因为它可以引导你对Apache进行配置以支持PHP,并且会设置环境变量PATH,让其包含PHP的安装位置。不过,如果使用安装程序,则一定要选择扩展安装,否则PDO支持和MySQL支持将不会被安装。

如果在设置PHP时遇到了问题,则可以打开包含在PHP发行包里INSTALL文件,查看其中的“详细安装”(VERBOSE INSTALL)部分。

在PHP和PDO安装完成之后,查看Apache的配置文件httpd.conf。你需要指示Apache在启动时加载PHP模块,以及如何识别PHP脚本。(配置文件httpd.conf可以使用Include指令来包含其他文件。如果在该文件里没有看到下面几段所描述的信息,那么还要检查其他包含文件。)

为告知Apache加载PHP模块,必须在httpd.conf里的适当部分包含LoadModuleAddModule指令(请查看其他类似的指令)。PHP安装可能已经替你添加了这些指令。如果没有,则必须自己动手添加它们。它们看起来应该像下面这个样子,但是LoadModule指令里的路径名可能需要根据你的系统进行相应的调整:

LoadModule php5_module libexec/libphp5.so
AddModule mod_php5.c

接下来,编辑httpd.conf文件,告知Apache如何识别PHP脚本。PHP的识别依据的是PHP脚本的文件扩展名。最常用的扩展名是.php,这也是本书的各个示例所使用的扩展名。为把.php用作PHP脚本扩展名,需要把下面这行内容添加到httpd.conf文件里:

AddTtype application/x-httpd-php .php

也可以让Apache在URL的末尾没有指定任何文件名时,把index.php文件识别为目录的默认文件。在http.conf里找到下面这样的行:

DirectoryIndex index.html

将其修改为下面这个样子:

DirectoryIndex index.php index.html

在编辑完Apache的配置文件之后,停止httpd服务器(如果它正在运行的话),然后将其重启。在许多系统里,可以使用如下命令来完成此操作(以root用户身份来执行):

/usr/local/apache/bin/apachectl stop/usr/local/apache/bin/apachectl start

也可以通过设置,让Apache随同系统的启动和关闭一起自动开始和停止。更多指导说明请参考Apache的帮助文档。通常情况下,可以在系统启动时运行apachectl start命令,在系统关机时运行apachectl stop命令。


本附录将介绍MySQL提供的各种数据类型。与这些类型的应用有关的更多信息请参考第3章。

数据类型名称的规范遵循以下约定。

在对各种数据类型的描述里都包含有一种或多种下面的信息。

含义:对该类型的简短描述。

可用属性:指的是一些可能会与CREATE TABLEALTER TABLE语句里的数据类型有关联的可选属性关键字。这些属性将按字母表的顺序依次列出,但这并不表示在CREATE TABLEALTER TABLE语句(参考附录E)里也必须按同样的顺序列出它们。在各个数据类型的描述里并没有把包含那些可应用于所有(或近乎所有)数据类型的全局属性列出来。这些全局属性都集中列在下面,而没有将它们单独列在各个类型的描述里。

允许长度:适用于字符串数据类型的列,它指的是该类型列的值被允许达到的最大长度。

取值范围:适用于数字类型和时态(日期/时间)类型,它指的是该类型所能表示的最大值。对于整数类型,因为存在有符号和无符号两种情况,所以需要指定两个取值范围,并且每种情况的取值范围也不同。

零值:适用于时态类型,它指的是如果有非法值插入该类型列里时,实际存入的那个零值。(必须将SQL模式设置为允许这种值;否则,有非法值插入时,会出现错误。)

默认值:类型定义没有明确设定DEFAULT属性时的默认值。这只在没有启用严格SQL模式时才适用。如果在严格模式中没有给出DEFAULT子句,并且能接受NULL值,那么会使用默认的NULL来定义列;否则,用非默认值定义。更多相关信息请参考3.2.3节。

存储空间要求:存储有关类型的值所需要使用的字节或字符个数。对于某些类型,该值是固定的。对于其他类型,该数字会因存储在列里的值的长度的不同而有所差异。

比较方式:对于字符串类型,这个值表明了比较操作进行的方式(这对分组、排序和索引操作也有影响)。二进制串类型是使用每个字节的数字值逐个字节地比较。非二进制串类型会根据字符集排序规则逐字符进行比较。

同义词:类型名称的同义词。

备注:与该类型有关的杂项说明。

如果不确定自己的MySQL版本会如何处理给定的列定义,可以这样做:创建一个表,让其包含一个使用你正迷惑的方式进行定义的列;然后使用SHOW CREATE TABLEDESCRIBE查看MySQL对该定义的报告内容。例如,如果想不起来字符类型属性UNICODE和缩写数据类型SERIAL的作用,可以创建一个使用它们的表;然后告知MySQL显示最终的表定义:

mysql> CREATE TABLE t (c CHAR(10) UNICODE, s SERIAL);
mysql> SHOW CREATE TABLE t\G
*************************** 1. row ***************************
    Table: t
Create Table: CREATE TABLE `t` (
 `c` char(10) CHARACTER SET ucs2 DEFAULT NULL,
 `s` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 UNIQUE KEY `s` (`s`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

MySQL提供了精确值和近似值数字数据类型。不同的数字类型有着不同的取值范围,因此要根据想要表示的数值范围来加以选择。此外,还有一种BIT类型,专门用来表示位域值。

整数和定点数(DECIMAL)类型都是精确值数据类型。FLOATDOUBLE类型都是近似值数据类型。对于精确值类型,数值在存储和给定时是完全一致的,只要数值和计算结果没有超出这些类型的取值范围,计算过程就将精确地进行而不会产生任何舍入误差。对于近似值类型,计算会产生舍入误差。

对于整数类型的列,如果为其指定了AUTO_INCREMENT属性,就必须对它进行索引。把NULL值插入一个AUTO_INCREMENT列将导致下一个序列值被插入该列里。通常情况下,新序列值比该列的当前最大值大1。第3章对AUTO_INCREMENT列的精确行为作了详细的描述。(AUTO_INCREMENT属性也可以用于浮点类型列,只是这种做法不太常见。)

属性ZEROFILLUNSIGNED可以用于除BIT类型以外的数字类型。

适用于整数和浮点数据类型的SERIAL DEFAULT VALUE属性是NOT NULL AUTO_INCREMENT UNIQUE的缩写形式。

在某些情况下,指定一种属性会导致另外一种属性也被启用。为数字类型指定ZEROFILL属性会导致该列被自动设定为UNSIGNED。指定AUTO_INCREMENT属性会导致该列被自动设定为NOT NULL

请注意,语句DESCRIBESHOW COLUMNS会报告说某个AUTO_INCREMENT列的默认值是NULL,尽管你无法把一个NULL值插入这个列里去。这说明,你可以通过在创建新行时将AUTO_INCREMENT列的值设置为NULL的方式来生成该列的默认值(即下一个序列编号)。

0~53以外的 p值是非法的。
可用属性: UNSIGNEDZEROFILL
取值范围:参见本节后面对 FLOATDOUBLE 类型的描述。
默认值:如果列可以为 NULL,则默认值为 NULL;如果带有 NOT NULL属性,则默认值为0。
存储空间要求:单精度值需要4个字节,双精度值需要8个字节。

MySQL中的字符串类型是通用类型,通常用来存储二进制或者字符(文本)数据。这些字符串类型能够容纳的值的最大长度是不一样的,可以根据你想把它们视为二进制串还是非二进制串来选择。

BINARYVARBINARYBLOB类型是二进制串类型。二进制串是一个字节序列,其长度单位是字节。二进制串没有字符集的概念,因此对值的比较是以字节值为基础的。

CHARVARCHARTEXT类型是非二进制串类型。非二进制串是一个字符序列。它有字符集和排序规则的概念。字符集定义的是允许数据类型使用的各种字符;排序规则定义的是字符排列顺序。在一个非二进制串列的定义里指定的长度表明的是该列能够容纳的最大字符数。

非二进制串值的长度单位通常为字符,但也可以使用字节为单位。若想获得某个非二进制串的字符长度或字节长度,可以分别使用函数CHAR_LENGHT()LENGTH()。如果某个非二进制串只包含单字节字符,且字符长度为n,那么其字节长度也为n;但如果它包含的是多字节字符,那么其字节长度会大于n。这种差异会影响非二进制串列的存储空间。

ENUMSET类型外,非二进制串类型(CHARVARCHARTEXT)也都可以指定字符集和排序规则。

服务器支持的字符集和排序规则可以通过SHOW CHARACTER SETSHOW COLLATION语句来查询。这些语句将显示出每种字符集的默认排序规则。另外,还可以去查看数据库INFORMATION_SCHEMA里的CHARACTER_SETS表和COLLATIONS表——它们包含的信息相同。

有些长到无法存储在字符串列里的值,对这些值的处理取决于SQL模式设置。如果没有启用严格SQL模式,则超长值会被截短,以便能刚好存放在该列里。如果被截去的字符不全是空格,那么MySQL还将生成一条警告消息。在严格SQL模式下,如果必须截去非空格字符,那么服务器会产生一个错误,并且不会插入任何值。

不同的字符串类型值有不同的尾部填充方式。

MySQL提供了几种表示时态数据的类型。用于表示日期和时间的类型,既可以单独使用,也可以组合使用;并且当不需要完整的日期时,还有一种用于存储年份的类型。对于新建行,有些类型可以被自动初始化成当前的日期和时间;当行里的其他列发生变化时,它们还可以被自动更新为当前的日期和时间。

日期格式里的术语CCYYMMDD分别代表世纪、年、月、日。时间格式里的术语hhmmss分别代表小时、分、秒。此外,MySQL 5.6.4为TIMEDATETIMETIMESTAMP类型引入了小数秒,并且允许选择性地将小数部分的最大精度设置为6位数(微秒)。时间格式里的术语uuuuuuu表示的是小数部分。(对于MySQL 5.6.4以前的版本,小数秒部分会被忽略。)

在类型语法描述里,fsp表示的是允许使用小数秒部分的类型的小数秒精度。该精度必须是一个0~6的值,其中,0表示的是无小数秒部分,6表示的是精确到微秒。如果没有指定fsp,则默认为0。

自MySQL 5.6.4起,对于那些允许使用小数秒部分的类型,其存储空间要求有所变化。对于非小数秒部分的存储,与前面各个类型的描述是一样的。对于小数秒部分的存储,所有类型都是一样的。下表列出了所需要的字节数。

小数秒精度

存储空间要求

0

0个字节

1、2

1个字节

3、4

2个字节

5、6

3个字节

MySQL 5.6.5引入了自动把当前时间戳作为初始值并进行更新的扩展支持。以前,这些属性最多只能用于表的一个TIMESTAMP列;现在,它们可以用于任何TIMESTAMP列,并且还可用于DATETIME列。


相关图书

Web应用安全
Web应用安全
MySQL是怎样使用的 快速入门MySQL
MySQL是怎样使用的 快速入门MySQL
MySQL是怎样运行的:从根儿上理解 MySQL
MySQL是怎样运行的:从根儿上理解 MySQL
深入浅出MySQL:数据库开发、优化与管理维护(第3版)
深入浅出MySQL:数据库开发、优化与管理维护(第3版)
MySQL数据库管理实战
MySQL数据库管理实战
PHP、MySQL和JavaScript入门经典(第6版)
PHP、MySQL和JavaScript入门经典(第6版)

相关文章

相关课程