一、insert优化:
要操作的表为test,字段test。这里优化的方式是先组合一条多行的sql语句,然后再进行插入操作,这样就很大程度上优化了插入数据的效率。
测试代码如下:
$sql = "insert into `test` (`test`) values
('$content')";
for ($i=1;$i<999;$i++) {
$sql .= ",('$content')";
}
mysql_query($sql);
执行时间为:
0.0323481559753
0.0371758937836
0.0419669151306
更多的说明:
起因:有一个innoDB引擎的表Table,在一个大概3000次的foreach循环中执行
INSERT INTO Table(columnA, columnB) VALUES (valueA,
valueB)
结果居然超出了60S的php执行限制(当然这个限制可以在php.ini中修改),让我很不解为何插入效率如此低下。
经过查找资料以及摸索,得到以下优化方法:
1、innoDB是mysql引擎中唯一支持事务transaction的引擎。默认所有用户行为都在事务内发生。
默认mysql建立新连接时,innoDB采用自动提交autocommit模式,每个SQL语句在它自己上形成一个单独的事务,即insert一次就commit了一次,InnoDB在该事务提交时必须刷新日志到磁盘,因此效率受限于磁盘读写效率。
你可以通过
mysql_query("SET AUTOCOMMIT = 0");
来关闭自动提交模式。
如 果自动提交模式被关闭,那么我们可以认为一个用户总是有一个事务打开着。一个SQL
COMMIT或ROLLBACK语句结束当前事务并且一个新事务开始。两个语句都释放所有在当前事务中被设置的InnoDB锁定。一个COMMIT语句意
味着在当前事务中做的改变被生成为永久的,并且变成其它用户可见的。一个ROLLBACK语句,在另一方面,撤销所有当前事务做的修改。
当然如果是自动提交模式,通过用明确的START
TRANSACTION或BEGIN语句来开始一个事务,并用COMMIT或者ROLLBACK语句来结束它,这样用户仍旧可以执行一个多重语句事务。
2、因此对于本例,在建立数据库连接后,立即关闭自动提交,在foreach循环结束后,一次commit即可,效率大大提升。
mysql_query("SET AUTOCOMMIT = 0");
foreach(***)
INSERT INTO Table(columnA, columnB) VALUES
(valueA, valueB)
mysql_query("commit");
3、对于多次insert行到同一表的需求,你还可以采用多行插入语法来减少客户端和服务器之间的通讯开支。
即
INSERT INTO Table(columnA, columnB) VALUES (1,2), (5,5),
(3,3), ...
4、如果你的表有索引,索引会拖慢insert速度。大量插入数据时,可以先关闭索引,然后再重建索引。
ALTER TABLE Table DISABLE KEYS;
INSERT INTO ***;
ALTER TABLE Table ENABLE KEYS;
二、update优化:
三、select优化:
使用索引优化查询:
2.索引作用
在索引列上,除了上面提到的有序查找之外,数据库利用各种各样的快速定位技术,能够大大提高查询效率。特别是当数据量非常大,查询涉及多个表时,使用索引往往能使查询速度加快成千上万倍。
例如,有3个未索引的表t1、t2、t3,分别只包含列c1、c2、c3,每个表分别含有1000行数据组成,指为1~1000的数值,查找对应值相等行的查询如下所示。
SELECT c1,c2,c3 FROM t1,t2,t3 WHERE c1=c2 AND
c1=c3
此查询结果应该为1000行,每行包含3个相等的值。在无索引的情况下处理此查询,必须寻找3个表所有的组合,以便得出与WHERE子句相配的那些行。而可能的组合数目为1000×1000×1000(十亿),显然查询将会非常慢。
如果对每个表进行索引,就能极大地加速查询进程。利用索引的查询处理如下。
(1)从表t1中选择第一行,查看此行所包含的数据。
(2)使用表t2上的索引,直接定位t2中与t1的值匹配的行。类似,利用表t3上的索引,直接定位t3中与来自t1的值匹配的行。
(3)扫描表t1的下一行并重复前面的过程,直到遍历t1中所有的行。
在此情形下,仍然对表t1执行了一个完全扫描,但能够在表t2和t3上进行索引查找直接取出这些表中的行,比未用索引时要快一百万倍。
利用索引,MySQL加速了WHERE子句满足条件行的搜索,而在多表连接查询时,在执行连接时加快了与其他表中的行匹配的速度。
13.4.2 创建索引
在执行CREATE TABLE语句时可以创建索引,也可以单独用CREATE
INDEX或ALTER TABLE来为表增加索引。
1.ALTER TABLE
ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY
KEY索引。
ALTER TABLE table_name ADD INDEX index_name
(column_list)
ALTER TABLE table_name ADD UNIQUE
(column_list)
ALTER TABLE table_name ADD PRIMARY KEY
(column_list)
其中table_name是要增加索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。索引名index_name可选,缺省时,MySQL将根据第一个索引列赋一个名称。另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。
2.CREATE INDEX
CREATE INDEX可对表增加普通索引或UNIQUE索引。
CREATE INDEX index_name ON table_name
(column_list)
CREATE UNIQUE INDEX index_name ON table_name
(column_list)
table_name、index_name和column_list具有与ALTER
TABLE语句中相同的含义,索引名不可选。另外,不能用CREATE INDEX语句创建PRIMARY
KEY索引。
3.索引类型
在创建索引时,可以规定索引能否包含重复值。如果不包含,则索引应该创建为PRIMARY
KEY或UNIQUE索引。对于单列惟一性索引,这保证单列不包含重复的值。对于多列惟一性索引,保证多个值的组合不重复。
PRIMARY KEY索引和UNIQUE索引非常类似。事实上,PRIMARY
KEY索引仅是一个具有名称PRIMARY的UNIQUE索引。这表示一个表只能包含一个PRIMARY
KEY,因为一个表中不可能具有两个同名的索引。
下面的SQL语句对students表在sid上添加PRIMARY
KEY索引。
ALTER TABLE students ADD PRIMARY KEY
(sid)
13.4.3 删除索引
可利用ALTER TABLE或DROP
INDEX语句来删除索引。类似于CREATE
INDEX语句,DROP INDEX可以在ALTER
TABLE内部作为一条语句处理,语法如下。
DROP INDEX index_name ON
talbe_name
ALTER TABLE table_name DROP INDEX
index_name
ALTER TABLE table_name DROP PRIMARY
KEY
其中,前两条语句是等价的,删除掉table_name中的索引index_name。
第3条语句只在删除PRIMARY
KEY索引时使用,因为一个表只可能有一个PRIMARY KEY索引,因此不需要指定索引名。如果没有创建PRIMARY KEY索引,但表具有一个或多个UNIQUE索引,则MySQL将删除第一个UNIQUE索引。
如果从表中删除了某列,则索引会受到影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。
13.5.1 使用INSERT增加记录
向表中增加新记录有以下几种方法。
通过INSERT语句将记录插入表中;
通过从某个文件读取数据来增加记录,可以利用LOAD DATA或mysqlimport实用程序装入数据,也可以预先写成多个INSERT语句的形式。
本节将首先介绍使用INSERT语句。使用INSERT语句,需要指定接收数据的表,以及所要增加的数据,具有以下几种形式。
1.给出所有列的值
语法如下。
INSERT INTO table_name
VALUES(value1,value2,...)
例如:
#向students表中添加两个学生记录张三
mysql> INSERT INTO students
VALUES
-> (1,'张三','男',21);
#向courses表中添加三个课程记录C语言
mysql> INSERT INTO courses
VALUES
-> (1,'C语言',3,'李老师');
#向stu_cou表中添加张三和李四的成绩信息
mysql> INSERT INTO stu_cou
VALUES
-> ( 1,2,90 );
其中,VALUES必须包含表中每列的值,并且按表中列的存放次序依次给出。在MySQL 中,需要用单引号或双引号将串和日期值括起来。如果某一列为AUTO_INCREMENT,则可赋值为NULL。
另外,利用单个的INSERT语句可以一次将几行插入一个表中。
INSERT INTO table_name VALIUES
(...),(...),...
如下所示:
mysql> INSERT INTO students
VALUES
-> (1,'张三','男',21),
-> (2,'李四','男',22);
2.给出要赋值的列,然后再给出值
语法如下。
INSERT INTO
table_name(column_name1,column_name2,...) VALUES(value1,
value1,...)
这种方式适用于只给表中的某些列赋值,例如:
mysql> INSERT INTO
courses(cid,cname,ccredit) VALUES
-> (1,'C语言',3);
同样,也可以一次添加多个记录:
mysql> INSERT INTO
courses(cid,cname,ccredit) VALUES
-> (1,'C语言',3);
-> (2,'数据结构',2);
对于未指定的列,所添加的指为默认值,或NULL。
3.用col_name =
value 的形式给出列和值
语法如下。
INSERT INTO table_name SET
column_name1=value1,column_name2=value2,...
例如:
mysql> INSERT INTO courses
SET
-> (cid=1,,cname='C语言',ccredit=3);
使用这种形式的INSERT不能一次插入多行数据。
13.5.2 使用LOAD
DATA批量增加记录
从文件读取数据可以将大量记录装载到表中。LOAD DATA语句可以批量装载数据,它从一个文件中读取数据。
mysql> LOAD DATA LOCAL
INFILE "students_data.txt" INTO TABLE students;
该语句读取客户机当前目录中数据文件students_data.txt的内容,并将其发送到服务器并装入students表。缺省时,LOAD
DATA语句假定列值由tab键分隔,而行以换行符结束,并假定各个值是按列在表中的存放次序给出的。
13.5.3 使用mysqlimport批量增加记录
除LOAD
DATA外,还可以使用mysqlimport实用程序直接从文件读取批量数据。实际上,mysqlimport实用程序相当于LOAD
DATA 的命令行的一个接口。
从外壳程序调用mysqlimport:
% mysqlimport --local
students_coureses student.txt
Mysqlimport自动生成一个LOAD
DATA语句,此语句把student.txt文件中的数据装入student表。Mysqlimport根据数据文件名导出表名(将文件名第一个圆点前的所有字符作为表名)。例如,student.txt
将被装入student表,而president.txt将被装入president表。如果有多个需要装入单个表的文件,应仔细地确定文件名,使表名一致,如table.1.txt、table.2.txt等,否则mysqlimport
将不能使用正确的表名。
对于table1.txt与table2.txt
这样的文件名,mysqlimport将会认为相应的表名为table1和table2。
13.5.4 修改记录(UPDATE)
为了修改现有记录,可利用UPDATE语句,格式如下。
UPDATE table_name
SET
column_name1=new_value1,column_name2=new_value2,...
WHERE Condition
WHERE子句是可选的,指定所要修改的记录。如果不指定的话,表中的每个记录都被更新。下面的查询将每个学生的性别都更改为“F”。
UPDATE students SET sgender='F'
将每个学生的年龄都加1:
UPDATE students SET sage=sage+1
本节,不详细介绍WHERE字句,在下一节“查询数据”中将详细介绍。
13.5.5 删除记录(DELETE)
DELETE语句用于删除现有记录,格式如下。
DELETE FROM table_name
WHERE Condition
同UPDATE一样,WHERE子句是可选的,指定所要删除的记录。如果不指定,表中的每个记录都被删除。下面的查询将删除性别为“F”的学生。
DELETE FROM students WHERE
sgender='f'
下面的查询将删除性别为“F”并且年龄大于21的学生。
DELETE FROM students WHERE sgender='f' AND
sage>21
不带WHERE的DELETE查询将删除所有的学生。
DELETE FROM students
刚给一个朋友解决他写的Discuz!插件的问题,说到MySQL的COUNT(*)的效率,发现越说越说不清楚,干脆写下来,分享给大家。
COUNT(*)与COUNT(COL)
网上搜索了下,发现各种说法都有:
比如认为COUNT(COL)比COUNT(*)快的;
认为COUNT(*)比COUNT(COL)快的;
还有朋友很搞笑的说到这个其实是看人品的。
在不加WHERE限制条件的情况下,COUNT(*)与COUNT(COL)基本可以认为是等价的;
但是在有WHERE限制条件的情况下,COUNT(*)会比COUNT(COL)快非常多;
具体的数据参考如下:
mysql> SELECT COUNT(*) FROM cdb_posts where fid =
604;
+————+
| COUNT(fid) |
+————+
| 79000 |
+————+
1 row in set (0.03 sec)
mysql> SELECT COUNT(tid) FROM cdb_posts where fid
= 604;
+————+
| COUNT(tid) |
+————+
| 79000 |
+————+
1 row in set (0.33 sec)
mysql> SELECT COUNT(pid) FROM cdb_posts where fid
= 604;
+————+
| COUNT(pid) |
+————+
| 79000 |
+————+
1 row in set (0.33 sec)
COUNT(*)通常是对主键进行索引扫描,而COUNT(COL)就不一定了,另外前者是统计表中的所有符合的纪录总数,而后者是计算表中所有符合的COL的纪录数。还有有区别的。
COUNT时的WHERE
这点以前就写过,详细请看《Mysql中count(*),DISTINCT的使用方法和效率研究》:http://www.ccvita.com/156.html
简单说下,就是COUNT的时候,如果没有WHERE限制的话,MySQL直接返回保存有总的行数
而在有WHERE限制的情况下,总是需要对MySQL进行全表遍历。
优化总结:
1.任何情况下SELECT COUNT(*) FROM tablename是最优选择;
2.尽量减少SELECT COUNT(*) FROM tablename WHERE COL = ‘value’
这种查询;
3.杜绝SELECT COUNT(COL) FROM tablename WHERE COL2 = ‘value’ 的出现。