在分析性能欠佳的查询时,应考虑:
1) 应用程序是否正获取超过需要的数据,即访问了过多的行或列。
2) Mysql服务器是否分析了超过需要的行。
如果发现访问的数据行数很大,而生成的结果中数据行很少,那么可以尝试修改,比如使用覆盖索引、更改架构或重写查询让优化器可以以优化的方式执行它。
优化最终集中在减少IO,降低CPU,提高查询速度。
一般应用中数据库通常是IO密集型的,大部分数据库操作中超过90%的时间是由IO操作所占用,所以减少IO访问次数是SQL优化中首要考虑的因素。除了IO外,需要再考虑优化CPU的运算量。通常,ORDER BY、GROUP BY、DISTINCT和一些比较运算都是主要消耗CPU的地方。降低CPU计算也就成为优化的重要目标。
1. 查询优化步骤
1)通过 show status和应用特点了解各种 SQL的执行频率
可以通过 SHOW STATUS 提供的服务器状态信息,或使用 mysqladmin extende d-status 命令获得。 SHOW STATUS 可以根据需要显示 session 级别的统计结果和 global级别的统计结果。
为了近似于实时知道服务器性能,可以周期性运行SHOW STATUS,并且和前一次的输出进行比较。如:
Mysqladmin extended –r –i 10
因为输出很多,可以把结果导入到grep中,过滤掉自己不想看的变量,也可以使用innotop或其他工具(如mysqlreport)来检查结果。一些值得监控的变量是:
Bytes_received和Bytes_send:和服务器之间来往的流量。
Com_* : 服务器正在执行的命令
Created_*: 在查询执行期间创建的临时表和文件
Handler_*: 存储引擎操作
Select_*: 不同类型的联接执行计划
Sort_*: 几种排序信息
同时用这种方法还能监视MySQL的内部操作,例如:键访问的次数、为MyISAM从磁盘上进行的键读取、数据访问率、为InnoDB从磁盘上读取的数据,等等。这有助于定位系统中实际的和潜在的瓶颈,而无需调查每一个查询。
以下几个参数是对 MyISAM 和 InnoDB 存储引擎的计数:
1. Com_select 执行 select 操作的次数,一次查询只累加 1 ;
2. Com_insert 执行 insert 操作的次数,对于批量插入的 insert 操作,只累加一次 ;
3. Com_update 执行 update 操作的次数;
4. Com_delete 执行 delete 操作的次数;
以下几个参数是针对 Innodb 存储引擎计数,累加的算法略有不同:
1. Innodb_rows_read select 查询返回的行数;
2. Innodb_rows_inserted 执行 Insert 操作插入的行数;
3. Innodb_rows_updated 执行 update 操作更新的行数;
4. Innodb_rows_deleted 执行 delete 操作删除的行数;
通过以上几个参数,可以了解到当前数据库的应用是以插入更新为主还 是以查询操作为主,以及各种类型的 SQL大致的执行比例。注意对更新操作的计数是针对执行次数的计数,不论提交还是回滚都会累加。
对于事务型的应用,通过 Com_commit 和 Com_rollback 可以了解事务提交和回 滚的情况,对于回滚操作非常频繁的数据库,可能意味着应用编写存在问题。此外,以下几个参数便于我们了解数据库的基本情况:
1. Connections 试图连接 Mysql 服务器的次数
2. Uptime 服务器工作时间
3. Slow_queries 慢查询的次数
为找到耗费了最多时间的工作,可以将测试分析建立在任何适合的粒度上:可以整体分析服务器,或者检查单个查询或批查询。得到信息包括:
* MySQL访问得最多的数据。
* MySQL执行得最多的查询的种类。
* MySQL停留时间最长的状态
* MySQL用来执行查询的使用得最频繁的子系统
* MySQL查询过程中访问的数据种类
* MySQL执行了多少种不同类型的活动,比如索引扫描
2)定位执行效率较低的SQL语句
一旦确定查询只获取了所需要的数据,那么接下来不应检查在生成查询结果时是否检查了过多数据,测量指标主要:执行时间、检查行数、返回行数。
可以通过以下两种方式定位执行效率较低的 SQL 语句:
1). 可以通过慢查询日志定位那些执行效率较低的 sql 语句
用 --log-slow-queries[=file_name] 选项启动时, mysqld 写一个包含所有执行时间超过long_query_time 秒的 SQL 语句的日志文件。
测量的三个指标(执行时间、检查行数、返回行数)都被写入了慢查询日志,故慢查询日志是检索查找过多数据查询的最佳方式。
注意:执行时间在不同负载下表现是不一样的;在理想情况下,返回的行和检查的行应该是一样的,但实际上是不可能的,例:使用联接查询时就需访问更多的行来产生一行输出。通常说来,检查的行和返回的行之间的比率通常较小,在1:1到10:1之间。
2). 使用 show processlist查看当前MySQL的线程
慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能有效定位问题,可以使用 show processlist 命令查看当前 MySQL 在进行的线程,包括线程的状态,是否锁表等等,它不仅可以显示哪种查询正在执行,也能看到连接的状态。一些因素,比如大量连接处于锁定状态,是瓶颈的明显线索。。
3)通过EXPLAIN 分析低效 SQL的执行计划:
通过以上步骤查询到效率低的 SQL 后,我们可以通过 explain 或者 desc 获取MySQL 如何执行 SELECT 语句的信息,包括 select 语句执行过程表如何连接和连接 的次序。
通过EXPLAIN来探知访问类型,访问在扫描表、索引、范围和常量时的速度是不一样的。如果没有得到好的访问类型,那么最好的解决办法是加一个索引。
通常说来,MySQL会在3种情况下使用where子句,从最好到最坏依次是:
* 对索引查找应用where子句来消除不匹配的行,这发生在存储引擎层;
* 使用覆盖索引来避免访问行(”Using Index’),并且从索引取得数据后过滤掉不匹配的行。这发生在服务器层
* 从表中检索出数据,然后过滤掉不匹配的行(‘Using Where’)。这发生在服务器端并且要求在过滤之前读取这些行。
2. MySQL索引
1) mysql如何使用索引
索引用于快速找出在某个列中有一特定值的行。对相关列使用索引是提高SELECT 操作性能的最佳途径。
查询要使用索引最主要的条件是查询条件中需要使用索引关键字,如果是多列索引,那么只有查询条件使用了多列关键字最左边的前缀时(前缀索引),才可以使用索引,否则将不能使用索引。
下列情况下, Mysql 不会使用已有的索引:
1、如果 mysql 估计使用索引比全表扫描更慢,则不使用索引。例如:如果 key_part 1均匀分布在 1 和 100 之间,下列查询中使用索引就不是很好:
SELECT * FROM table_name where key_part1 > 1 and key_part1 < 90
2、如果使用 heap 表并且 where 条件中不用=索引列,其他 > 、 < 、 >= 、 <= 均不使 用索引(MyISAM和innodb表使用索引);
3、查询条件里使用了函数(WHERE DAY(column) = …)或索引字段是表达式的一部分,则不会使用索引。
4、使用or分割的条件,如果or前的条件中的列有索引,后面的列中没有索引,那么涉及到的索引都不会使用。
5、如果创建复合索引,如果条件中使用的列不是索引列的第一部分;(不是前缀索引)
6、比较操作符LIKE和REGEXP的搜索模板的第一个字符是通配符,如 like 是以%开始时,不使用索引。
7、对 where 后边条件为字符串的一定要加引号,字符串如果为数字 mysql 会自动转为字符串,但是不使用索引。
2)查看索引使用情况
通过下面几个参数来了解索引使用情况:
Handler_read_key 请求数字基于键读行。
Handler_read_next 请求读入基于一个键的一行的次数。
如果索引正在工作, Handler_read_key 的值将很高,这个值代表了一个行被索引值读的次数,很低的值表明增加索引得到的性能改善不高,因为索引并不经常使 用。
Handler_read_rnd_next 的值高则意味着查询运行低效,并且应该建立索引补救。这个值的含义是在数据文件中读下一行的请求数。如果你正进行大量的表扫描,该值较高。通常说明表索引不正确或写入的查询没有利用索引。
3.查询语句分析
1)使用SHOW STATUS分析查询
我们可以通过FLUSH STATUS和SHOW SESSION STATUS相结合来分析查询或批处理查询。这是一种优化查询的好办法:
首先,运行FLUSH STATUS把会话状态变量设置为零,这样就可以知道MySQL执行查询时做了多少工作:mysql > FLUSH STATUS;
接下来运行查询。我们添上了SQL_NO_CACHE,这样MySQL不会从查询缓存中取得查询结果。
首先,运行FLUSH STATUS把会话状态变量设置为零,这样就可以知道MySQL执行查询时做了多少工作:mysql > FLUSH STATUS;
接下来运行查询。我们添上了SQL_NO_CACHE,这样MySQL不会从查询缓存中取得查询结果:
mysql> select sql_no_cache id, count(*) from tb inner join tb1 using(id) group by id order by count(*) desc;
…
4 rows in set (0.72 sec)
现在来分析这条语句的运行?先看看服务器选择的查询计划:
mysql> show session status like 'select%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| Select_full_join | 0 |
| Select_full_range_join | 0 |
| Select_range | 0 |
| Select_range_check | 0 |
| Select_scan | 2 |
+------------------------+-------+
5 rows in set (0.00 sec)
看上去MySQL进行了全表扫描。如果查询涉及了多张表,有几个变量的值就会大于零。例如,如果MySQL在后续表中进行了范围扫描,以寻找匹配行,select_full_range_join变会有值,甚至还可以查看查询执行的低层次存储引擎操作:
mysql> show session status like 'Handler%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Handler_commit | 1 |
| Handler_delete | 0 |
| Handler_discover | 0 |
| Handler_prepare | 0 |
| Handler_read_first | 1 | --- 请求读入表中第一行的次数。
| Handler_read_key | 12 | ---请求数字基于键读行。
| Handler_read_next | 0 | ---请求读入基于一个键的一行的次数
| Handler_read_prev | 0 |
| Handler_read_rnd | 4 | ---请求读入基于一个固定位置的一行的次数
| Handler_read_rnd_next | 14 |
| Handler_rollback | 0 |
| Handler_savepoint | 0 |
| Handler_savepoint_rollback | 0 |
| Handler_update | 0 |
| Handler_write | 7 |
+----------------------------+-------+
15 rows in set (0.02 sec)
“读(read)”操作的值很高,意味着MySQL需要扫描多个表才能满足查询需要。通常,如果MySQL只对一个表使用了全表扫描,我们就会看到Handler_read_rn_next的值较高,并且Handler_read_rnd是零。
在这个例子中,多个非零值意味着MySQL必须使用临时表来满足GROUP BY和ORDER BY子句,这是Handler_write和Handler_update不为零的原因:MySQL假定写入临时表,扫描它并进行排序,然后再次进行扫描,输出排序后的结果。
再来看看MySQL为排序做了些什么:
mysql> show session status like 'Sort%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Sort_merge_passes | 0 |
| Sort_range | 0 |
| Sort_rows | 4 |
| Sort_scan | 1 |
+-------------------+-------+
4 rows in set (0.01 sec)
正如我们猜测那样,MySQL通过扫描包含输出中所有行的临时表进行排序。如果值多于4行,我们怀疑它在查询执行的过程中在别的地方进行了排序。还能看到MySQL为查询创建了多少临时表:
mysql> show session status like 'created%';
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 0 |
| Created_tmp_files | 0 |
| Created_tmp_tables | 2 |
+-------------------------+-------+
3 rows in set (0.00 sec)
Create_tmp_disk_tables为0表示不需要使用磁盘上的临时表。
我们可以通过将命令运行两次,并将第2次结果减去第1次结果来计算开销,得到精确的结果。这样我们就可以知晓MySQL在执行查询的过程中做了多少工作。这将是我们进行优化的基础。
2)使用SHOW PROFILE分析查询
在默认情况下,分析是关闭的,但是可以在会话的层面打开。打开它会让服务器收集用于执行查询的资源信息。在开始收集统计信息之前,需要把分析变量设置为1:
Mysql>set profiling = 1;
现在运行查询:
mysql> select sql_no_cache id, count(*) from tb inner join tb1 using(id) group by id order by count(*) desc;
+----+----------+
| id | count(*) |
+----+----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
+----+----------+
4 rows in set (0.00 sec)
查询的分析数据被保存在会话中。使用SHOW PROFILES查看已经被分析过后查询:
mysql> show profilesG;
*************************** 1. row ***************************
Query_ID: 1
Duration: 0.00242825
Query: select sql_no_cache id, count(*) from tb inner join tb1 using(id) group by id order by count(*) desc
1 row in set (0.00 sec)
ERROR:
No query specified
可以使用SHOW PROFILE命令取得被保存下来的分析数据。如果不加任何参数,它就会显示最近一个命令的状态值和运行时间:
mysql> show profile;
+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| starting | 0.001015 |
| Opening tables | 0.000042 |
| System lock | 0.000013 |
| Table lock | 0.000024 |
| init | 0.000097 |
| optimizing | 0.000027 |
| statistics | 0.000092 |
| preparing | 0.000036 |
| Creating tmp table | 0.000261 |
| executing | 0.000010 |
| Copying to tmp table | 0.000288 |
| Sorting result | 0.000093 |
| Sending data | 0.000048 |
| end | 0.000006 |
| removing tmp table | 0.000036 |
| end | 0.000008 |
| query end | 0.000010 |
| freeing items | 0.000202 |
| logging slow query | 0.000011 |
| logging slow query | 0.000098 |
| cleaning up | 0.000013 |
+----------------------+----------+
21 rows in set (0.00 sec)
每一行代表了进程的一种变化,以及在这种状态停留的时间。Status列和SHOW FULL PROCESSLIST输出的State列是对应的。它的值来自于thd->proc_info变量,因此可以直接看到MySQL内部的值。尽管它们的名字都很直观,也不难理解,但是这些变量还是可以从MySQL的手册中找到。
可以从SHOW PROFILES的输出中得到特定的Query_ID,并且进行指定的分析,并且还可以定义输出的其余列。例如,为了解执行查询时用户的CPU使用率,可以使用下面的命令:
Mysql> SHOW PROFILE CPU FOR QUERY 1;
SHOW PROFILE很好地揭示了服务器执行查询时所做的事情,并且有助于理解查询在操作上花费时间。它的局限是未实现的特性,不能查看和分析其他联接的查询,以及由于分析带来的开销。
4.MySQL查询优化器的限制
1)关联子查询(Correlated Subqueries):MySQL有时把子查询优化得很差,最差的就是在Where子句中使用IN。优化语句需要考虑到执行顺序和缓存:从里向外执行查询是一种优化方式 ,缓存内部查询的结果是另外一种方式。重写查询时应考虑兼顾这两方面。
但MySQL不会总是把关联子查询优化得很差,比如Exists在逻辑上表达“有一个匹配”概念,它不会产生任何重复的行,也能够避免使用GROUP BY和DISTINCT操作,有时子查询会比联接快得多。对待子查询不能有绝对的态度,应该用测试来证明。
2)联合的限制:MySQL有时不能把UNION外的一些条件“下推”到UNION的内部,而这些外部条件本来用于限制结果或者产生优化。具体应该把LIMIT子句或ORDER BY子句添加到UNION内部的每一个子句上。
3)索引合并优化:索引合并算法可以在查询中对一个表使用多个索引。查询可以同时扫描多个索引,并且合并结果,这种算法有3种变体,分别是:对OR取并集、对AND取交集、对AND和OR的组合并集。但有时这种算法的缓冲、排序和合并操作使用了大量的CPU和内存资源,对于没有足够区分性的索引,并行扫描会返回大量需要合并操作的列,这种情况就更容易发生。
如果查询因为优化器的限制而运行得很慢,那么可以通过IGNORE INDEX命令禁止一些索引,或使用老的UNION策略。
4)相等传递:优化器通过把相应的列拷贝到相关的表里来共享列表。这通常是有用的,因为它让优化器和执行引擎有更多的机会选择执行IN操作的时机。但是如果这个列非常大,它就可能导致较慢的优化和执行。
5)并行执行:MySQL不能在多个CPU上并行执行一个查询。
6)哈希联接(Hash Join):MySQL还不能真正执行哈希联接。
7)松散索引扫描(Loose Index Scan):MySQL不支持松散索引扫描,即扫描不连续的索引。MySQL索引扫描通常都需要一个确定的起点和终点,即使查询只需要其中一些不连续的行,MySQL也会扫描起点到终点范围内的所有行。
例:假设某表在列(a,b)上有索引,要执行查询:select … from tb1 where b between 2 and 3;因为索引是从a开始,但是Where子句中没有列a,MySQL将会全表扫描并且去掉不匹配的行。Explain中”Using Index for group-by”表示查询使用了松散索引扫描。
8)Min()和Max():MySQL不能很好地优化MIN()和MAX()。
9)对同一个表进行SELECT和UPDATE:MySQL不会让你在对一个表进行UPDATE的同时运行SELECT。一个变通的方式是衍生表(临时表),这样可以有效地处理两个查询:一是在子查询内部使用SELECT,二是使用表和子查询的结果进行联接,然后进行更新:
mysql> UPDATE tb1 INNER JOIN(
SELECT type, count(*) AS cnt FROM tb1 GROUP BY type) AS der USING(type)
SET tb1.cnt = der.cnt;
MySQL能够处理的一些优化类型是:
》对联接中的表重新排序
》对外联接转换成内联接
》代数等价法则:如5=5 AND a > 5等价于a>5
》优化COUNT()、MIN()和MAX()
》计算和减少常量表达式
》覆盖索引
》子查询优化
》尽早停止:一旦满足查询或某个步骤的条件,MySQL就会立即停止处理该查询或该步骤
》相等传递:
》比较IN()里面的数据:MySQL会对IN()里面的数据进行排序,然后用二分法查找某个值是否在列表中。这个算法效率是O(logn),而等同的OR 子句的效率是O(N),在列表很大的时候,OR子句会慢得多。
5. 具体优化查询语句
1. 应尽量避免全表扫描
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。相对于使用给定的索引,全表扫描将非常耗时。可以尝试下面的技巧以避免优化器错选了表扫描:
* 使用ANALYZE TABLEtbl_name为扫描的表更新关键字分布。
* 对扫描的表使用FORCE INDEX。
SELECT * FROM t1, t2 FORCE INDEX (index_for_column) WHERE t1.col_name=t2.col_name;
* 用--max-seeks-for-key=1000选项启动MySQL或者使用SET max_seeks_for_key=1000告知优化器扫描不会超过1,000次关键字搜索。
虽然对表建立了索引,但表查询仍然可能会使用全表索引。
1) 应尽量避免在 where 子句中对字段进行 null 值判断
任何在where子句中使用is null或is not null的语句优化器都是不允许使用索引,相反会进行全表扫描,如:
select fa from tb where fb is null
NULL对于大多数数据库都需要特殊处理,MySQL也不例外,它需要更多的代码,更多的检查和特殊的索引逻辑,只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。
2) 应尽量避免在 where 子句中使用!=或<>操作符
通常MySQL只对<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE才会使用索引。如果查询条件中存在!=或<>符号时,MySQL将无法使用索引而进行全表扫描。
对于LIKE语句,只有在不是以通配符(%或者_)开头的查询下才会使用索引。例如:
SELECT fa FROM tb WHERE fb LIKE 'Mich%'; # 这个查询将使用索引,
SELECT fa FROM tb WHERE fb LIKE '%ike'; #这个查询不会使用索引。
对于TEXT类型,若要提高效率,可考虑全文检索。
3) 应尽量避免在 where 子句中使用 or 来连接条件
WHERE语句中使用OR,且没有使用覆盖索引,会进行全表扫描。如:
select id from t where num=10 or num=20
可以 使用UNION合并查询:
select id from t where num=10 union all select id from t where num=20
在某些情况下,or条件可以避免全表扫描的:必须所有的or条件都是独立索引。
4) 慎用in 和 not in,否则会导致全表扫描
MySQL索引能很好地匹配匹配范围值,但这也只会使用索引第一列,同时也不能跳过索引中的列。对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
使用BETWEEN语句时也需注意:MySQL不支持松散索引扫描,即扫描不连续的索引。MySQL索引扫描通常都需要一个确定的起点和终点,即使查询只需要其中一些不连续的行,MySQL也会扫描起点到终点范围内的所有行。
例:假设某表在列(a,b)上有索引,要执行查询:select … from tb1 where b between 2 and 3;因为索引是从a开始,但是Where子句中没有列a,MySQL将会全表扫描并且去掉不匹配的行
5) 如果在 where 子句中使用参数,也会导致全表扫描。
因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引: select id from t with(index(索引名)) where num=@num
6) 应尽量避免在 where 子句中对字段进行表达式操作。
这将导致MySQL放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
该查询不会使用索引。MySQL不会帮你求解方程,应该养成简化WHERE子句的习惯,这样就会把被索引的列单独放在比较运算符的一边,不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算。上句可改为:
select id from t where num=100*2 或者selece id from t where num=200;
7) 应尽量避免在where子句中对字段进行函数操作,
这同样会导致MySQL放弃使用索引而进行全表扫描。如:
SELECT … WHERE TO_DAYS(CURRENT_DATE) – TO_DAYS(date_col) <= 10;
这个查询将会查找date_col值离今天不超过10天的所有行,但是它不会使用索引,因为使用了TO_DAYS()函数。下面是一种较好的方式:
SELECT … WHERE date_col >= DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY) ;
使用CURRENT_DATE将会阻止查询缓存把结果缓存起来,可以用常量替换CURRENT_DATE的值对上条语句进行改进:
SELECT … WHERE date_col >=DATE_SUB(‘2008-01-17’, INTERVAL 10 DAY);
8) 索引字段不是复合索引的前缀索引
通常MySQL索引对于以下类型的查询有用(以下针对B-Tree树索引):
* 匹配全名: 全键值匹配指和索引中的所有列匹配。
* 匹配最左前缀:这仅仅适用了索引中的第一列。
* 匹配列前缀:可以匹配某列的值的开头部分
如果查找没有从索引列的最左边开始,它就没有什么用处。如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。例如在表tb上存在索引(fa, fb, fc),这种索引不能帮助查询存在fb查询条件同时却没有定义fa条件的数据,即这种查询不能使用上索引(fa, fb, fc)。
同样查询条件不能跳过索引中的列。同样以上面表tb的索引(fa, fb, fc),索引也不能帮助查询指定fa、fc条件同时缺没有定义fb查询条件的数据。
另外,存储引擎也不能优化访问任何在第一个范围条件右边的列。例如表tb上的索引(fa,fb,fc),如果查询形如WHERE fa=’….’ AND fb LIKE ‘x%’ AND fc=’….’,那么访问就只能使用索引的头两列(fa, fb),因为LIKE是范围条件。
这些局限都和列顺序有关,所以列顺序极端重要了。对于高性能应用程序,也许要针对相同列以不同顺序创建多个索引,以满足程序要求。
这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合B-Tree索引)。而(B,A)上的索引就不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。
2. 其他一些注意优化
1) 不要写一些没有意义的查询,
如生成一个空表结构:
select col1, col2 into #t from t where 1=0
上面这条语句不会返回任何结果集,但是会消耗系统资源的,应改用: create table #t(...)
2) 很多时候用 exists 代替 in 是一个好的选择:
EXISTS用于检查子查询是否至少会返回一行数据,用于检测行的存在,该子查询实际上并不返回任何数据,而是返回值True或False。EXISTS在逻辑上表达“有一个匹配”概念,它不会产生任何重复的行,也能够避免使用GROUP BY和DISTINCT操作:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
关于EXISTS与IN查询的效率,可以网上找‘mysql in和exists性能比较和使用’查看。具体以测试结果为准。
3) 并不是所有索引对查询都有效,
SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。通常认为在这样字段上不适合于建立索引。
4) 索引并不是越多越好
索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
在任何可能的地方,首先应试着扩展索引,而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。
5) 应尽可能的避免更新 clustered 索引数据列
因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
6) 尽量使用数字型字段
若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
特别是在作关键字段的类型选择上,要同时考虑存储类型和MySQL如何对它们进行计算和比较。
7) 尽可能的使用 varchar/nvarchar 代替 char/nchar
因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
8) 最好不要使用"*"返回所有: select * from t
若要利用覆盖索引,则应用具体的字段列表代替“*”,不要返回用不到的任何字段。
3. 临时表的问题
临时表一般都很少用,一般是程序中动态创建或者由MySQL内部根据SQL执行计划需要时创建。
内存表则大多数是当Cache用,随着memcache、NoSQL的流行,内存表也越来越少使用了。
1) 避免频繁创建和删除临时表,以减少系统表资源的消耗。
即便在删除临时表数据时,建议使用TRUNCATE TABLE 替代DELETE操作。
2) 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
3) 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
使用EXPLAIN分析查询语句时,extra列显示“using temporary”即使用了内部临时表。内部临时表的创建条件:
* group by 和 order by中的列不相同
* order by的列不是引用from 表列表中 的第一表
* group by的列不是引用from 表列表中 的第一表
* 使用了sql_small_result选项
* 含有distinct 的 order by语句
4. 游标的问题:
1) 尽量避免使用游标,
因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。 Mysql的游标不适合处理大一点的数据量,仅适合用于操作几百上千的小数据量。
2) 使用基于游标的方法或临时表方法之前,应先寻找基于集合的解决方案来解决问题,基于集合的方法通常更有效。
对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
5. 事务的问题:
1) 尽量避免大事务操作,提高系统并发能力。
innodb在事务下可以锁定行也可以锁定表(MyISAM仅支持表锁),当然对于应用而言,锁定行是最佳性能,当锁定了表,其他进程对表进行write操作时只能队列等候事务的完成再继续。
6. 数据量的问题
1) 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
通常避免向客户端返回大数据量的方法是采用分页系统,在分页系统中使用LIMIT和OFFSET很常见,它们通常也会和ORDER BY一起使用。但分页系统中(LIMIT和OFFSET)的一个常见问题是偏移量很大时,可以限制一个分页里访问的页面数目,或者让偏移量很大时查询效率更高。一个提高效率的简单技巧就是在覆盖索引上进行偏移,而不是对全行数据进行偏移。可以将从覆盖索引上提取出来的数据和全行数据进行联接,然后取得需要的列。有时可以把LIMIT转换为位置性查询(bwteen and子句),服务器可以以索引范围扫描的方式来执行。
7. COUNT优化:
COUNT有两种不同的工作方式:统计值的数量和统计行的数量。如果在COUNT()的括号中定义了列名或其它表达式,COUNT就会统计这个表达式有值班的次数。COUNT(*)则统计结果中行的数量。
通常说来,使用了COUNT的查询很难优化,因为它们通常需要统计很多行。在MySQL内部优化它的唯一其他选择就是使用覆盖索引。如果这还不够,那么就需要更改应用程序的架构。可以考虑使用汇总表,还可以利用外部缓存系统,比如数据库缓存服务器(MemCached)。
1) InnoDB引擎在统计方面和MyISAM是不同的,MyISAM内置了一个计数器,COUNT(*)在没有查询条件的情况下使用 select count(*) from table 的时候,MyISAM直接可以从计数器中取出数据,而InnoDB必须全表扫描一次方能得到总的数量。
2) 但是当有查询条件的时候,两者的查询效率一致。
对主键索引作COUNT的时候之所以慢,是因为:
* InnoDB引擎:
[1] 数据文件和索引文件存储在一个文件中,主键索引默认直接指向数据存储位置。
[2] 二级索引存储指定字段的索引,实际的指向位置是主键索引。当我们通过二级索引统计数据的时候,无需扫描数据文件;而通过主键索引统计数据时,由于主键索引与数据文件存放在一起,所以每次都会扫描数据文件,所以主键索引统计没有二级索引效率高。
[3] 由于主键索引直接指向实际数据,所以当我们通过主键id查询数据时要比通过二级索引查询数据要快。
* MyISAM引擎
[1] 该引擎把每个表都分为几部分存储,比如用户表,包含user.frm,user.MYD和user.MYI。
[2] User.frm负责存储表结构
[3] User.MYD负责存储实际的数据记录,所有的用户记录都存储在这个文件中
[4] User.MYI负责存储用户表的所有索引,这里也包括主键索引。
8.优化联接:
对于JOIN查询,
• 首先确保ON或USING使用的列上有索引。这样,MySQL内部会启动为你优化Join的SQL语句的机制。
• 确保GROUP BY或ORDER BY只引用一个表中的列。这样MySQL可以尝试对这些操作使用索引。
• 确保被用来Join的字段,应该是相同的类型的。例如:如果你要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样)
• 要谨慎升级MySQL。
9.优化子查询:
对子查询重要的建议就是尽可能使用联接。
10. 优化order by语句
基于索引的排序
MySQL的弱点之一是它的排序。虽然MySQL可以在1秒中查询大约15,000条记录,但由于MySQL在查询时最多只能使用一个索引。因此,如果WHERE条件已经占用了索引,那么在排序中就不使用索引了,这将大大降低查询的速度。我们可以看看如下的SQL语句:
SELECT * FROM SALES WHERE NAME = “name” ORDER BY SALE_DATE DESC;
在以上的SQL的WHERE子句中已经使用了NAME字段上的索引,因此,在对SALE_DATE进行排序时将不再使用索引。为了解决这个问题,我们可以对SALES表建立复合索引:
ALTER TABLE SALES DROP INDEX NAME, ADD INDEX (NAME, SALE_DATE)
这样再使用上述的SELECT语句进行查询时速度就会大副提升。但要注意,在使用这个方法时,要确保WHERE子句中没有排序字段,在上例中就是不能用SALE_DATE进行查询,否则虽然排序快了,但是SALE_DATE字段上没有单独的索引,因此查询又会慢下来。
在某些情况中, MySQL可以使用一个索引来满足 ORDER BY子句,而不需要额外的排序。 where条件和order by使用相同的索引,并且order by 的顺序和索引顺序相 同,并且order by的字段都是升序或者都是降序。例如:下列sql可以使用索引。
SELECT * FROM t1 ORDER BY key_part1,key_part2,... ;
SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2 DESC;
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;
但是以下情况不使用索引:
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC ; --order by 的字段混合 ASC 和 DESC
SELECT * FROM t1 WHERE key2=constant ORDER BY key1 ;-- 用于查询行的关键字与 ORDER BY 中所使用的不相同
SELECT * FROM t1 ORDER BY key1, key2 ;-- 对不同的关键字使用 ORDER BY :
11. 优化GROUP BY和DISTINCT
通常说来,索引也是优化它们的最重要手段。
当不能使用索引时,MySQL有两种优化GROUP BY的策略:使用临时表或文件排序进行分组。通过使用SQL_SMALL_RESULT强制MySQL选择临时表,或者使用SQL_BIG_RESULT强制它使用文件排序。
如果要对联接进行分组,通过对表的ID列进行分组会更加高效。通过配置SQL_MODE参数来禁止SELECT中使用未在GROUP BY中出现的列。
分组查询的一个变化就是要求MySQL在结果内部实现超级聚合(Super Aggregation)。可以在GROUP BY后面加上WITH ROLLUP来实现。但也许它没有被很好地优化。可以使用解释器检查执行方法,确认分组是否已经通过文件排序或临时表完成,然后试着移除WITH ROLLUP,并且查看分组方法是否没有变化。
默认情况下, MySQL 排序所有 GROUP BY col1 , col2 , .... 。查询的方法如同在查询中指定 ORDER BY col1 , col2 , ... 。如果显式包括一个包含相同的列的 ORDER BY子句, MySQL 可以毫不减速地对它进行优化,尽管仍然进行排序。如果查询包括 GROUP BY 但你想要避免排序结果的消耗,你可以指定 ORDER BY NULL禁止排序。
12.优化LIMIT和OFFSET:
在分页系统中使用LIMIT和OFFSET很常见,它们通常也会和ORDER BY一起使用。
一个常见问题是偏移量很大时,可以限制一个分页里访问的页面数目,或者让偏移量很大时查询效率更高。一个提高效率的简单技巧就是在覆盖索引上进行偏移,而不是对全行数据进行偏移。可以将从覆盖索引上提取出来的数据和全行数据进行联接,然后取得需要的列。有时可以把LIMIT转换为位置性查询(bwteen and子句),服务器可以以索引范围扫描的方式来执行。
如果确实需要优化分页系统,也许应该利用预先计算好的汇总数据。
13.优化联合:
MySQL总是通过创建并填充临时表方式来执行UNION,它不能对UNION进行太多的优化。重要的是始终要使用UNION ALL,除非需要服务器消除重复的行。如果忽略了ALL关键字,MySQL就会向临时表添加distinct选项,它会利用所有行来决定数据的唯一性,这种操作开销很大。
10. 优化 OR
通常情况下,在OR子句中的字段都具有索引的情况下,用UNION子句来替换WHERE子句中的OR将会起到较好的效果。但如果存在没有索引的列,则不应使用UNION子句。对索引列使用OR将造成全表扫描。
另外,可以使用IN子句来替代OR子句。通过给索引添加越来越多的列,并且使用IN()列表来覆盖那些不存WHERE子句的列。
对于OR子句,通常MyISAM存储索引可以使用索引来扫描,而InnoDB则不能。为了利用索引,OR子句中的所有条件中的字段都具有单独的索引,否则利用不上索引。
6.查询优化提示
如果不满意MySQL优化器选择的优化方案,可以使用一些优化提示来控制优化器的行为。
• HIGH_PRIORITY和LOW_PRIORITY:决定访问同一个表的语句相对于其他语句的优先级。HIGH_PRIORITY告诉MySQL将一个SELECT语句放在其他语句的前面,以便它修改数据。LOW_PRIORITY则相反,如果有其他语句需要访问数据,它就将当前语句放到队列的最后。可以将这个选项用于SELECT、INSERT、UPDATE、REPLACE和DELETE。这两个选项只影响服务器对访问表的队列的处理,并不是指在查询上分配较多或较少的资源。
• DELAYED:用于INSERT和UPDATE。应用这个提示的语句会立即返回并将待插入的列放入缓冲区中,在表空闲的时候再执行插入。
• STRAIGHT_JOIN:用于SELECT语句中SELECT关键字后,也可用于联接语句。它可强制MySQL按照查询中表出现的顺序来联接表,并当它出现在两个联接表中间时,强制这两个表按照顺序联接。
• SQL_SMALL_RESULT和SQL_BIG_RESULT:用于SELECT语句。告诉MySQL在GROUP BY或DISTINCT查询中如何并何时使用临时表。SQL_SMALL_RESULT告诉优化器结果集会比较小,可以放在索引过的临时表中,以避免对分组后的数据排序。SQL_BIG_RESULT是结果集很大,最好使用磁盘上的临时表进行排序。
• SQL_BUFFER_RESULT:将结果放在临时表中,并且尽快释放掉表锁。
• SQL_CACHE和SQL_NO_CACHE:SQL_CACHE表明查询已经存在缓存中,而SQL_NO_CACHE正好相反。
• SQL_CALC_FOUND_ROWS:告诉MySQL在有LIMIT子句时计算完整的结果集。(见上述了解为什么不要使用这个提示)
• FOR UPDATE和LOCK IN SHARE MODE:SELECT语句使用这两个提示来控制锁定,但只针对有行级锁的存会引擎。它可帮助预先锁定匹配的行。INSERT..SELECT查询不需要这两个提示。
• USE INDEX、IGNORE INDEX和FORCE INDEX:告诉优化器从表中寻找行时使用或忽略索引。
• Optimizer_search_depth:这个变量告诉优化器检查执行计划的深度。如果查询在“统计”的状态停留很长时间,就可以考虑减少这个变量的值。
• Optimizer_prune_level:让优化器根据检查的行的数量跳过某些查询计划
7. SQL核心语句(非常实用的几个技巧)
1.优化insert语句
如果可以同时从同一客户插入很多行,使用多个值表的INSERT 语句。多个值表的 INSERT 语句 ,可以大大缩减客户端与数据库之间的连接、语法分析等消耗,使得效率比分开执行的单个 INSERT 语句快很多。
如批量插入:
INSERT INTO tb (fa, fb, fc) VALUES ('1', '12', '13'), ('2', '22', '23'), ('3', '32', '33'),
如果从不同客户插入很多行,可以通过使用INSERT DELAYED 语句得到更高的速度。Delayed 的含义是让insert 语句马上执行,其实数据都被放在内存的队列中,并没有真正的写入磁盘;这比每条语句都分别插入要快的多;LOW_PRIORITY刚好相反,在所有其他用户对表的读写完成后才进行插入。
将索引文件和数据文件分在不同的磁盘上存放(利用建表中的选项);
如果进行批量插入,可以增加bulk_insert_buffer_size 变量值的方法来提高速度,但是,这只能对myisam表使用
当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这通常比使用很多INSERT语句快20倍;
根据应用情况使用replace 语句代替insert;
根据应用情况使用ignore 关键字忽略重复记录。
2.大批量插入数据
1. 对于Myisam 类型的表,可以通过以下方式快速的导入大量的数据。
ALTER TABLE tblname DISABLE KEYS;
loading the data
ALTER TABLE tblname ENABLE KEYS;
这两个命令用来打开或者关闭MyISAM 表非唯一索引的更新。在导入大量的数据到一个非空的Myisam 表时,通过设置这两个命令,可以提高导入的效率。对于导入大量数据到一个空的Myisam 表,默认就是先导入数据然后才创建索引的,所以不用进行设置。
2. 而对于Innodb 类型的表,这种方式并不能提高导入数据的效率。对于Innodb 类型的表,我们有以下几种方式可以提高导入的效率:
a. 因为Innodb 类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。如果Innodb 表没有主键,那么系统会默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这个优势提高导入数据的效率。
b. 在导入数据前执行SET UNIQUE_CHECKS=0,关闭唯一性校验,在导入结束后执行SETUNIQUE_CHECKS=1,恢复唯一性校验,可以提高导入的效率。
c. 如果应用使用自动提交的方式,建议在导入前执行SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。
3.清空数据表
TRUNCATE TABLE `mytable`
注意:删除表中的所有记录,应使用TRUNCATE TABLE语句。
TRUNCATE TABLE 与没有 WHERE 子句的 DELETE 语句类似;但是,TRUNCATE TABLE 速度更快,使用的系统资源和事务日志资源更少。这也意味着TRUNCATE TABLE要比DELETE快得多。
DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一个项。TRUNCATE TABLE 通过释放用于存储表数据的数据页来删除数据,并且在事务日志中只记录页释放。
当使用行锁执行 DELETE 语句时,将锁定表中各行以便删除。TRUNCATE TABLE 始终锁定表和页,而不是锁定各行。
执行 DELETE 语句后,表仍会包含空页。例如,必须至少使用一个排他 (LCK_M_X) 表锁,才能释放堆中的空表。如果执行删除操作时没有使用表锁,表(堆)中将包含许多空页。对于索引,删除操作会留下一些空页,尽管这些页会通过后台清除进程迅速释放。
TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。若要删除表定义及其数据,就需使用 DROP TABLE 语句。
需要注意的是,在下列情况下,不能对以下表使用 TRUNCATE TABLE:
* 由 FOREIGN KEY 约束引用的表。
* 参与索引视图的表。
* 通过使用事务复制或合并复制发布的表。
* 对于具有以上一个或多个特征的表,请使用 DELETE 语句。
* TRUNCATE TABLE 不能激活触发器,因为该操作不记录各个行删除。
4.拆分大的 DELETE 或 INSERT 语句
如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。
Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。
如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,可能不仅仅会让你泊WEB服务Crash,还可能会让你的整台服务器马上掛了。
所以,如果你有一个大的处理,你定你一定把其拆分,使用 LIMIT 条件是一个好的方法。下面是一个示例:
while (1) {
//每次只做1000条
mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
if (mysql_affected_rows() == 0) {
// 没得可删了,退出!
break;
}
// 每次都要休息一会儿
usleep(50000);
}
5.用SELECT创建记录和表
INSERT语句与DELETE语句和UPDATE语句有一点不同,它一次只操作一个记录。然而,有一个方法可以使INSERT 语句一次添加多个记录。要作到这一点,你需要把INSERT语句与SELECT语句结合起来,象这样:
INSERT mytable(first_column,second_column)
SELECT another_first,another_second FROM anothertable WHERE another_first='Copy Me!';
这个语句从anothertable拷贝记录到mytable.只有表anothertable中字段another_first的值为'Copy Me!'的记录才被拷贝。
当为一个表中的记录建立备份时,这种形式的INSERT语句是非常有用的。在删除一个表中的记录之前,你可以先用这种方法把它们拷贝到另一个表中。
如果你需要拷贝整个表,你可以使用SELECT INTO语句。例如,下面的语句创建了一个名为newtable的新表,该表包含表mytable的所有数据:
SELECT * INTO newtable FROM mytable;
你也可以指定只有特定的字段被用来创建这个新表。要做到这一点,只需在字段列表中指定你想要拷贝的字段。另外,你可以使用WHERE子句来限制拷贝到新表中的记录。下面的例子只拷贝字段second_columnd的值等于'Copy Me!'的记录的first_column字段。
SELECT first_column INTO newtable FROM mytable
WHERE second_column='Copy Me!';
使用SQL修改已经建立的表是很困难的。例如,如果你向一个表中添加了一个字段,没有容易的办法来去除它。另外,如果你不小心把一个字段的数据类型给错了,你将没有办法改变它。但是,使用本节中讲述的SQL语句,你可以绕过这两个问题。
例如,假设你想从一个表中删除一个字段。使用SELECT INTO语句,你可以创建该表的一个拷贝,但不包含要删除的字段。这使你既删除了该字段,又保留了不想删除的数据。
如果你想改变一个字段的数据类型,你可以创建一个包含正确数据类型字段的新表。创建好该表后,你就可以结合使用UPDATE语句和SELECT语句,把原来表中的所有数据拷贝到新表中。通过这种方法,你既可以修改表的结构,又能保存原有的数据。
8.重构查询的方式
一个重要的查询设计问题就是是否可以把一个复杂查询分解成多个简单的查询。注意的是,在应用程序中如果可以使用简单查询返回结果时仍然需要避免使用太多的查询。(从网络开销与服务器开销作平衡)。
1) 缩短查询(分治法):让查询每次执行一小部分,以减少受影响的行数。
例:将一次性大量数据的删除细化成多次中等大小的数据删除,得到移除相同数据的目的。对一个高效的查询来说,一次删除10000行数据的任务已经足够大了。
2)分解联接:把一个多表联接分解成多个单表查询,然后在应用程序端实现联接操作。
这种重构方式的优势:
* 缓存的效率更高:许多程序都直接缓存了表,这样可直接利用上缓存数据。
* 对MyISAM表来说,每个表一个查询可以有效地利用表锁。因为查询会在短时间内锁住单个表,而不是把所有表长时间锁住。
* 查询本身会更高效。
* 可以减少多余的行访问。联接会反复访问同一行数据。
* 在某种意义上可以认为这种方式是手工执行了哈希联接,而不是MySQL内部执行联接操作时采用的嵌套循环算法。
注意:在什么时候应用程序端进行联接效率会更高:
* 可以缓存早期查询的大量数据。
* 使用了多个MyISAM表。
* 数据分布在不同的服务器上。
* 对于大表使用IN()替换联接。
* 一个联接引用了同一个表很多次。
3)为查询缓存优化你的查询
大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。
这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓存。请看下面的示例:
// 查询缓存不开启
$r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
// 开启查询缓存
$today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
MySQL还允许改变语句调度的优先级,它可以使来自多个客户端的查询更好地协作,这样单个客户端就不会由于锁定而等待很长时间。改变优先级还可以确保特定类型的查询被处理得更快。