mysql一个表不同mysql 条件查询多次

简单 PHP + MySQL 数据库动态网站制作 – Blog - Renfei Song
由于学校社团活动需要一个支持数据库和用户提交信息的动态网站,所以花了几个晚上速成了一下 PHP 和 MySQL 的相关知识,之后就写了一个网站,结果表明运行的很顺利。之前感觉这种动态网站很深奥,以为会很难,可是没想到如果只实现不太复杂的功能的话,其实并不很难。
我争取用最浅显易懂的语言来说明一下是如何实现的。阅读本文需要简单的 HTML 基础知识和(任一编程语言的)编程基础知识(例如变量、值、循环、语句块的概念等)。
PHP 是一种解释性语言,可用于对网页进行预处理。PHP 脚本在服务器端运行,其运行结果是一个可用来显示的网页。尽管可以完成许多类似工作,但是 JavaScript 和 PHP 的一大区别就是,JavaScript 是在浏览器端运行的。事实上,浏览器会接收 JavaScript 代码并运行它,所以用户是可以查看 JavaScript 代码的。而 PHP 不会将原始代码交给浏览器, 只会将其运行的结果交给浏览器,所以用 PHP 处理用户登陆、用户权限等问题是安全可靠的。
PHP 与 HTML
实际编写的时候,通常采用的方式是建立扩展名为 php 的文件(网页文件本质上是文本文件)。编写 php 代码和编写 html 代码并没有多少区别,而最方便的地方在于,在一个 php 文件中,两种代码是可以混编的。
规则:php 代码需要包含在 &?php ... ?& 标签中,就像这样:
// code goes here
提示:这是一个 php 和 html 混编的较为生动的例子。
if ($var == true):
&html id="ie6"&
&html id="ie8"&
这里的意思是,如果 php 中的变量 $var 的值为 true,则放置一个标签,否则放置另一个标签。PHP 的 if 语句可以像上面那样写,也可以写成C/C++风格的:
if ($var == true) {
// do something
// do other things
关于 PHP 中的操作符
PHP 采用的操作符和 C/C++ 是类似的,例如用 = 表示赋值,== 表示相等性比较,以及 & 和 & (小于、大于)比较符、! 取反、&&逻辑与、||逻辑或等。当然,也支持 +-*/ 等数学表达式的运算。
关于 PHP 中的变量
PHP 中变量的命名一律以符号 $ 开头,可以使用下划线,例如 $is_logged_in 就是一个表意清晰的变量名。和大多数编程语言不同,PHP 中变量没有类型的概念,而且不用声明就可以直接使用。虽然很爽,但是变量多了的时候也容易混乱,这一点需要特别注意。
关于 PHP 中的语句
这一点 PHP 和许多其他常见的编程语言很类似,也可以用 if...else 选择语句(之前已经见过了),PHP 还包括 while 循环、foreach 循环等,以后遇到了会详细介绍。
MySQL 基础
使用 MySQL 数据库是存储数据的一种方法,MySQL 需要和 PHP 配合来完成对数据库的查询(这里术语“查询”包括写入、更新、读取等)操作。利用 MySQL,你可以创建许多数据库(database),每个数据库可以包含多个表(table),而每个表包含若干字段。为了高效,一般会采取分类维护多个表的方式,而不是把所有数据都储存在同一个表中。
MySQL 需要服务器支持。使用的第一步是建立一个数据库,可以用相应的图形化工具(例如 phpMyAdmin)来建立数据库,也可以在终端直接使用下列 SQL 语句来创建一个名为 database_name 的数据库:
CREATE DATABASE database_
创建好数据库之后,需要创建表。可以通过下列 SQL 语句来创建一个名为 table_name 的表:
USE database_
CREATE TABLE table_name (
first_name varchar(30),
last_name varchar(30),
第一句说明在哪个数据库中添加表,第二个说明添加的表的详情。这里我们在表中添加了两个字段,分别叫做 first_name 和 last_name,它们的类型是 varchar(30)。其中 varchar 是一种可变长字符类型,而 30 代表了最大长度。
其他常见的数据类型如下:
VARCHAR(100) --可变字符
CHARACTER(1) --定长
INTEGER --整数
DECIMAL(10, 2) --小数(小数点前后的位数)
TIMESTAMP --日期和时间
DATE --日期
你可能已经看出来了,MySQL 的注释符为 --。你可能觉得没太大用,但是它却是一种稍后要提到的攻击的关键之处。此外,一般字符串都应该使用变长的VARCHAR类型,而非定长的CHARACTER类型,因为后者会占据更多的空间,而这是不必要的。
使 PHP 和 MySQL 协作
第一种方式
现在你已经创建好了 SQL 数据表,并对 PHP 语言有了一个概览。下面我们直奔主题,学习如何对数据表进行查询。
为了使 PHP 和 MySQL 进行交互,需要为 PHP 提供你的数据库用户名、密码、数据库名和数据表名。当然,最重要的,查询操作的 SQL 语句。我们一一来观察是如何实现的。
define('DB_HOST', 'localhost');
define('DB_USER', 'renfei');
define('DB_PASS', 'root');
define('DB_NAME', 'database_name');
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
$query = "INSERT INTO table_name (column1, column2) VALUES ('value1', 'value2')";
mysqli_query($dbc, $query);
下面来解释一下这一坨代码的工作原理。
首先 3~6 行为 PHP 中的 define 语句,作用很明显,把 DB_HOST 定义为 localhost,下面的代码中就可以使用 DB_HOST 来代替 localhost。这样做的好处在于,如果mysqli_connect函数在代码中出现多次,修改参数的时候则只需修改 define语句,非常方便。
然后是一个叫做 mysqli_connect() 的函数,它需要四个变量,分别是主机名、用户名、密码、数据库名。这个函数执行后的返回值传递给变量 $dbc,$dbc 包含了一次数据库连接。注意,这个变量名是任意的,并不强制要求叫做$dbc。
然后,我们把要对数据库执行的操作对应的 SQL 语句以字符串的形式赋给变量 $query。这个变量名也是任意的。应该注意到,这里的 SQL 语句是不以分号结尾的。
最后,我们执行 mysqli_query(); 函数,该函数有两个参数,分别是一个数据库连接,和一个 SQL 查询操作。执行该函数后,相应的查询操作被执行。
如果把这些代码保存成一个网页,当用户打开网页的时候,如果各项参数正确,它就会完整地运行下去。
这里的 SQL 语句的含义是向叫做 table_name 的表中插入一行,其中把 colume# 字段的值相应地设置为 value#。这里只设定了两个字段的数值(表中还可以有其他字段;没有显式说明的字段则留空或者使用数据表指定的默认值)。该语句的通用形式为:
INSERT INTO table_name (column1, column2, ...) VALUES ('value1', 'value2', ...)
如果你要做的仅仅是执行一个 SQL 语句,那么使用这种模式就可以完成。提醒一下,$dbc 变量往往是重复使用的。
另一个常用的 SQL 语句就是修改某一行。它的形式为:
UPDATE table_name SET
column1 = 'preferred_value1',
column2 = 'preferred_value2',
WHERE id = '$id'
当然,这个语句应该是写到一行的,不过为了清晰我分开来写。它的含义是,修改名为 table_name 的表中字段 id 的值是变量 $id 的值的所有行,把 column1 字段的值设为 preferred_value1,把 column2 字段的值设为 preferred_value2,依此类推。这里我们还看到,值既可以用常量表示,也可以用变量表示。
注意:会修改所有符合 WHERE 子句限定的条件的行(如果省略 WHERE 子句,就会修改所有行)。WHERE 子句可以设定多个条件,也可以使用比较运算符。例如:
WHERE age & 20 AND gender = 'male'
WHERE is_admin = 'true' OR id = '$id'
(如果你想问 AND 和 OR 为什么不是符号 && 和 || 的话,我想提示你,不要把 PHP 语言和 SQL 语言搞混了。这是 SQL 语言,而我只说过 PHP 语言和 C/C++ 有些类似)。
下面介绍其他 SQL 语句。
--删除table_name表中的所有行
DELETE FROM table_name
--删除table_name表中email字段为的所有行
DELETE FROM table_name WHERE email = ''
--删除table_name表
DROP TABLE table_name
--删除table_name表中的score字段
ALTER TABLE table_name DROP COLUMN score
--给table_name表添加一个叫age的字段,类型为 INTEGER
ALTER TABLE table_name ADD COLUMN age INTEGER
可见,第一种方式的本质就是编写一条 SQL 语句,然后通过 PHP 来执行它。下面,我们来看第二种方式。
第二种方式
有时,我们不满足于让服务器去执行一条 SQL 语句。我们会需要从数据库中查询信息,然后把得到的信息储存起来(其实就是储存在变量中)。这样,我们需要一些额外的工作。先看一坨代码:
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
$query = "SELECT * FROM table_name WHERE problem_id='$id'";
$result = mysqli_query($dbc, $query);
$row = mysqli_fetch_array($result);
$problem_title = $row['problem_title'];
这里我们省略了 define 语句。
这一坨代码和上一坨的主要区别是,我们使用了 mysqli_query() 函数的返回值,把它保存到 $result 变量中。这个 $result 变量里边保存的即为执行 SELECT 语句的返回结果。
解释一下 SELECT 语句,它的作用是选取 table_name 表中符合 WHERE 子句条件的所有行。上面的语句会选定每一行的所有字段(通配符说明了这一点),并且把这些信息全部储存到变量 $result中。
然后,用变量 $row 储存 mysqli_fetch_array() 函数的返回值。$row 这个变量非常神奇,$row['column_name'] 这个事儿包含的内容正是刚才选定的行的 column_name 字段的值(事实上,$row 正是一个数组)。这里,我们把它赋给了 $problem_title 变量。
到这里你应该问一个问题:如果满足 WHERE 子句条件的有不止一行的话怎么办?要解答这个问题,需要稍微细致的讲解一下 $row 这个事儿。如果满足条件的只有一行,那么使用 $row = mysqli_fetch_array($result) 自然会把这唯一的一行信息储存到 $row 中。如果有很多行,那么第一次使用 $row = mysqli_fetch_array($result) 会把第一行的信息储存到 $row 中,而第二次使用 $row = mysqli_fetch_array($result) 会把第二行的信息储存到 $row 中。如果这时没有下一行了,再次调用的话 $row 会储存逻辑假(false 或 0)。类似,如果符合 WHERE 子句条件的一行都没有,那么执行后 $row 直接存储逻辑假。
最后补充一点刚才没有提到的。如果不需要所有字段的数据,可以只选择需要的字段。方法是把原来 SQL 语句中的通配符换成字段名称。例如:
SELECT problem_name, problem_type FROM table_name WHERE problem_id='$id'
while 循环在 PHP 中的应用举例
如果我们要把一个数据库的许多行信息都展示在网页中,那么需要用到 while 循环和上面的第二种方式。代码如下:
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
$query = "SELECT user_id FROM database_name ORDER BY user_id ASC";
$result = mysqli_query($dbc, $query);
while ($row = mysqli_fetch_array($result)) {
$user_id = $row['user_id'];
echo "&tr&";
echo "&td&".$user_id."&/td&";
echo "&/tr&";
如果有一定编程基础的话上面的代码很容易看懂。上面新出现了三种用法,说明如下:
SELECT 语句可以附加一个 ORDER BY 子句,用来控制顺序。例如这里是按照字段 user_id 升序排列。下面的例子会先按照 user_rank 降序排列,user_rank 相同时按照 user_id 升序排列:
SELECT user_id, user_rank FROM table_name ORDER BY user_rank DESC, user_id ASC
关于 PHP 中的 echo 语句,它可以用来生成文本,类似于 C 中的 printf() 函数。这里利用它直接生成 HTML 代码。它的用法参考例子就可以了。
关于符号 . 的用法,它的作用是连接字符串(和变量),往往和 echo 配合使用,用法参考示例。
从表单获取信息
这一部分我们演示如何构建一个表单,使用户填写这个表单并把内容储存到数据库。这一技术是用户注册系统和用户互动的基础。
要实现这个功能,需要 HTML 和 PHP 配合完成。HTML 负责表单,而 PHP 负责获取信息并使用 SQL 查询储存信息。首先来看 HTML 部分(就是普通的表单):
&form method="post" action="&?php echo $_SERVER['PHP_SELF']; ?&"&
&label for="username"&用户名:&/label&
&input type="text" id="username" name="username" /&&br/&
&label for="info"&信息:&/label&
&input type="text" id="info" name="info" /&&br/&
&input type="submit" value="Submit" name="submit"/&
属于 HTML 部分的不再解释了,说一说新鲜的。这里的 action 属性后面的 $_SERVER['PHP_SELF'](严格地说,$_SERVER),是 PHP 的一个超级全局变量,内容是当前页面的相对路径,例如 signup.php。这个 action 属性的含义是指定用户填写的信息在哪里被处理,这里是在当前页面处理。一般的做法都是将负责处理这部分信息的 PHP 代码和 HTML 代码放在同一页面内。
下面来看一下相应的 PHP 处理部分的代码:
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if (isset($_POST['submit'])) {
$user = $_POST['username'];
$info = $_POST['info'];
$query = "INSERT INTO table_name (tb_user, tb_info) VALUES ('$user', '$info')";
mysqli_query($dbc, $query);
echo "&p&提交成功&/p&";
mysqli_close($dbc);
首先仍然是建立数据库连接。当用户点击 sumbit 按钮后,表单的内容会被储存在 PHP 中 $_POST 超级全局变量内,这个超级全局变量仍然是一个数组。isset() 函数用来检查变量是否被设置,只有用户点击 submit 后 isset($_POST['submit']) 才返回真,所以不用担心,首次加载表单(那时用户还没有填写任何内容)是不会执行这部分 PHP 代码的,只有用户提交之后才会执行。用户填写的具体内容可以用 $_POST['name'] 来获取。这里的 name 对应的是 HTML 中 name 属性的内容。这一段程序把用户填写的内容赋给变量,然后执行插入到数据库的操作。
这里新出现了一个内容,就是 mysqli_close() 函数,它的作用是关闭数据库连接。当我们不再需要这个连接的时候,及时关闭是一个好主意。
需要注意的是,这仅仅是最简单的代码,而且实际上是不完善的。如果要真正投入使用,我们需要使它更健壮一些。下面逐一讨论这些内容。
检查用户输入是否合法
如果用户根本没有填写表单,就直接点击提交按钮,会发生什么?在上面的实例中,PHP 依然会乖乖地把空内容插入,而这显然是垃圾信息,不是我们需要的。所以,需要在插入前检查被插入的变量是否为空。例如:
if (!empty($user) && !empty($info)) {
// 插入操作
这里出现了 empty() 函数,用于检查内容是否为空。注意这里使用 isset() 是无效的,因为 isset() 检查的是是否“被设置”,而被设置为空也属于被设置。
用户输入有误时,上面的改进除了不执行SQL查询,并没有多少直观上的变化。用户不会收到任何信息表明他们的填写是不合适的。所以我们要在这时产生一些提示,引导用户正确填写表单。
if (!empty($user) && !empty($info)) {
// 插入操作
echo "请填写全部内容后再提交";
防范 SQL 注入攻击
我们执行的 SQL语句中包含变量,执行的时候会直接把变量内容替换进去。而如果攻击者在输入框中输入一些危险的字符(通常包含 SQL 注释符 --,以及其他预先精心设置的内容),就可能导致该次 SQL 查询完全被改写成攻击者需要的意思。为了防范这种攻击,我们需要对可能存在的危险字符进行过滤和转义,较为便捷的方法是使用两个函数。改进后的部分如下:
$user = mysqli_real_escape_string($dbc, trim($_POST['username']));
$info = mysqli_real_escape_string($dbc, trim($_POST['info']));
如果用户第一次填写失败,他们希望能保留已经填写好的内容,只做些修改就好了。这需要使用粘性表单技术。要实现,只需要稍稍改动 HTML 表单部分的代码:
&label for="username"&用户名:&/label&
&input type="text" id="username" name="user" value="&?php if (!empty($user)) echo $ ?&" /&
显而易见,如果用户填写后因为某些原因没有提交而是回到了这个表单,并且之前填写了 user 字段的内容,那么此时 $user 变量已经被赋值了。那么就会在 HTML 表单显示这些内容,避免用户再次输入。
构造一个注册页面
虽然上面说了很多,但是仅仅满足了我们最基本的输入要求。许多时候我们需要更为复杂的功能。举例来说,要写一个注册页面,必须检查用户名是否重复,还要对密码采取某种技术加密以保证安全。
检查用户是否重复
基本原理就是,根据需要判重的字段(例如用户名)去数据库搜索。如果发现结果则用户名重复,如果没有找到则允许注册。需要一个新函数 mysqli_num_rows(),返回 SELECT 语句得到的行数,根据其是否等于 0 进行判断。
$dbc = mysqli_connect(DB_HOST, DB_USER, DB_PASS, DB_NAME);
$query = "SELECT * FROM table_name WHERE user_name = '$user'";
$data = mysqli_query($dbc, $query);
if (mysqli_num_rows($data) == 0) {
// 把内容插入数据库
echo "注册成功";
mysqli_close($dbc);
echo "用户名已被占用,请重新选择用户名";
$user = "";
把 $user 清空是为了配合粘性表单。
需要说明的是 exit(); 函数,它会立刻终止 PHP 的运行。因为用户已经注册成功,没有必要执行后面的任何代码,所以使用这个函数。写自己的程序的时候可以亲自试验是否需要这一行、PHP 和 HTML 在 php 中的顺序不同有何影响。我通常的做法是把 PHP 代码放在前面,HTML 代码放在后面。
对密码进行加密存储
明文存储密码是对用户很不负责的,不仅数据库管理员可以看到密码,一旦数据库泄漏,密码就会被公开。所以,我们应该加密存储用户密码。在 PHP 中,可以使用 sha1() 函数进行加密(sha 即 secure hash algorithm 的首字母缩写),它是一种不可逆的加密,加密后会生成定长的一段字符串,并且是无法由这段字符串还原原密码的。
加密的原理是,用户输入密码后,利用 PHP 把 hash 过的密码储存在数据库中。用户登陆的时候,把用户输入的密码进行 hash 运算,之后和数据库中的进行比对。
使用方法如下:
sha1($password)
对于注册页面,不要忘记确认密码,即让用户输入两次,比较确定相等后再执行注册。
识别用户登录:Cookie
只注册没有用,必须添加登录功能。登录功能可以使用 Cookie 来实现。这里假定你已经了解 Cookie 的基础知识,只说如何实现。
设置 Cookie
setcookie('user_type', $user_type, time() + (60 * 60 * 24 * 30));
上面的代码用来设置 Cookie,其中函数的第一个参数为 Cookie 名称,第二个参数为数值(这里用一个变量传递),第三个参数为过期时间,单位秒。示例为一个月。
可以用设置多个 Cookie 来存储许多内容,例如用户 ID、用户组(管理员还是普通用户)等。
验证 Cookie
用户登陆后,我们可以设置一个 Cookie 来存储登录信息(即哪个用户登陆的),然后通过检查这个 Cookie 来设定相应功能。
if (isset($_COOKIE['user_type'])) {
$user_type = $_COOKIE['user_type'];
删除 Cookie
要删除 Cookie,只需要把过期时间设定在过去。
setcookie('user_type', '', time() - 3600);
不要问我为什么设定在过去一个小时,设定几个小时都没问题。
Cookie 的安全性
设置 Cookie 有其潜在的危险。由于 Cookie 是保存在用户本地的,所以用户完全可以通过篡改 Cookie 来达到他们的目的。所以,把 Cookie 的值设置得“通俗易懂”不是一个好主意。例如,我们要用 Cookie 来保存登陆的用户名,如果单纯把这个用户名存入 Cookie,那么攻击者会很容易通过修改成他人的用户名来伪造 Cookie 登陆。所以,我们需要其他的手段来防止这一点。
我的做法是,用户注册的时候,把用户名按一定手段进行变换,然后使用 SHA() 函数加密生成一个用户密钥,然后把这个密钥储存进数据库。登陆时,再把这个密钥存储到 Cookie 中,通过检查 Cookie 中的密钥和数据库中用户密钥的匹配情况判定是哪位用户登录。这样,只要你的用户名变换方法不泄露,攻击者就很难按他们的想法伪造 Cookie。
使用 GET 方法
在网页间传递信息除了刚才介绍的 POST 方法外,还有 GET 方法。GET 方法是通过 URL 来完成信息传递的。例如,构造下列网址:
http://www.renfei.org/index.php?id=2
网址最后有 ?id=2 标记。这个信息会储存在 $_GET['id'] 这个超级全局变量中,并且可以在 PHP 中使用:
if (isset($_GET['id'])) {
$id = $_GET['id'];
// code goes there
这个例子中我们把 2 赋给了变量 $id。当然,也可以构造这样的网址:
http://www.renfei.org/index.php?id=2&message=10
除了多一个可以使用的 $_GET['message'] 以外没有任何不同。
这个特性的用处之一就是可以根据网址的不同,配合数据库查询,返回不同的网页内容。例如我做的在线问答系统,就是根据 problem_id 来返回不同题目的。
注意,由于 GET 方法的数值是不可靠的(用户可以手动构造 URL 来传递他们想要的参数),所以应该仅仅用它来做一些无关痛痒的事情(例如显示不同的页面内容)。这里我并没有强调 GET 方法的数值是“透明”的:虽然 POST 方法的数值不会显示在 URL 中,但是它还是会通过 HTTP Header 发送到服务器,用许多插件和小工具都可以查看 HTTP Header 信息。
另外,如果你的表单是用来上传文件的,那么估计你会更喜欢 POST 方法:因为
GET 方法得到的 URL 可能会很长,甚至超过浏览器的限制!
最后一部分,来讲一下使用模板构造一个网站。
事实上,网站的每个页面中,有许多部分是完全相同的,例如数据库连接常量(就是那些define语句)以及每一页的 header 和 footer 部分等。这样,我们没必要在每一页内写相同的代码。除了麻烦和浪费空间以外,还有一点很重要的原因,就是修改的时候工作量很大。
PHP 中 require_once 语句作用就是把其他文件的内容插入此处。例如,我们可以创建一个define.php,把define语句全部写到里面,并在每个页面顶部添加如下语句:
require_once "define.php";
这样一来,会把 define.php 中的内容插入当前位置。同理,我们可以建立一个 header.php 和 footer.php,写好页面的头部、底部之后在每个其他页面导入就可以了。
PHP的错误处理
分级的错误信息
最后来讲一下 PHP 的错误处理机制。如果你写了有错误的 PHP 代码,那么运行的时候系统会自动生成一些错误提示信息并且打印到屏幕上,以提醒用户修复。通常,这些错误信息是分级的。首先,是 notice。如果屏幕出现了 notice: (...) 的提示说明你有需要修复的小问题(你没有完全按照规则进行),不过问题不大,代码还是会继续执行完毕。而 warning 则更严重一些,如果出现 warning,你可能需要思考一下你是否真的知道自己在做什么,并作出修改。但是,程序仍然会运行。如果出现了 error,那么 PHP 是在跟你说:你是个白痴;这种代码无法执行,程序的运行会中止。
在写 PHP 程序的时候,我们需要这些错误提示来帮助我们改正错误,但是当产品发布的时候,开发人员往往倾向于隐藏错误提示:用户收到这些信息是很让人恼火的,而且,让他人知道你的代码有什么漏洞总归不是一个好主意,因为这可能被某些图谋不轨的攻击者加以利用。
Suppression Operator
有时,为了代码的简洁性考虑我们可能会故意犯一些无关痛痒的小错误。例如,如果 $_GET 中的某一个元素不一定总会被提交到 PHP,那么理论上应该使用 isset() 函数来进行检测。但是,如果你觉得到处使用这个函数太麻烦了,可以省略 isset() 函数而直接使用这个元素,只不过如果它没有被设置的话会返回一个 notice 错误信息(类似于 C/C++ 中的变量未声明)。这时,为了忽略这一条信息,可以使用错误抑制操作符 @。例如:
if (@$_GET['opt']) {
// code goes here...
尽管没有特别强调,但是有几个函数是需要灵活掌握和使用的,例如 exit()。它可以立刻结束 PHP 程序的运行。例如,有的页面需要一定用户权限才能访问,则可以把验证权限的代码放在页面顶端,如果验证失败则显示错误信息并调用 exit() 函数。
当一个 SQL 连接的使命完成后,不要忘了用 mysqli_close() 关闭它。
设计 SQL 数据库的结构是一件非常重要的事情,设计的原则是高效且便于查询。一旦你的数据库充满各种信息,再想更改它的结构就会变得有些困难。
SQL 的知识这里介绍得不多。它有许多特性,比如默认值、主键等。默认值的意思是如果不设定,那么该字段采用默认值;主键则规定该字段每行是不能重复的。默认值除了固定字符以外,还可以设定为时间,甚至自增。例如,要建立一个用户数据库,为每个用户分配一个唯一 ID,则可以把数据库中的 ID 字段设为 AUTO INCREMENT,这样每次不用手工维护这个字段,只要新增一行,这个字段的数值就增 1(默认从 1 开始),很方便。 一般会把这种 ID 字段设为主键。
本来打算简要介绍一下 PHP 和 MySQL 的,但是一写就是 7000 字。即使如此,本文介绍的所有特性也仅仅是构建一个动态网站最基本的知识,而且许多非重要的知识并没有介绍。你应该通过书籍更深入地学习。
额外推荐 PHP 的文档,对新手非常友好,值得一看:用好SELECT索引 提高MySQL查询统计速度
日13:28  来源:
  数据库系统是管理信息系统的核心,基于数据库的联机事务处理(OLTP)以及联机分析处理(OLAP)是银行、企业、政府等部门最为重要的计算机应用之一。从大多数系统的应用实例来看,查询操作在各种数据库操作中所占据的比重最大,而查询操作所基于的SELECT语句在SQL语句中又是代价最大的语句。  举例来说,如果数据的量积累到一定的程度,比如一个银行的账户数据库表信息积累到上百万甚至上千万条记录,全表扫描一次往往需要数十分钟,甚至数小时。如果采用比全表扫描更好的查询策略,往往可以使查询时间降为几分钟,由此可见查询优化技术的重要性笔者在应用项目的实施中发现,许多程序员在利用一些前端数据库开发工具(如PowerBuilder、Delphi等)开发数据库应用程序时,只注重用户界面的华丽,并不重视查询语句的效率问题,导致所开发出来的应用系统效率低下,资源浪费严重。因此,如何设计高效合理的查询语句就显得非常重要。本文以应用实例为基础,结合数据库理论,介绍查询优化技术在现实系统中的运用。   分析问题  许多程序员认为查询优化是DBMS(数据库管理系统)的任务,与程序员所编写的SQL语句关系不大,这是错误的。一个好的查询计划往往可以使程序性能提高数十倍。查询计划是用户所提交的SQL语句的集合,查询规划是经过优化处理之后所产生的语句集合。DBMS处理查询计划的过程是这样的:在做完查询语句的词法、语法检查之后,将语句提交给DBMS的查询优化器,优化器做完代数优化和存取路径的优化之后,由预编译模块对语句进行处理并生成查询规划,然后在合适的时间提交给系统处理执行,最后将执行结果返回给用户。在实际的数据库产品(如Oracle、Sybase等)的高版本中都是采用基于代价的优化方法,这种优化能根据从系统字典表所得到的信息来估计不同的查询规划的代价,然后选择一个较优的规划。虽然现在的数据库产品在查询优化方面已经做得越来越好,但由用户提交的SQL语句是系统优化的基础,很难设想一个原本糟糕的查询计划经过系统的优化之后会变得高效,因此用户所写语句的优劣至关重要。系统所做查询优化我们暂不讨论,下面重点说明改善用户查询计划的解决方案。  解决问题  下面以关系数据库系统Informix为例,介绍改善用户查询计划的方法。  1.合理使用索引  索引是数据库中重要的数据结构,它的根本目的就是为了提高查询效率。现在大多数的数据库产品都采用IBM最先提出的ISAM索引结构。索引的使用要恰到好处,其使用原则如下:  在经常进行连接,但是没有指定为外键的列上建立索引,而不经常连接的字段则由优化器自动生成索引。  在频繁进行排序或分组(即进行group by或order by操作)的列上建立索引。  在条件表达式中经常用到的不同值较多的列上建立检索,在不同值少的列上不要建立索引。比如在雇员表的“性别”列上只有“男”与“女”两个不同值,因此就无必要建立索引。如果建立索引不但不会提高查询效率,反而会严重降低更新速度。  如果待排序的列有多个,可以在这些列上建立复合索引(compound index)。  使用系统工具。如Informix数据库有一个tbcheck工具,可以在可疑的索引上进行检查。在一些数据库服务器上,索引可能失效或者因为频繁操作而使得读取效率降低,如果一个使用索引的查询不明不白地慢下来,可以试着用tbcheck工具检查索引的完整性,必要时进行修复。另外,当数据库表更新大量数据后,删除并重建索引可以提高查询速度。  2.避免或简化排序  应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素:  索引中不包括一个或几个待排序的列;  group by或order by子句中列的次序与索引的次序不一样;  排序的列来自不同的表。  为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图简化它,如缩小排序的列的范围等。  3.消除对大型表行数据的顺序存取  在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如,两个表:学生表(学号、姓名、年龄……)和选课表(学号、课程号、成绩)。如果两个表要做连接,就要在“学号”这个连接字段上建立索引。  还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的where子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作:  SELECT * FROM orders WHERE (customer_num=104ANDorder_num&1001) OR order_num=1008  虽然在customer_num和order_num上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:  SELECT * FROM orders WHERE customer_num=104 ANDorder_num&1001UNION SELECT * FROM orders WHEREorder_num=1008  这样就能利用索引路径处理查询。  4.避免相关子查询  一个列的标签同时在主查询和where子句中的查询中出现,那么很可能当主查询中的列值改变之后,子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行。  5.避免困难的正规表达式  MATCHES和LIKE关键字支持通配符匹配,技术上叫正规表达式。但这种匹配特别耗费时间。例如:  SELECT * FROM customer WHERE zipcode LIKE “98_ _ _”  即使在zipcode字段上建立了索引,在这种情况下也还是采用顺序扫描的方式。如果把语句改为SELECT *FROMcustomer WHERE zipcode&“98000”,在执行查询时就会利用索引来查询,显然会大大提高速度。  另外,还要避免非开始的子串。例如语句:SELECT * FROM customerWHEREzipcode[2,3]&“80”,在where子句中采用了非开始子串,因而这个语句也不会使用索引。  6.使用临时表加速查询  把表的一个子集进行排序并创建临时表,有时能加速查询。它有助于避免多重排序操作,而且在其他方面还能简化优化器的工作。例如:  SELECT cust.name,rcvbles.balance,……other columns FROMcust,rcvblesWHERE cust.customer_id = rcvlbes.customer_idANDrcvblls.balance&0 AND cust.postcode&“98000” ORDERBYcust.name  如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个临时文件中,并按客户的名字进行排序:  SELECT cust.name,rcvbles.balance,……other columns FROMcust,rcvblesWHERE cust.customer_id = rcvlbes.customer_idANDrcvblls.balance&0 ORDER BY cust.name INTOTEMPcust_with_balance  然后以下面的方式在临时表中查询:  SELECT * FROM cust_with_balance WHERE postcode&“98000”  临时表中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘I/O,所以查询工作量可以得到大幅减少。  注意:临时表创建后不会反映主表的修改。在主表中数据频繁修改的情况下,注意不要丢失数据。  7.用排序来取代非顺序存取  非顺序磁盘存取是最慢的操作,表现在磁盘存取臂的来回移动。SQL语句隐藏了这一情况,使得我们在写应用程序时很容易写出要求存取大量非顺序页的查询。  有些时候,用数据库的排序能力来替代非顺序的存取能改进查询。  编辑推荐  MySQL的数据类型和建库策略详解  MySQL索引分类和各自用途  MySQL性能优化实战
【来源:】
(责任编辑:和讯网站)
05/11 12:3005/11 11:0605/11 09:4905/10 18:5605/08 13:0305/07 14:1905/06 15:2405/05 14:05
感谢您的参与!查看[]
script src="/track/track_xfh.js?ver=">

我要回帖

更多关于 mysql 条件查询 的文章

 

随机推荐