mysql 事务、索引、锁、分区/分表、sql优化、查询优化

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)



mysql 索引类型以及创建

一、简介
MySQL目前主要有以下几种索引类型:
1.普通索引
2.唯一索引
3.主键索引
4.组合索引
5.全文索引

二、语句
CREATE TABLE table_name[col_name data type]
[unique|fulltext][index|key][index_name](col_name[length])[asc|desc]
1.unique|fulltext为可选参数,分别表示唯一索引、全文索引
2.index和key为同义词,两者作用相同,用来指定创建索引
3.col_name为需要创建索引的字段列,该列必须从数据表中该定义的多个列中选择
4.index_name指定索引的名称,为可选参数,如果不指定,默认col_name为索引值
5.length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度
6.asc或desc指定升序或降序的索引值存储

三、索引类型
1.普通索引
是最基本的索引,它没有任何限制。它有以下几种创建方式:
(1)直接创建索引

CREATE INDEX index_name ON table(column(length))
(2)修改表结构的方式添加索引

ALTER TABLE table_name ADD INDEX index_name ON (column(length))
(3)创建表的时候同时创建索引

复制代码
CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    PRIMARY KEY (`id`),
    INDEX index_name (title(length))
)
复制代码
(4)删除索引

DROP INDEX index_name ON table
2.唯一索引
与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
(1)创建唯一索引

CREATE UNIQUE INDEX indexName ON table(column(length))
(2)修改表结构

ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
(3)创建表的时候直接指定

复制代码
CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    UNIQUE indexName (title(length))
);
复制代码
3.主键索引
是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引:

CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) NOT NULL ,
    PRIMARY KEY (`id`)
);
4.组合索引
指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合

ALTER TABLE `table` ADD INDEX name_city_age (name,city,age); 
5.全文索引
主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的where语句的参数匹配。fulltext索引配合match against操作使用,而不是一般的where语句加like。它可以在create table,alter table ,create index使用,不过目前只有char、varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用CREATE index创建fulltext索引,要比先为一张表建立fulltext然后再将数据写入的速度快很多。
(1)创建表的适合添加全文索引

复制代码
CREATE TABLE `table` (
    `id` int(11) NOT NULL AUTO_INCREMENT ,
    `title` char(255) CHARACTER NOT NULL ,
    `content` text CHARACTER NULL ,
    `time` int(10) NULL DEFAULT NULL ,
    PRIMARY KEY (`id`),
    FULLTEXT (content)
);
复制代码
(2)修改表结构添加全文索引

ALTER TABLE article ADD FULLTEXT index_content(content)
(3)直接创建索引

CREATE FULLTEXT INDEX index_content ON article(content)
四、缺点
1.虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行insert、update和delete。因为更新表时,不仅要保存数据,还要保存一下索引文件。
2.建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会增长很快。
索引只是提高效率的一个因素,如果有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。

五、注意事项
使用索引时,有以下一些技巧和注意事项:
1.索引不会包含有null值的列
只要列中包含有null值都将不会被包含在索引中,复合索引中只要有一列含有null值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为null。
2.使用短索引
对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个char(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
3.索引列排序
查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
4.like语句操作
一般情况下不推荐使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。
5.不要在列上进行运算
这将导致索引失效而进行全表扫描,例如

SELECT * FROM table_name WHERE YEAR(column_name)<2017;
6.不使用not in和<>操作
关于MySQL索引的好处,如果正确合理设计并且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索引的MySQL就是一个人力三轮车。对于没有索引的表,单表查询可能几十万数据就是瓶颈,而通常大型网站单日就可能会产生几十万甚至几百万的数据,没有索引查询会变的非常缓慢。还是以WordPress来说,其多个数据表都会对经常被查询的字段添加索引,比如wp_comments表中针对5个字段设计了BTREE索引。

一个简单的对比测试
以我去年测试的数据作为一个简单示例,20多条数据源随机生成200万条数据,平均每条数据源都重复大概10万次,表结构比较简单,仅包含一个自增ID,一个char类型,一个text类型和一个int类型,单表2G大小,使用MyIASM引擎。开始测试未添加任何索引。

执行下面的SQL语句:

1	mysql> SELECT id,FROM_UNIXTIME(time) FROM article WHERE a.title='测试标题'
查询需要的时间非常恐怖的,如果加上联合查询和其他一些约束条件,数据库会疯狂的消耗内存,并且会影响前端程序的执行。这时给title字段添加一个BTREE索引:

1	mysql> ALTER TABLE article ADD INDEX index_article_title ON title(200);
再次执行上述查询语句,其对比非常明显:

 

MySQL索引的概念
索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度。上述SQL语句,在没有索引的情况下,数据库会遍历全部200条数据后选择符合条件的;而有了相应的索引之后,数据库会直接在索引中查找符合条件的选项。如果我们把SQL语句换成“SELECT * FROM article WHERE id=2000000”,那么你是希望数据库按照顺序读取完200万行数据以后给你结果还是直接在索引中定位呢?上面的两个图片鲜明的用时对比已经给出了答案(注:一般数据库默认都会为主键生成索引)。

索引分为聚簇索引和非聚簇索引两种,聚簇索引是按照数据存放的物理位置为顺序的,而非聚簇索引就不一样了;聚簇索引能提高多行检索的速度,而非聚簇索引对于单行的检索很快。

MySQL索引的类型
1. 普通索引

这是最基本的索引,它没有任何限制,比如上文中为title字段创建的索引就是一个普通索引,MyIASM中默认的BTREE类型的索引,也是我们大多数情况下用到的索引。

01	–直接创建索引
02	CREATE INDEX index_name ON table(column(length))
03	–修改表结构的方式添加索引
04	ALTER TABLE table_name ADD INDEX index_name ON (column(length))
05	–创建表的时候同时创建索引
06	CREATE TABLE `table` (
07	`id` int(11) NOT NULL AUTO_INCREMENT ,
08	`title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
09	`content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,
10	`time` int(10) NULL DEFAULT NULL ,
11	PRIMARY KEY (`id`),
12	INDEX index_name (title(length))
13	)
14	–删除索引
15	DROP INDEX index_name ON table
2. 唯一索引

与普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值(注意和主键不同)。如果是组合索引,则列值的组合必须唯一,创建方法和普通索引类似。

01	–创建唯一索引
02	CREATE UNIQUE INDEX indexName ON table(column(length))
03	–修改表结构
04	ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
05	–创建表的时候直接指定
06	CREATE TABLE `table` (
07	`id` int(11) NOT NULL AUTO_INCREMENT ,
08	`title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
09	`content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,
10	`time` int(10) NULL DEFAULT NULL ,
11	PRIMARY KEY (`id`),
12	UNIQUE indexName (title(length))
13	);
3. 全文索引(FULLTEXT)

MySQL从3.23.23版开始支持全文索引和全文检索,FULLTEXT索引仅可用于 MyISAM 表;他们可以从CHAR、VARCHAR或TEXT列中作为CREATE TABLE语句的一部分被创建,或是随后使用ALTER TABLE 或CREATE INDEX被添加。对于较大的数据集,将你的资料输入一个没有FULLTEXT索引的表中,然后创建索引,其速度比把资料输入现有FULLTEXT索引的速度更为快。不过切记对于大容量的数据表,生成全文索引是一个非常消耗时间非常消耗硬盘空间的做法。

01	–创建表的适合添加全文索引
02	CREATE TABLE `table` (
03	`id` int(11) NOT NULL AUTO_INCREMENT ,
04	`title` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
05	`content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,
06	`time` int(10) NULL DEFAULT NULL ,
07	PRIMARY KEY (`id`),
08	FULLTEXT (content)
09	);
10	–修改表结构添加全文索引
11	ALTER TABLE article ADD FULLTEXT index_content(content)
12	–直接创建索引
13	CREATE FULLTEXT INDEX index_content ON article(content)
4. 单列索引、多列索引

多个单列索引与单个多列索引的查询效果不同,因为执行查询时,MySQL只能使用一个索引,会从多个索引中选择一个限制最为严格的索引。

5. 组合索引(最左前缀)

平时用的SQL查询语句一般都有比较多的限制条件,所以为了进一步榨取MySQL的效率,就要考虑建立组合索引。例如上表中针对title和time建立一个组合索引:ALTER TABLE article ADD INDEX index_titme_time (title(50),time(10))。建立这样的组合索引,其实是相当于分别建立了下面两组组合索引:

–title,time

–title

为什么没有time这样的组合索引呢?这是因为MySQL组合索引“最左前缀”的结果。简单的理解就是只从最左面的开始组合。并不是只要包含这两列的查询都会用到该组合索引,如下面的几个SQL所示:

1	–使用到上面的索引
2	SELECT * FROM article WHREE title='测试' AND time=1234567890;
3	SELECT * FROM article WHREE utitle='测试';
4	–不使用上面的索引
5	SELECT * FROM article WHREE time=1234567890;
MySQL索引的优化
上面都在说使用索引的好处,但过多的使用索引将会造成滥用。因此索引也会有它的缺点:虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。下面是一些总结以及收藏的MySQL索引的注意事项和优化方法。

1. 何时使用聚集索引或非聚集索引?

动作描述	使用聚集索引	使用非聚集索引
列经常被分组排序	使用	使用
返回某范围内的数据	使用	不使用
一个或极少不同值	不使用	不使用
小数目的不同值	使用	不使用
大数目的不同值	不使用	使用
频繁更新的列	不使用	使用
外键列	使用	使用
主键列	使用	使用
频繁修改索引列	不使用	使用

 

事实上,我们可以通过前面聚集索引和非聚集索引的定义的例子来理解上表。如:返回某范围内的数据一项。比如您的某个表有一个时间列,恰好您把聚合索引建立在了该列,这时您查询2004年1月1日至2004年10月1日之间的全部数据时,这个速度就将是很快的,因为您的这本字典正文是按日期进行排序的,聚类索引只需要找到要检索的所有数据中的开头和结尾数据即可;而不像非聚集索引,必须先查到目录中查到每一项数据对应的页码,然后再根据页码查到具体内容。其实这个具体用法我还不是很理解,只能等待后期的项目开发中慢慢学学了。

2. 索引不会包含有NULL值的列

只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。

3. 使用短索引

对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

4. 索引列排序

MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

5. like语句操作

一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。

6. 不要在列上进行运算

例如:select * from users where YEAR(adddate)<2007,将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where adddate<’2007-01-01′。关于这一点可以围观:一个单引号引发的MYSQL性能损失。

最后总结一下,MySQL只对一下操作符才使用索引:<,<=,=,>,>=,between,in,以及某些时候的like(不以通配符%或_开头的情形)。
而理论上每张表里面最多可创建16个索引,不过除非是数据量真的很多,否则过多的使用索引也不是那么好玩的,比如我刚才针对text类型的字段创建索引的时候,系统差点就卡死了。

 


索引 


create index ix_test on aaa(col(20))
恰当的索引可以加快查询速度,可以分为四种类型:主键、唯一索引、全文索引、普通索引。
主键:唯一且没有null值。
create table pk_test(f1 int not null,primary key(f1));
alter table customer modify id int not null, add primary key(id);

普通索引:允许重复的值出现。
create table tableanme (fieldname1 columntype,fieldname2 columntype,index [indexname] (fieldname1 [,fieldname2...]));
create table tablename add index [indexname] (fieldname1 [fieldname2...]);
alter table slaes add index(value);

全文索引:用来对大表的文本域(char,varchar,text)进行索引。
语法和普通索引一样-fulltext。
使用全文索引:create table ft2 (f1 varchar(255),fulltext(f1));
insert into ft2 values('wating for the bvarbariands'),('in the heart of the country'),('the master of petersburg'),('writing and being'),('heart of the beast'),('master master');
select * from ft2 where match(f1) against('master'); // match()-匹配域;against()匹配值。
mysql会对某些字忽略,造成查询的误差:a. 50%以上的域出现的单词;b.少于三个字的单词;c.mysql预定义的列表,包括the。查询语句:select * from ft2 where match(f1) against('the master'); // 与希望的结果是不同的
相关性分数查询:select f1,(match(f1) against('master')) from ft2;
mysql4的新功能-布尔全文查询:select * from ft2 where match(f1) against('+master -pet' in boolean mode); // 运算符类型 +-<>()~*"

唯一索引:除了不能有重复的记录外,其它和普通索引一样。
create table ui_test (f1 int,f2 int,unique(f1));
alter table ui_test add unique(f2);
对域(varchar,char,blob,text)的部分创建索引:alter table customer add index (surname(10));

自动增加域:每次插入记录时会自动增加一个域的值,只能用于一个域,且这个域有索引。
create table tablename(fieldname int auto_increment,[fieldname2...,] primary key(filedname));
alter table tablename modify fieldname columntype auto_increment;
last_insert_id()函数返回最新插入的自动增加值。
select last_insert_id() from customer limit 1;
此函数在多个连接同时进行时,会发生错误。

重置自动增加计数器的值:
create table tablename(fieldname int auto_increment,[fieldname2...,] primary key(filedname) auto_increment=50);
alter table tablename auto_increment=50;
如果重置的值比存在的值小,自动增加计数器会从记录中最大的那个值开始增加计数,比如customer表中的id已经有1、2、3、15、16、20,当把自动增加计数器的值设为1时,下次插入的记录会从21开始。
自动增加计数器的越界:有效值为1~2的127次方,即2147483647。如果超过这个值(包括负值),mysql会自动把它设为最大值,这样就会产生一个重复键值的错误。
自动增加域在多列索引中的使用:
create table staff(rank enum('employee','manager','contractor') not null,position varchar(100),id int not null auto_increment,primary key(rank,id));
insert into staff(rank,position) values('employee','cleaner'),('cotractor','network maintenance'),('manager','sales manager');
在对每个级别添加一些数据,会看到熟悉的自动增加现象:
insert into staff(rank,position) values('employee','cleaner1'),('employee','network maintenance1'),('manager','sales manager1');
在这种情况下是不能重置自动增加计数器的。

删除或更改索引:对索引的更改都需要先删除再重新定义。
alter table tablename drop primary key;
alter table table drop index indexname;
drop index on tablename;

高效使用索引:下面讨论的是用了索引会给我们带来什么?
1.) 获得域where从句中匹配的行:select * from customer where surname>'c';
2.) 查找max()和min()值时,mysql只需在排序的索引中查找第一个和最后一个值。
3.) 返回的部分是索引的一部分,mysql就不需要去查询全表的数据而只需看索引:select id from customer;
4.) 对域使用order by的地方:select * from customer order by surname;
5.) 还可以加速表的连接:select first_name,surname,commission from sales,sales_rep where sales.sales_rep=sales_rep.employee_number and code=8;
6.) 在通配符的情况下:select * from sales_rep where surname like 'ser%';
     这种情况就不能起作用:select * from sales_rep where surname like '%ser%';

选择索引:
1.) 有查询需要使用索引(比如where从句中条件的域)的时候,要创建索引;不要不使用的域(不如第一个字符是通配符的)创建索引。
2.) 创建的索引返回的行越少越好,主键最好,枚举类型的索引不什么用处。
3.) 使用短索引(比如,名字的头十个字符而不是全部)。
4.) 不要创建太多的索引,虽然加快了查询的速度,但增加了更新的添加记录的时间。如果索引在查询中很少使用,而没有索引只是轻微的影响速度,就不要创建索引。
最左边规则:这种情况发生在多个有索引的域上,mysql从索引列表的最左边开始,按顺序使用他们。

alter table customer add initial varchar(5);
alter table customer add index(surname,initial,first_name);
update customer set initial='x' where id=1;
update customer set initial='c' where id=2;
update customer set initial='v' where id=3;
update customer set initial='b' where id=4;
update customer set initial='n' where id=20;
update customer set initial='m' where id=21;
如果在查询中使用了这三个域,那就最大限度的利用了索引:select * from customer where surname='clegg' and initial='x' and first_name='yvonne';
或者是利用索引的大部分:select * from customer where surname='clegg' and initial='x';
或仅仅是surname:select * from customer where surname='clegg';
如果打破最左边规则,下面的例子就不会用到索引:select * from customer where  initial='x' and first_name='yvonne';
select * from customer where initial='x' ;
select * from customer where first_name='yvonne';
select * from customer where surname='clegg' and first_name='yvonne';

使用explain-解释mysql如何使用索引来处理select语句及连接表的。
输入 explain select * from customer; 后,出现一张表,个行的意思如下:
table-显示此行数据属于哪张表;type-重要的一列,显示使用了何种连接,从好到差依次为const、eq_ref、ref、range、index、all,下面会详细说明;possible_keys-可以应用在这张表中的索引,如果为null,则表示没有可用索引;key-实际使用的索引,如为null,表示没有用到索引;key_len-索引的长度,在不损失精确度的情况下,越短越好;ref-显示索引的哪一列被使用了,如果可能的话,是个常数;rows-返回请求数据的行数;extra-关于mysql如何解析查询的额外信息,下面会详细说明。

extra行的描述:distinct-mysql找到了域行联合匹配的行,就不再搜索了;
not exists-mysql优化了left join,一旦找到了匹配left join的行,就不再搜索了;
range checked for each-没找到理想的索引,一次对于从前面表中来的每一个行组合;
record(index map: #)-检查使用哪个索引,并用它从表中返回行,这是使用索引最慢的一种;
using filesort-看到这个就需要优化查询了,mysql需要额外的步骤来发现如何对返回的行排序。他根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。
using index-列数据是从单单使用了索引中的信息而没有读取实际行的表返回的,这发生在对表的全部的请求列都是同一个索引时;
using temporary-看到这个就需要优化查询了,mysql需要创建一个临时表来查询存储结果,这通常发生在多不同的列表进行order by时,而不是group by;
where used-使用了where从句来限制哪些行将与下一张表匹配或是返回给用户。如不想返回表中用的全部行,并连接类型是all或index,这就会发生,也可能是查询有问题。

type的描述:system-表只有一行,这是const连接类型的特例;const-表中一个记录的最大值能够匹配这个查询(索引可以是主键或唯一索引)。因为只有一行,这个值实际就是常数,因为mysql先读这个值,再把它当作常数对待;eq_ref-从前面的表中,对每一个记录的联合都从表中读取一个记录。在查询使用索引为主键或唯一索引的全部时使用;ref-只有使用了不是主键或唯一索引的部分时发生。对于前面表的每一行联合,全部记录都将从表中读出,这个连接类型严重依赖索引匹配记录的多少-越少越好;range-使用索引返回一个范围中的行,比如使用>或<查找时发生;index-这个连接类型对前面的表中的每一个记录联合进行完全扫描(比all好,因为索引一般小于表数据);all-这个连接类型多前面的表中的每一个记录联合进行完全扫描,这个比较糟糕,应该尽量避免。
举个例子:create index sales_rep on sales(sales_rep);  // 可以比较一下创建索引前后的变化
explain select * from sales_rep left join sales on sales.sales_rep = sales_rep.employee_number;
结果如下:
table          type          
possible
_keys
         key           key_len           ref           rows           extra
sales_rep          all          null          null          null           null           5           
sales          ref          sales_rep          sales_rep          5         
sales_rep.
employee_number
         2          
这个结果表示sales_rep表有个不好的连接类型-all,没用到索引,要查询的行数为5;sales的连接类型为ref,可用的索引是 sales_rep,实际也使用sales_rep索引,这个索引的长度是5,对应的列是employee_number,要查询的行数为2,所以这次查询对表共进行了5×2次查询。

查看索引信息:show index from tablename;
列的描述:table-正在查看的表名;non_unique-1或1.0表示索引不能包含重复值(主键和唯一索引),1表示可以;key_name-索引名;seq_in_index-索引中列的顺序,从1开始;column_name-列名;collation-a或null,a表示索引以序升排列,null表示不排序;cardinality-索引中唯一值的个数;sub_part-如果整个列为索引,值为null,否则以字符表示索引的大小;packed-是否打包;null-如果列能包含null,则为yes;comment-各种注释。
命名规范
1. 数据库、表、字段、别名规范
识别符         最大长度(字节)         允许的字符
数据库         64         [a-z_] (所有字符均小写, 字之间用 _ 分割)
表         64         [a-z_] (所有字符均小写, 字之间用 _ 分割)
列         64         [a-z_] (所有字符均小写, 字之间用 _ 分割)
索引         64         [a-z_] (所有字符均小写, 字之间用 _ 分割)
别名         255         [a-z_] (所有字符均小写, 字之间用 _ 分割)

数据库、表、列、索引、别名的命名应尽可能描述其真实的意思。
2. 统一命名

    字段

命名空间为:
数据库::表(数据库_表名):: 字段(简)

* 名称或标题      name (char[])
* 创建时间  create_time (datetime)
* 更新时间  update_time (datetime)
* 过期时间  expire_time (datetime)
* 数据状态  status (tinyint) ''0:正常 1:隐藏''
* ID       id (int)
* IP       ip (char[19])
* 资源文件/图片Id   resource_id (int)
* 标签  tag  (char[])
* 类型 type (tinyint)

索引 命名空间为:

index_table_field
unique_table_field
key_table_field

3. 所有日志表均以 log_ 开头 如 :log_user_login
4. 各模块表以模块名开头 如奖品:award award_exchange
5. 数据库、表的备份,请使用数据库、表加备份时间 如:

数据库 '''camp camp_20091130'''
  表 '''award award_20091130'''

6. 对于与用户表关联的其它表,对于用户表的关联字段,除非有特殊需要,请使用 username 关联而不要使用 user_id 关联。因为许多查询中都使用 username,这样可以避免不必要的查询 (从 user_id 查得 username
二、设计规则

    在设计过程中,应该从实际需求出发,以性能提升为根本目标来展开工作,很多时候为了尽可能提高性能,必须做反范式设计。

1. 适度冗余,让查询尽量减少 JOIN (MYSQL JOIN 性能不是很高)
2. 大字段垂直分表

    大字段垂直分表简单来说就是将自己身上的字段拆分出去放到另外的表里。
    大字段一般都是存放着一些较长的 Detail 信息,如文章内容、帖子内容、产品的介绍等。
    其次是和表中的其它字段相比访问频率明显要少很多。

3. 合适的数据类型

   1. 通过选用更 "小" 的数据类型减少存储空间, 使查询相同数据需要的IO资源降低.
   2. 通过合适的数据类型加速数据的比较.
   3. 选择字段时尽量不要选用 SET, EMNUM 类型, 不便于扩展.
   4. 除了像 TEXT, BLOB, AUTO_INCREMENT 等这些列不能指定默认值的列类型之外, 应尽量为每个字段指定默认值. 这样可以增强数据的移植性和减少严格模式下出错的机会.
   5. 关联字段尽可能地建成相同列类型, 这样可以加快表关联搜索.
   6. 尽可以为每列指定 NOT NULL, 除了在确实需要 NULL 值的情况下. 这样可以减少存储空间和索引优化.
   7. 如果一个表没有像 text 这类字段,一个表尽可能用 char 替代 varchar,因为固定长度的表有更高的查询和恢复性能.
   8. 所有表、字段均应用 comment 列属性来描述此表、字段所代表的真正含义,除了意思明了的字段如: id

4. 适度的空间换时间

  比如一个查询频繁的表,如果大部分字都时静态长度的;可以全部都换成静态的长度的(静态表),以提高查询效率。

注意:

CHAR[M] 属于静态长度类型, 存放长度完全以字符数来计算, 所以最终的存储长度是基于字符集的, 如 latin1 其最大存储长度为 255 字节, 但是如果使用 gbk 则最大存储长度为 510 (255x2) 字节. CHAR 类型的存储特点是不管实际存放的数据多长, 在数据库中都会存放 M 个字符, 不够通过空格补上, M 默认为1. 虽然 CHAR 会通过空格补齐存放空间, 但是在访问数据时, MYSQL会忽略最的的所有空格, 所以如果实数据在最后确实需要空格, 则不能使用 CHAR 类型来存放. 在MYSQL 5.03 之前的版本中, 如果定义 CHAR时 M值超过 255, MYSQL 会自动将 CHAR 类型转换为可以存入对应数据量的 TEXT类型, 如 CHAR(1000) 会自动转换为 TEXT, CHAR(10000) 则会转为 MEDIUMTEXT. 而从 MYSQL 5.0.3 开始, 所有超过 255 的定义 MYSQL 都会直接拒绝并给出错误信息, 不再自动转换.

VARCHAR[M] 属于动态存储长度类型, 仅存储占用实际存储数据的长度. 其存放的最大长度与 MYSQL 版本有关, 在 5.0.3 之前的版本 VARCHAR 以字符数控制存储的最大长度, 最大只能存放 255 个字符, 占用存储空间的实际大小与字符集有关. 但是从 5.0.3 开始, VARCHAR 的最大存储限制已经更改为字节数限制了, 扩展到可以存放 65535 字节的数据, 不同的字符集可能存放的字符数并不一样. 也就是说, 在 MYSQL 5.0.3 之前的版本, M 所代表的是字符数, 而从 5.0.3 版本开始, M 代表字节数了. VARCHAR 的存储特点是不管设定 M 为多大值, 真正占用的存储空间只有存入的实际数据的大小, 和 CHAR 不同的是 VARCAHR 会保留存入数据最后的空格, 也就是说我们存入什么, MYSQL 返回的就是什么. 在 VARCHAR 类型字段的数据中, MYSQL 会在每个 VARCHAR 数据中使用 1 到 2 个字节来存放 VARCHAR 数据的实际长度, 当实际数据在 255 字节之内时, 会使用 1 字节来存放实际长度, 而大于 255 字节时, 则需要使用 2 字节来存放.

TINYTEXT, TEXT, MEDIUMTEXT 和 LONGTEXT 这 4 种类型同属于一种存储方式, 即动态存储长度类型, 不同的仅是最大长度的限制. 4 种类型的定义都是通过最大字符数来限制, 但它们的字符数限制实际上是可以理解为字节数限制, 因为当使用多字节字符集时, 实际能存放的字符数并没最大字符数那么多, 而是以单字节字符来计算的字符数. 此外, 由于是动态存储长度类型, 所以和 VARCHAR 一样, 每个字段数据之前都需要一个存放实际长度的空间. TINYTEXT 需要 1 个字节来存放, TEXT 需要 2 个字节, MEDIUMTEXT 和 LONGTEXT 则分别需要 3 个和 4 个字节来存放实际数据长度. 实际上, 除了 MYSQL 内嵌的最大长度限制之外, 它们还受到客户端与服务器端的网络通信缓冲区最大值 (max_allowed_packet 默认为 1M, 也就是说, MEDIUMTEXT 和 LONGTEXT 在默认情况可能存不进去值) 的限制.

这 4 种 TEXT 类型和 CHAR 及 VARCHAR 在实际使用中存在几个不一样的地方:

1. 不能设置默认值.
2. 只有 TEXT 可以使用 TEXT[M] 这样的方式通过 M 设置大小.
3. 基于这 4 种类型的索引必须指定前缀长度.

TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别占用的字节为 1, 2, 3, 4, 8. INT 类型的值就上亿了.
对于 INT[M] 中 M 值的解释: 以前我遇到很多人他们认为 INT(4), INT(10) 其取值范围分别是 (-9999 到 9999), (-9999999999 到 9999999999). 这种理解是错误的. 其实对整型中的 M 值, 与 ZEROFILL 属性结合使用时, 可以实现列值等宽. 不管 INT[M] 中 M 值是多少, 其取值范围还是 (-2147483648 到 2147483647 有符号时), (0 到 4294967295 无符号时). 官方文档说明: 显示宽度并不限制可以在列内保存的值的范围, 也不限制超过列的指定宽度的值的显示. 当结合可选扩展属性ZEROFILL使用时, 默认补充的空格用零代替. 例如: 对于声明为INT(5) ZEROFILL的列, 值4检索为00004. 请注意如果在整数列保存超过显示宽度的一个值, 当MySQL为复杂联接生成临时表时会遇到问题, 因为在这些情况下MySQL相信数据适合原列宽度.如果为一个数值列指定ZEROFILL, MySQL自动为该列添加UNSIGNED属性.
三、创建索引

a. 较频繁的作为查询条件的字段应该创建索引.

b. 唯一性太差的字段不适合单独创建索引, 即使频繁作为查询条件.

唯一性太差的字段主要是指哪些呢? 如状态字段, 类型字段等这些字段中存放的数据可能总共就那么几个或几十个值重复使用, 每个值都会存在于成千上万或更多的记录中. 对于这类字段, 完全没有必要创建单独的索引. 因为即使创建了索引, MYSQL QUERY OPTIMIZER 大多数时候也不会去选择使用, 如果什么时候 MYSQL QUERY OPTIMIZER 选择了这各索引, 那么非常遗憾地告诉你, 这可能会带来极大的性能问题. 由于索引字段中每个值都会含有大量的记录, 那么存储引擎在根据索引访问数据的时候会带来大量的随机 IO, 甚至有些时候还会出现大量的重复 IO.

c. 更新频繁的字段不适合创建索引

索引中的字段被更新的时候, 不仅要更新表中的数据, 还要更新索引数据, 以确保索引信息是准确. 这个问题致使 IO 访问量较大增加, 不仅仅影响了更新 Query 的响应时间, 还影响了整个存储系统资源消耗, 加大了整个存储系统负载.

d. 不会出现在 WHERE 子句中的字段不该创建索引.

注意:
MYSQL 中索引的限制
1. MYISAM 存储引擎索引长度的总和不能超过 1000 字节.
2. BLOB 和 TEXT 类型的列只能创建前缀索引.
3. MYSQL 目前不支持函数索引.
4. 使用不等于 (!= 或者 <>) 的时候, MYSQL 无法使用索引.
5. 过滤字段使用函数运算 (如 abs (column)) 后, MYSQL无法使用索引.
6. Jion 语句 中 Jion 条件字段类型不一致的时候, MYSQL无法使用索引.
7. 使用 LIKE 操作的时候如果条件以通配符开始 (如 '%abc...')时, MYSQL无法使用索引.
8. 使用非等值查询的时候, MYSQL 无法使用 Hash 索引.

建表 SQL 语句示例:

http://hi.baidu.com/thk_phper/bl ... 0d7c330dd7da6f.html

CREATE TABLE `admin` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `role_id` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '所属组ID',
  `username` char(16) NOT NULL COMMENT '用户名',
  `password` char(33) NOT NULL COMMENT '密码',
  `acl` text NOT NULL COMMENT '资源控制',
  `ctime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='用户表';
恰当的索引可以加快查询速度,可以分为四种类型:主键、唯一索引、全文索引、普通索引。
主键:唯一且没有null值。
create table pk_test(f1 int not null,primary key(f1));
alter table customer modify id int not null, add primary key(id);
普通索引:允许重复的值出现。
create table tableanme (fieldname1 columntype,fieldname2 columntype,index [indexname] (fieldname1 [,fieldname2...]));
create table tablename add index [indexname] (fieldname1 [fieldname2...]);
alter table slaes add index(value);
全文索引:用来对大表的文本域(char,varchar,text)进行索引。
语法和普通索引一样-fulltext。
使用全文索引:create table ft2 (f1 varchar(255),fulltext(f1));
insert into ft2 values('wating for the bvarbariands'),('in the heart of the country'),('the master of petersburg'),('writing and being'),('heart of the beast'),('master master');
select * from ft2 where match(f1) against('master'); // match()-匹配域;against()匹配值。
mysql会对某些字忽略,造成查询的误差:a. 50%以上的域出现的单词;b.少于三个字的单词;c.mysql预定义的列表,包括the。查询语句:select * from ft2 where match(f1) against('the master'); // 与希望的结果是不同的
相关性分数查询:select f1,(match(f1) against('master')) from ft2;
mysql4的新功能-布尔全文查询:select * from ft2 where match(f1) against('+master -pet' in boolean mode); // 运算符类型 +-<>()~*"
唯一索引:除了不能有重复的记录外,其它和普通索引一样。
create table ui_test (f1 int,f2 int,unique(f1));
alter table ui_test add unique(f2);
对域(varchar,char,blob,text)的部分创建索引:alter table customer add index (surname(10));
自动增加域:每次插入记录时会自动增加一个域的值,只能用于一个域,且这个域有索引。
create table tablename(fieldname int auto_increment,[fieldname2...,] primary key(filedname));
alter table tablename modify fieldname columntype auto_increment;
last_insert_id()函数返回最新插入的自动增加值。
select last_insert_id() from customer limit 1;
此函数在多个连接同时进行时,会发生错误。
重置自动增加计数器的值:
create table tablename(fieldname int auto_increment,[fieldname2...,] primary key(filedname) auto_increment=50);
alter table tablename auto_increment=50;
如果重置的值比存在的值小,自动增加计数器会从记录中最大的那个值开始增加计数,比如customer表中的id已经有1、2、3、15、16、20,当把自动增加计数器的值设为1时,下次插入的记录会从21开始。
自动增加计数器的越界:有效值为1~2的127次方,即2147483647。如果超过这个值(包括负值),mysql会自动把它设为最大值,这样就会产生一个重复键值的错误。
自动增加域在多列索引中的使用:
create table staff(rank enum('employee','manager','contractor') not null,position varchar(100),id int not null auto_increment,primary key(rank,id));
insert into staff(rank,position) values('employee','cleaner'),('cotractor','network maintenance'),('manager','sales manager');
在对每个级别添加一些数据,会看到熟悉的自动增加现象:
insert into staff(rank,position) values('employee','cleaner1'),('employee','network maintenance1'),('manager','sales manager1');
在这种情况下是不能重置自动增加计数器的。
删除或更改索引:对索引的更改都需要先删除再重新定义。
alter table tablename drop primary key;
alter table table drop index indexname;
drop index on tablename;
高效使用索引:下面讨论的是用了索引会给我们带来什么?
1.) 获得域where从句中匹配的行:select * from customer where surname>'c';
2.) 查找max()和min()值时,mysql只需在排序的索引中查找第一个和最后一个值。
3.) 返回的部分是索引的一部分,mysql就不需要去查询全表的数据而只需看索引:select id from customer;
4.) 对域使用order by的地方:select * from customer order by surname;
5.) 还可以加速表的连接:select first_name,surname,commission from sales,sales_rep where sales.sales_rep=sales_rep.employee_number and code=8;
6.) 在通配符的情况下:select * from sales_rep where surname like 'ser%';
     这种情况就不能起作用:select * from sales_rep where surname like '%ser%';
选择索引:
1.) 有查询需要使用索引(比如where从句中条件的域)的时候,要创建索引;不要不使用的域(不如第一个字符是通配符的)创建索引。
2.) 创建的索引返回的行越少越好,主键最好,枚举类型的索引不什么用处。
3.) 使用短索引(比如,名字的头十个字符而不是全部)。
4.) 不要创建太多的索引,虽然加快了查询的速度,但增加了更新的添加记录的时间。如果索引在查询中很少使用,而没有索引只是轻微的影响速度,就不要创建索引。
最左边规则:这种情况发生在多个有索引的域上,mysql从索引列表的最左边开始,按顺序使用他们。
alter table customer add initial varchar(5);
alter table customer add index(surname,initial,first_name);
update customer set initial='x' where id=1;
update customer set initial='c' where id=2;
update customer set initial='v' where id=3;
update customer set initial='b' where id=4;
update customer set initial='n' where id=20;
update customer set initial='m' where id=21;
如果在查询中使用了这三个域,那就最大限度的利用了索引:select * from customer where surname='clegg' and initial='x' and first_name='yvonne';
或者是利用索引的大部分:select * from customer where surname='clegg' and initial='x';
或仅仅是surname:select * from customer where surname='clegg';
如果打破最左边规则,下面的例子就不会用到索引:select * from customer where  initial='x' and first_name='yvonne';
select * from customer where initial='x' ;
select * from customer where first_name='yvonne';
select * from customer where surname='clegg' and first_name='yvonne';

使用explain-解释mysql如何使用索引来处理select语句及连接表的。
输入 explain select * from customer; 后,出现一张表,个行的意思如下:
table-显示此行数据属于哪张表;type-重要的一列,显示使用了何种连接,从好到差依次为const、eq_ref、ref、range、index、all,下面会详细说明;possible_keys-可以应用在这张表中的索引,如果为null,则表示没有可用索引;key-实际使用的索引,如为null,表示没有用到索引;key_len-索引的长度,在不损失精确度的情况下,越短越好;ref-显示索引的哪一列被使用了,如果可能的话,是个常数;rows-返回请求数据的行数;extra-关于mysql如何解析查询的额外信息,下面会详细说明。
extra行的描述:distinct-mysql找到了域行联合匹配的行,就不再搜索了;
not exists-mysql优化了left join,一旦找到了匹配left join的行,就不再搜索了;
range checked for each-没找到理想的索引,一次对于从前面表中来的每一个行组合;
record(index map: #)-检查使用哪个索引,并用它从表中返回行,这是使用索引最慢的一种;
using filesort-看到这个就需要优化查询了,mysql需要额外的步骤来发现如何对返回的行排序。他根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。
using index-列数据是从单单使用了索引中的信息而没有读取实际行的表返回的,这发生在对表的全部的请求列都是同一个索引时;
using temporary-看到这个就需要优化查询了,mysql需要创建一个临时表来查询存储结果,这通常发生在多不同的列表进行order by时,而不是group by;
where used-使用了where从句来限制哪些行将与下一张表匹配或是返回给用户。如不想返回表中用的全部行,并连接类型是all或index,这就会发生,也可能是查询有问题。
type的描述:system-表只有一行,这是const连接类型的特例;const-表中一个记录的最大值能够匹配这个查询(索引可以是主键或唯一索引)。因为只有一行,这个值实际就是常数,因为mysql先读这个值,再把它当作常数对待;eq_ref-从前面的表中,对每一个记录的联合都从表中读取一个记录。在查询使用索引为主键或唯一索引的全部时使用;ref-只有使用了不是主键或唯一索引的部分时发生。对于前面表的每一行联合,全部记录都将从表中读出,这个连接类型严重依赖索引匹配记录的多少-越少越好;range-使用索引返回一个范围中的行,比如使用>或<查找时发生;index-这个连接类型对前面的表中的每一个记录联合进行完全扫描(比all好,因为索引一般小于表数据);all-这个连接类型多前面的表中的每一个记录联合进行完全扫描,这个比较糟糕,应该尽量避免。
举个例子:create index sales_rep on sales(sales_rep);  // 可以比较一下创建索引前后的变化
explain select * from sales_rep left join sales on sales.sales_rep = sales_rep.employee_number;
结果如下:
table          type           possible_keys          key           key_len           ref           rows           extra
sales_rep          all          null          null          null           null           5           
sales          ref          sales_rep          sales_rep          5          sales_rep.employee_number          2          
这个结果表示sales_rep表有个不好的连接类型-all,没用到索引,要查询的行数为5;sales的连接类型为ref,可用的索引是 sales_rep,实际也使用sales_rep索引,这个索引的长度是5,对应的列是employee_number,要查询的行数为2,所以这次查询对表共进行了5×2次查询。

查看索引信息:show index from tablename;
列的描述:table-正在查看的表名;non_unique-1或1.0表示索引不能包含重复值(主键和唯一索引),1表示可以;key_name-索引名;seq_in_index-索引中列的顺序,从1开始;column_name-列名;collation-a或null,a表示索引以序升排列,null表示不排序;cardinality-索引中唯一值的个数;sub_part-如果整个列为索引,值为null,否则以字符表示索引的大小;packed-是否打包;null-如果列能包含null,则为yes;comment-各种注释。

 

大家都知道在数据库表中,使用索引可以大大提高查询速度。本文讲了这么多,也无非是想利用索引提高数据库的执行效率。不过索引只是提高效率的一个因素。如果你的MySQL有大数据的表,就需要花时间研究建立最优秀的索引或优化查询语句。

    一、索引分单列索引和组合索引

    单列索引:即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。
    组合索引:即一个索包含多个列。

    二、介绍一下索引的类型

    1.普通索引。
    这是最基本的索引,它没有任何限制。它有以下几种创建方式:
    (1)创建索引:CREATE INDEX indexName ON tableName(tableColumns(length));如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是 BLOB 和 TEXT 类型,必须指定length,下同。
    (2)修改表结构:ALTER tableName ADD INDEX [indexName] ON (tableColumns(length))
    (3)创建表的时候直接指定:CREATE TABLE tableName ( [...], INDEX [indexName] (tableColumns(length)) ;

    2.唯一索引。
    它与前面的"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
    (1)创建索引:CREATE UNIQUE INDEX indexName ON tableName(tableColumns(length))
    (2)修改表结构:ALTER tableName ADD UNIQUE [indexName] ON (tableColumns(length))
    (3)创建表的时候直接指定:CREATE TABLE tableName ( [...], UNIQUE [indexName] (tableColumns(length));

    3.主键索引
    它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引:CREATE TABLE testIndex(i_testID INT NOT NULL AUTO_INCREMENT,vc_Name VARCHAR(16) NOT NULL,PRIMARY KEY(i_testID)); 当然也可以用ALTER命令。
记住:一个表只能有一个主键。

    4.全文索引
    MySQL从3.23.23版开始支持全文索引和全文检索。这里不作讨论,删除索引的语法:DROP INDEX index_name ON tableName

三、单列索引和组合索引

    为了形象地对比两者,再建一个表:

CREATE TABLE myIndex ( i_testID INT NOT NULL AUTO_INCREMENT, vc_Name VARCHAR(50)
NOT NULL, vc_City VARCHAR(50) NOT NULL, i_Age INT NOT NULL, i_SchoolID INT NOT NULL, PRIMARY KEY (i_testID) );

    在这10000条记录里面7上8下地分布了5条vc_Name="erquan"的记录,只不过city,age,school的组合各不相同。来看这条T-SQL:

SELECT i_testID FROM myIndex WHERE vc_Name=@#erquan@# AND vc_City=@#郑州@# AND i_Age=25;

    首先考虑建单列索引:
    在vc_Name列上建立了索引。执行T-SQL时,MYSQL很快将目标锁定在了vc_Name=erquan的5条记录上,取出来放到一中间结果集。在这个结果集里,先排除掉vc_City不等于"郑州"的记录,再排除i_Age不等于25的记录,最后筛选出唯一的符合条件的记录。

    虽然在vc_Name上建立了索引,查询时MYSQL不用扫描整张表,效率有所提高,但离我们的要求还有一定的距离。同样的,在vc_City和i_Age分别建立的单列索引的效率相似。

    为了进一步榨取MySQL的效率,就要考虑建立组合索引。就是将vc_Name,vc_City,i_Age建到一个索引里:
    ALTER TABLE myIndex ADD INDEX name_city_age (vc_Name(10),vc_City,i_Age);--注意了,建表时,vc_Name长度为50,这里为什么用10呢?因为一般情况下名字的长度不会超过10,这样会加速索引查询速度,还会减少索引文件的大小,提高INSERT的更新速度。

    执行T-SQL时,MySQL无须扫描任何记录就到找到唯一的记录!!

    肯定有人要问了,如果分别在vc_Name,vc_City,i_Age上建立单列索引,让该表有3个单列索引,查询时和上述的组合索引效率一样吧?嘿嘿,大不一样,远远低于我们的组合索引~~虽然此时有了三个索引,但MySQL只能用到其中的那个它认为似乎是最有效率的单列索引。

    建立这样的组合索引,其实是相当于分别建立了

vc_Name,vc_City,i_Age vc_Name,vc_City vc_Name

    这样的三个组合索引!为什么没有vc_City,i_Age等这样的组合索引呢?这是因为mysql组合索引"最左前缀"的结果。简单的理解就是只从最左面的开始组合。并不是只要包含这三列的查询都会用到该组合索引,下面的几个T-SQL会用到:

SELECT * FROM myIndex WHREE vc_Name="erquan" AND vc_City="郑州" SELECT * FROM myIndex WHREE vc_Name="erquan"

    而下面几个则不会用到:

SELECT * FROM myIndex WHREE i_Age=20 AND vc_City="郑州" SELECT * FROM myIndex WHREE vc_City="郑州"

1

四、使用索引

    到此你应该会建立、使用索引了吧?但什么情况下需要建立索引呢?一般来说,在WHERE和JOIN中出现的列需要建立索引,但也不完全如此,因为 MySQL只对 <,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE(后面有说明)才会使用索引。
SELECT t.vc_Name FROM testIndex t LEFT JOIN myIndex m ON t.vc_Name=m.vc_Name WHERE m.i_Age=20 AND m.vc_City=@#郑州@# 时,有对myIndex表的vc_City和i_Age建立索引的需要,由于testIndex表的vc_Name开出现在了JOIN子句中,也有对它建立索引的必要。

    刚才提到了,只有某些时候的LIKE才需建立索引?是的。因为在以通配符 % 和 _ 开头作查询时,MySQL不会使用索引,如


SELECT * FROM myIndex WHERE vc_Name like@#erquan%@#

    会使用索引,而

SELECT * FROM myIndex WHEREt vc_Name like@#%erquan@#

    就不会使用索引了。

    五、索引的不足之处

    上面说了那么多索引的好话,它真的有像传说中那么优秀么?当然会有缺点了。

    1.虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件

    2.建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。

mysql 联合索引

mysql联合索引
命名规则:表名_字段名
1、需要加索引的字段,要在where条件中
2、数据量少的字段不需要加索引
3、如果where条件中是OR关系,加索引不起作用
4、符合最左原则

https://segmentfault.com/q/1010000003984016/a-1020000003984281

联合索引又叫复合索引。对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。


两个或更多个列上的索引被称作复合索引。
利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引 不同于使用两个单独的索引。复合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。如果您知 道姓,电话簿将非常有用;如果您知道姓和名,电话簿则更为有用,但如果您只知道名不姓,电话簿将没有用处。
所以说创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。

http://blog.csdn.net/lmh12506/article/details/8879916

 

 

当一个表有多条索引可走时,  Mysql  根据查询语句的成本来选择走哪条索引, 联合索引的话, 它往往计算的是第一个字段(最左边那个), 这样往往会走错索引. 如: 
索引Index_1(Create_Time, Category_ID), Index_2(Category_ID) 

如果每天的数据都特别多, 而且有很多category, 但具体每个category的记录不会很多.

当查询SQL条件为select …where create_time ….and category_id=..时, 很可能不走索引Index_1, 而走索引Index_2, 导致查询比较慢.

解决办法是将索引字段的顺序调换一下.

http://www.cnblogs.com/krisy/archive/2013/07/12/3186258.html

 

 创建索引
在执行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)

 

4.  删除索引
可利用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索引。

如果从表中删除了某列,则索引会受到影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。

 

5.查看索引

mysql> show index from tblname;

mysql> show keys from tblname;
  · Table

  表的名称。

  · Non_unique

  如果索引不能包括重复词,则为0。如果可以,则为1。

  · Key_name

  索引的名称。

  · Seq_in_index

  索引中的列序列号,从1开始。

  · Column_name

  列名称。

  · Collation

  列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分类)。

  · Cardinality

  索引中唯一值的数目的估计值。通过运行ANALYZE TABLE或myisamchk -a可以更新。基数根据被存储为整数的统计数据来计数,所以即使对于小型表,该值也没有必要是精确的。基数越大,当进行联合时,MySQL使用该索引的机会就越大。

  · Sub_part

  如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL。

  · Packed

  指示关键字如何被压缩。如果没有被压缩,则为NULL。

  · Null

  如果列含有NULL,则含有YES。如果没有,则该列含有NO。

  · Index_type

  用过的索引方法(BTREE, FULLTEXT, HASH, RTREE)。

  · Comment

6.什么情况下使用索引
       表的主关键字

自动建立唯一索引

如zl_yhjbqk(用户基本情况)中的hbs_bh(户标识编号)

表的字段唯一约束

ORACLE利用索引来保证数据的完整性

如lc_hj(流程环节)中的lc_bh+hj_sx(流程编号+环节顺序)

直接条件查询的字段

在SQL中用于条件约束的字段

如zl_yhjbqk(用户基本情况)中的qc_bh(区册编号)

select * from zl_yhjbqk where qc_bh=’<????甼曀???>7001’

查询中与其它表关联的字段

字段常常建立了外键关系

如zl_ydcf(用电成份)中的jldb_bh(计量点表编号)

select * from zl_ydcf a,zl_yhdb b where a.jldb_bh=b.jldb_bh and b.jldb_bh=’540100214511’

查询中排序的字段

排序的字段如果通过索引去访问那将大大提高排序速度

select * from zl_yhjbqk order by qc_bh(建立qc_bh索引)

select * from zl_yhjbqk where qc_bh=’7001’ order by cb_sx(建立qc_bh+cb_sx索引,注:只是一个索引,其中包括qc_bh和cb_sx字段)

查询中统计或分组统计的字段

select max(hbs_bh) from zl_yhjbqk

select qc_bh,count(*) from zl_yhjbqk group by qc_bh

什么情况下应不建或少建索引

表记录太少

如果一个表只有5条记录,采用索引去访问记录的话,那首先需访问索引表,再通过索引表访问数据表,一般索引表与数据表不在同一个数据块,这种情况下ORACLE至少要往返读取数据块两次。而不用索引的情况下ORACLE会将所有的数据一次读出,处理速度显然会比用索引快。

如表zl_sybm(使用部门)一般只有几条记录,除了主关键字外对任何一个字段建索引都不会产生性能优化,实际上如果对这个表进行了统计分析后ORACLE也不会用你建的索引,而是自动执行全表访问。如:

select * from zl_sybm where sydw_bh=’5401’(对sydw_bh建立索引不会产生性能优化)

经常插入、删除、修改的表

对一些经常处理的业务表应在查询允许的情况下尽量减少索引,如zl_yhbm,gc_dfss,gc_dfys,gc_fpdy等业务表。

数据重复且分布平均的表字段

假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。

经常和主字段一块查询但主字段索引值比较多的表字段

如gc_dfss(电费实收)表经常按收费序号、户标识编号、抄表日期、电费发生年月、操作 标志来具体查询某一笔收款的情况,如果将所有的字段都建在一个索引里那将会增加数据的修改、插入、删除时间,从实际上分析一笔收款如果按收费序号索引就已 经将记录减少到只有几条,如果再按后面的几个字段索引查询将对性能不产生太大的影响。

对千万级MySQL数据库建立索引的事项及提高性能的手段

一、注意事项:

首先,应当考虑表空间和磁盘空间是否足够。我们知道索引也是一种数据,在建立索引的时候势必也会占用大量表空间。因此在对一大表建立索引的时候首先应当考虑的是空间容量问题。

其次,在对建立索引的时候要对表进行加锁,因此应当注意操作在业务空闲的时候进行。

二、性能调整方面:

首当其冲的考虑因素便是磁盘I/O。物理上,应当尽量把索引与数据分散到不同的磁盘上(不考虑阵列的情况)。逻辑上,数据表空间与索引表空间分开。这是在建索引时应当遵守的基本准则。

其次,我们知道,在建立索引的时候要对表进行全表的扫描工作,因此,应当考虑调大初始化参数db_file_multiblock_read_count的值。一般设置为32或更大。

再次,建立索引除了要进行全表扫描外同时还要对数据进行大量的排序操作,因此,应当调整排序区的大小。

    9i之前,可以在session级别上加大sort_area_size的大小,比如设置为100m或者更大。

    9i以后,如果初始化参数workarea_size_policy的值为TRUE,则排序区从pga_aggregate_target里自动分配获得。

最后,建立索引的时候,可以加上nologging选项。以减少在建立索引过程中产生的大量redo,从而提高执行的速度。

 

MySql在建立索引优化时需要注意的问题

 

设计好MySql的索引可以让你的数据库飞起来,大大的提高数据库效率。设计MySql索引的时候有一下几点注意:

1,创建索引

对于查询占主要的应用来说,索引显得尤为重要。很多时候性能问题很简单的就是因为我们忘了添加索引而造成的,或者说没有添加更为有效的索引导致。如果不加

索引的话,那么查找任何哪怕只是一条特定的数据都会进行一次全表扫描,如果一张表的数据量很大而符合条件的结果又很少,那么不加索引会引起致命的性能下降。
但是也不是什么情况都非得建索引不可,比如性别可能就只有两个值,建索引不仅没什么优势,还会影响到更新速度,这被称为过度索引。

2,复合索引

比如有一条语句是这样的:select * from users where area=’beijing’ and age=22;

如果我们是在area和age上分别创建单个索引的话,由于mysql查询每次只能使用一个索引,所以虽然这样已经相对不做索引时全表扫描提高了很多效

率,但是如果在area、age两列上创建复合索引的话将带来更高的效率。如果我们创建了(area, age,salary)的复合索引,那么其实相当于创建了(area,age,salary)、(area,age)、(area)三个索引,这被称为最佳左前缀特性。
因此我们在创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减。

3,索引不会包含有NULL值的列

只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。

4,使用短索引

对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的 列,如果在前10 个或20 个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

5,排序的索引问题

mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

6,like语句操作

一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。

7,不要在列上进行运算

select * from users where

YEAR(adddate)

8,不使用NOT IN和操作

NOT IN和操作都不会使用索引将进行全表扫描。NOT IN可以NOT EXISTS代替,id3则可使用id>3 or id

http://www.cnblogs.com/alazalazalaz/p/4083696.html

MySQL分区和分表

一、概念
1.为什么要分表和分区?
日常开发中我们经常会遇到大表的情况,所谓的大表是指存储了百万级乃至千万级条记录的表。这样的表过于庞大,导致数据库在查询和插入的时候耗时太长,性能低下,如果涉及联合查询的情况,性能会更加糟糕。分表和表分区的目的就是减少数据库的负担,提高数据库的效率,通常点来讲就是提高表的增删改查效率。
2.什么是分表?
分表是将一个大表按照一定的规则分解成多张具有独立存储空间的实体表,我们可以称为子表,每个表都对应三个文件,MYD数据文件,.MYI索引文件,.frm表结构文件。这些子表可以分布在同一块磁盘上,也可以在不同的机器上。app读写的时候根据事先定义好的规则得到对应的子表名,然后去操作它。
3.什么是分区?
分区和分表相似,都是按照规则分解表。不同在于分表将大表分解为若干个独立的实体表,而分区是将数据分段划分在多个位置存放,可以是同一块磁盘也可以在不同的机器。分区后,表面上还是一张表,但数据散列到多个位置了。app读写的时候操作的还是大表名字,db自动去组织分区的数据。
4.mysql分表和分区有什么联系呢?
(1)都能提高mysql的性高,在高并发状态下都有一个良好的表现。
(2)分表和分区不矛盾,可以相互配合的,对于那些大访问量,并且表数据比较多的表,我们可以采取分表和分区结合的方式(如果merge这种分表方式,不能和分区配合的话,可以用其他的分表试),访问量不大,但是表数据很多的表,我们可以采取分区的方式等。
(3)分表技术是比较麻烦的,需要手动去创建子表,app服务端读写时候需要计算子表名。采用merge好一些,但也要创建子表和配置子表间的union关系。
(4)表分区相对于分表,操作方便,不需要创建子表。

二、分区
1.分区的类型:
(1)Range:把连续区间按范围划分
例:

复制代码
create table user(
    id int(11),
    money int(11) unsigned not null,
    date datetime
)
partition by range(YEAR(date))(
    partition p2014 values less than (2015),
    partition p2015 values less than (2016),
    partition p2016 values less than (2017),
    partition p2017 values less than maxvalue
);
复制代码
(2)List:把离散值分成集合,按集合划分,适合有固定取值列的表
例:

复制代码
create table user(
    a int(11),
    b int(11)
)
partition by list(b)(
    partition p0 values in (1,3,5,7,9),
    partition p1 values in (2,4,6,8,0)
);
复制代码
(3)Hash:随机分配,分区数固定
例:

复制代码
create table user(
    a int(11),
    b datetime
)
partition by hash(YEAR(b))
partitions 4;
复制代码
(4)Key:类似Hash,区别是只支持1列或多列,且mysql提供自身的Hash函数
例:

复制代码
create table user(
    a int(11),
    b datetime
)
partition by key(b)
partitions 4;
复制代码
2.分区管理
(1)新增分区

ALTER TABLE sale_data
ADD PARTITION (PARTITION p201710 VALUES LESS THAN (201711));
(2)删除分区

--当删除了一个分区,也同时删除了该分区中所有的数据。
ALTER TABLE sale_data DROP PARTITION p201710;
(3)分区的合并
下面的SQL,将p201701 - p201709 合并为3个分区p2017Q1 - p2017Q3

复制代码
ALTER TABLE sale_data
REORGANIZE PARTITION p201701,p201702,p201703,
p201704,p201705,p201706,
p201707,p201708,p201709 INTO
(
    PARTITION p2017Q1 VALUES LESS THAN (201704),
    PARTITION p2017Q2 VALUES LESS THAN (201707),
    PARTITION p2017Q3 VALUES LESS THAN (201710)
);
复制代码
3.分区应该注意的事项:
(1)做分区时,要么不定义主键,要么把分区字段加入到主键中。
(2)分区字段不能为NULL,要不然怎么确定分区范围呢,所以尽量NOT NULL

三、分表
1.垂直分表
把原来有很多列的表拆分成多个表,原则是:
(1)把常用、不常用的字段分开放
(2)把大字段独立存放在一个表中
2.水平分表
为了解决单表数据量过大的问题,每个水平拆分表的结构完全一致。
例:
(1)按时间结构
如果业务系统对时效性较高,比如新闻发布系统的文章表,可以把数据库设计成时间结构,按时间分有几种结构:
(a)平板式
表类似:

article_201701
article_201702
article_201703
用年来分还是用月可自定,但用日期的话表就太多了,也没这必要。一般建议是按月分就可以。
这种分法,其难处在于,假设我要列20条数据,结果这三张表里都有2条,那么业务上很有可能要求读三次表。如果时间长了,有几十张表,而每张表是0条,那不就是要读完整个系统的表才行么?另外这个结构,要作分页是比较难实现的。
主键:在这个系统中,主键是13位带毫秒的时间戳,不要用自动编号,否则难以通过主键定位到表,也可以在查询时带上时间,但比较烦琐。
(b)归档式
表类似:

article_old
article_new
为了解决平板式的缺点,可以采用时间归档式设计,可以看到这个系统只有两张表。一张是旧文章表,一张是新文章表,新文章表放2个月的信息,每天定期把2
个月中的最早一天的文章归入旧表中。这样一方面可以解决性能问题,因为一般新闻发布系统读取的都是新的内容,旧的内容读取少;第二可以委婉地解决功能问
题,比如平板式所说的问题,在归档式中最多也只需要读2张表就完成了。
归档式的缺点在于旧表容量还是相对比较大,如果业务允许,可对旧表中的超旧内容进行再归档或直接清理掉。
(2)按版块结构
如果按照文章的所属版块进行拆表,比如新闻、体育版块拆表,一方面可以使每个表数据量分离,另一方面是各版块之间相互影响可降到最低。假如新闻版块的数据表损坏或需要维护,并不会影响到体育版块的正常工作,从而降低了风险。版块结构同时常用于bbs这样的系统。
板块结构也有几种分法:
(a)对应式
对于版块数量不多,而且较为固定的形式,就直接对应就好。比如新闻版块,可以分出新闻的目录表,新闻的文章表等。

news_category
news_article
sports_category
sports_article
可看到每一个版块都对应着一组相同的表结构,好处就是一目了然。在功能上,因为版块之间还是有一些隔阂,所以需要联合查询的需求不多,开发上比时间结构的方式要轻松。
主键:依旧要考虑的,在这个系统中,主键是版块+时间戳,单纯的时间戳或自动编号也能用,查询时要记得带上版块用于定位表。
(b)冷热式
对应式的缺点是,如果版块数量很大而且不确定,那要分出的表数量就太多了。举个例子:百度贴吧,如果按一个词条一个表设计,那得有多少张表呢?
用这样的方式吧。

tieba_汽车
tieba_飞机
tieba_火箭
tieba_unite
这个表汽车、火箭表是属于热门表,定义为新建的版块放在unite表里面,待到其超过一万张主贴的时候才开对应表结构。因为在贴吧这种系统中,冷门版块
肯定比热门版块多得多,这些冷门版块通常只有几张帖子,为它们开表也太浪费了;同时热门版块数量和访问量等,又比冷门版块多得多,非常有特点。
unite表还可以扩展成哈希表,利用词条的md5编码,可以分成n张表,我算了一下,md5前一位可分36张表,两位即是1296张表,足够了。

tieba_unite_ab
tieba_unite_ac
(3)按哈希结构
哈希结构通常用于博客之类的基于用户的场合,在博客这样的系统里有几个特点,1是用户数量非常多,2是每个用户发的文章数量都较少,3是用户发文章不定
期,4是每个用户发得不多,但总量仍非常之大。基于这些特点,用以上所说的任何一种分表方式都不合适,一没有固定的时效不宜用时间拆,二用户很多,而且还
偏偏都是冷门,所以也不宜用版块(用户)拆。
哈希结构在上面有所提及,既然按每个用户不好直接拆,那就把一群用户归进一个表好了。

blog_aa
blog_ab
blog_ac
如上所说,md5取前两位哈希可以达到1296张表,如果觉得不够,那就再加一位,总数可达46656张表,还不够?
表的数量太多,要创建这些表也是挺麻烦的,可以考虑在程序里往数据库insert之前,多执行一句判断表存在与否并创建表的语句,很实用,消耗也并不很大。
主键:依旧要考虑的,在这个系统中,主键是用户ID+时间戳,单纯的时间戳或自动编号也能用,但查询时要记得带上用户名用于定位表。

MySQL各存储引擎 

MySQL中的数据用各种不同的技术存储在文件(或者内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术,你能够获得额外的速度或者功能,从而改善你的应用的整体功能。这些不同的技术以及配套的相关功能在MySQL中被称作存储引擎(也称作表类型)。MySQL默认配置了许多不同的存储引擎,可以预先设置或者在MySQL服务器中启用。你可以选择适用于服务器、数据库和表格的存储引擎,以便在选择如何存储你的信息、如何检索这些信息以及你需要你的数据结合什么性能和功能的时候为你提供最大的灵活性。
使用以下命令可以查看MySQL支持的引擎:

mysql> show engines;
一、MyISAM存储引擎
默认的存储引擎,提供高速存储和检索,以及全文搜索能力。
不支持事务。表级锁。不能在表损坏后恢复数据。
每个表会生成三个文件(文件名就是表名):
.frm 表结构;
.MYD 数据;
.MYI 索引。
适合在以下几种情况下使用:
1.做很多count的计算
2.查询非常频繁

二、InnoDB存储引擎
具有提交、回滚和崩溃恢复能力的事务安全(ACID兼容)存储引擎。
基于聚簇索引建立,聚簇索引对主键查询有很高的性能。不过它的二级索引(secondary index,非主键索引)中必须包含主键列,所以如果主键列很大的话,其他的所有索引都会很大。因此表上的索引较多的话,主键应当尽可能的小。
支持事务和外键。行级锁。
适合在以下几种情况下使用:
1.更新和查询都相当的频繁,多重并发
2.要求事务,或者可靠性要求比较高
3.外键约束,MySQL支持外键的存储引擎只有InnoDB
一般来说,如果需要事务支持,并且有较高的并发读取频率,InnoDB是不错的选择。

三、MEMORY(HEAP)引擎
数据保存在内存中,拥有极高的插入、更新和查询效率。但是不稳定,重启以后数据都会丢失。
不支持事务。支持表级锁,因此并发写入的性能较低。
支持长度不变的数据类型,不支持BLOB或TEXT长度可变的数据类型。VARCHAR是一种长度可变的类型,但因为它在MySQL内部当做长度固定不变的CHAR类型,所以可以使用。
每个表会生成一个.frm文件,该文件只存储表的结构。
支持HASH索引和B-Tree索引,擎默认使用HASH索引。B-Tree索引的优于HASH索引的是,可以使用部分查询和通配查询,也可以使用<、>和>=等操作符方便数据挖掘。HASH索引进行“相等比较”非常快,但是对“范围比较”的速度就慢多了,因此HASH索引值适合使用在=和<>的操作符中,不适合在<或>操作符中,也同样不适合用在order by子句中。
在内存中存放数据,所以会造成内存的使用,可以通过参数max_heap_table_size控制MEMORY表的大小。

四、ARCHIVE引擎
拥有很好的压缩机制,它使用zlib压缩库,在记录被请求时会实时压缩。
支持最基本的插入和查询两种功能。在MySQL 5.5开始支持索引。
不支持事务。支持行级锁和专用的缓存区,所以可以实现高并发的插入。
适合存储大量日志、历史数据。

五、BLACKHOLE引擎
接受但不存储数据,但是如果MySQL启用了二进制日志,SQL语句被写入日志(并被复制到从服务器)。
用于做日志记录或同步归档的中继存储。但这种应用方式会碰到很多问题,因此并不推荐。
支持事务,而且支持mvcc的行级锁。

六、CSV引擎
每个表会生成一个.CSV文件,将CSV类型的文件当做表进行处理。
把数据以逗号分隔的格式存储在文本文件中,这种文件是一种普通文本文件,每个数据行占用一个文本行。
不支持索引,即使用该种类型的表没有主键列,也不允许表中的字段为null。

MySQL慢查询(一) - 开启慢查询

一、简介
开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。

二、参数说明
slow_query_log 慢查询开启状态
slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录)
long_query_time 查询超过多少秒才记录

三、设置步骤
1.查看慢查询相关参数

复制代码
mysql> show variables like 'slow_query%';
+---------------------------+----------------------------------+
| Variable_name             | Value                            |
+---------------------------+----------------------------------+
| slow_query_log            | OFF                              |
| slow_query_log_file       | /mysql/data/localhost-slow.log   |
+---------------------------+----------------------------------+

mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+

2.设置方法
方法一:全局变量设置
将 slow_query_log 全局变量设置为“ON”状态

mysql> set global slow_query_log='ON'; 
设置慢查询日志存放的位置

mysql> set global slow_query_log_file='/usr/local/mysql/data/slow.log';
查询超过1秒就记录

mysql> set global long_query_time=1;
方法二:配置文件设置
修改配置文件my.cnf,在[mysqld]下的下方加入

[mysqld]
slow_query_log = ON
slow_query_log_file = /usr/local/mysql/data/slow.log
long_query_time = 1
3.重启MySQL服务

service mysqld restart
4.查看设置后的参数

复制代码
mysql> show variables like 'slow_query%';
+---------------------+--------------------------------+
| Variable_name       | Value                          |
+---------------------+--------------------------------+
| slow_query_log      | ON                             |
| slow_query_log_file | /usr/local/mysql/data/slow.log |
+---------------------+--------------------------------+

mysql> show variables like 'long_query_time';
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| long_query_time | 1.000000 |
+-----------------+----------+
复制代码
四、测试
1.执行一条慢查询SQL语句

mysql> select sleep(2);
2.查看是否生成慢查询日志

ls /usr/local/mysql/data/slow.log
如果日志存在,MySQL开启慢查询设置成功!

MySQL慢查询(二) - pt-query-digest详解慢查询日志 

一、简介
pt-query-digest是用于分析mysql慢查询的一个工具,它可以分析binlog、General log、slowlog,也可以通过SHOWPROCESSLIST或者通过tcpdump抓取的MySQL协议数据来进行分析。
可以把分析结果输出到文件中,分析过程是先对查询语句的条件进行参数化,然后对参数化以后的查询进行分组统计,统计出各查询的执行时间、次数、占比等,
可以借助分析结果找出问题进行优化。

二、安装pt-query-digest
1.下载页面:https://www.percona.com/doc/percona-toolkit/2.2/installation.html
2.perl的模块

yum install -y perl-CPAN perl-Time-HiRes
3.安装步骤
方法一:rpm安装

cd /usr/local/src
wget percona.com/get/percona-toolkit.rpm
yum install -y percona-toolkit.rpm
工具安装目录在:/usr/bin

方法二:源码安装

复制代码
cd /usr/local/src
wget percona.com/get/percona-toolkit.tar.gz
tar zxf percona-toolkit.tar.gz
cd percona-toolkit-2.2.19
perl Makefile.PL PREFIX=/usr/local/percona-toolkit
make && make install
复制代码
工具安装目录在:/usr/local/percona-toolkit/bin

4.各工具用法简介(详细内容:https://www.percona.com/doc/percona-toolkit/2.2/index.html)
(1)慢查询日志分析统计

pt-query-digest /usr/local/mysql/data/slow.log
(2)服务器摘要

pt-summary 
(3)服务器磁盘监测

pt-diskstats 
(4)mysql服务状态摘要

pt-mysql-summary -- --user=root --password=root 
三、pt-query-digest语法及重要选项

复制代码
pt-query-digest [OPTIONS] [FILES] [DSN]
--create-review-table  当使用--review参数把分析结果输出到表中时,如果没有表就自动创建。
--create-history-table  当使用--history参数把分析结果输出到表中时,如果没有表就自动创建。
--filter  对输入的慢查询按指定的字符串进行匹配过滤后再进行分析
--limit    限制输出结果百分比或数量,默认值是20,即将最慢的20条语句输出,如果是50%则按总响应时间占比从大到小排序,输出到总和达到50%位置截止。
--host  mysql服务器地址
--user  mysql用户名
--password  mysql用户密码
--history 将分析结果保存到表中,分析结果比较详细,下次再使用--history时,如果存在相同的语句,且查询所在的时间区间和历史表中的不同,则会记录到数据表中,可以通过查询同一CHECKSUM来比较某类型查询的历史变化。
--review 将分析结果保存到表中,这个分析只是对查询条件进行参数化,一个类型的查询一条记录,比较简单。当下次使用--review时,如果存在相同的语句分析,就不会记录到数据表中。
--output 分析结果输出类型,值可以是report(标准分析报告)、slowlog(Mysql slow log)、json、json-anon,一般使用report,以便于阅读。
--since 从什么时间开始分析,值为字符串,可以是指定的某个”yyyy-mm-dd [hh:mm:ss]”格式的时间点,也可以是简单的一个时间值:s(秒)、h(小时)、m(分钟)、d(天),如12h就表示从12小时前开始统计。
--until 截止时间,配合—since可以分析一段时间内的慢查询。
复制代码
四、分析pt-query-digest输出结果
第一部分:总体统计结果
Overall:总共有多少条查询
Time range:查询执行的时间范围
unique:唯一查询数量,即对查询条件进行参数化以后,总共有多少个不同的查询
total:总计   min:最小   max:最大  avg:平均
95%:把所有值从小到大排列,位置位于95%的那个数,这个数一般最具有参考价值
median:中位数,把所有值从小到大排列,位置位于中间那个数

复制代码
# 该工具执行日志分析的用户时间,系统时间,物理内存占用大小,虚拟内存占用大小
# 340ms user time, 140ms system time, 23.99M rss, 203.11M vsz
# 工具执行时间
# Current date: Fri Nov 25 02:37:18 2016
# 运行分析工具的主机名
# Hostname: localhost.localdomain
# 被分析的文件名
# Files: slow.log
# 语句总数量,唯一的语句数量,QPS,并发数
# Overall: 2 total, 2 unique, 0.01 QPS, 0.01x concurrency ________________
# 日志记录的时间范围
# Time range: 2016-11-22 06:06:18 to 06:11:40
# 属性               总计      最小    最大    平均    95%  标准    中等
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# 语句执行时间
# Exec time             3s   640ms      2s      1s      2s   999ms      1s
# 锁占用时间
# Lock time            1ms       0     1ms   723us     1ms     1ms   723us
# 发送到客户端的行数
# Rows sent              5       1       4    2.50       4    2.12    2.50
# select语句扫描行数
# Rows examine     186.17k       0 186.17k  93.09k 186.17k 131.64k  93.09k
# 查询的字符数
# Query size           455      15     440  227.50     440  300.52  227.50
复制代码
第二部分:查询分组统计结果
Rank:所有语句的排名,默认按查询时间降序排列,通过--order-by指定
Query ID:语句的ID,(去掉多余空格和文本字符,计算hash值)
Response:总的响应时间
time:该查询在本次分析中总的时间占比
calls:执行次数,即本次分析总共有多少条这种类型的查询语句
R/Call:平均每次执行的响应时间
V/M:响应时间Variance-to-mean的比率
Item:查询对象

# Profile
# Rank Query ID           Response time Calls R/Call V/M   Item
# ==== ================== ============= ===== ====== ===== ===============
#    1 0xF9A57DD5A41825CA  2.0529 76.2%     1 2.0529  0.00 SELECT
#    2 0x4194D8F83F4F9365  0.6401 23.8%     1 0.6401  0.00 SELECT wx_member_base
第三部分:每一种查询的详细统计结果
由下面查询的详细统计结果,最上面的表格列出了执行次数、最大、最小、平均、95%等各项目的统计。
ID:查询的ID号,和上图的Query ID对应
Databases:数据库名
Users:各个用户执行的次数(占比)
Query_time distribution :查询时间分布, 长短体现区间占比,本例中1s-10s之间查询数量是10s以上的两倍。
Tables:查询中涉及到的表
Explain:SQL语句

复制代码
# Query 1: 0 QPS, 0x concurrency, ID 0xF9A57DD5A41825CA at byte 802 ______
# This item is included in the report because it matches --limit.
# Scores: V/M = 0.00
# Time range: all events occurred at 2016-11-22 06:11:40
# Attribute    pct   total     min     max     avg     95%  stddev  median
# ============ === ======= ======= ======= ======= ======= ======= =======
# Count         50       1
# Exec time     76      2s      2s      2s      2s      2s       0      2s
# Lock time      0       0       0       0       0       0       0       0
# Rows sent     20       1       1       1       1       1       0       1
# Rows examine   0       0       0       0       0       0       0       0
# Query size     3      15      15      15      15      15       0      15
# String:
# Databases    test
# Hosts        192.168.8.1
# Users        mysql
# Query_time distribution
#   1us
#  10us
# 100us
#   1ms
#  10ms
# 100ms
#    1s  ################################################################
#  10s+
# EXPLAIN /*!50100 PARTITIONS*/
select sleep(2)\G
复制代码
五、用法示例
1.直接分析慢查询文件:

pt-query-digest  slow.log > slow_report.log
2.分析最近12小时内的查询:

pt-query-digest  --since=12h  slow.log > slow_report2.log
3.分析指定时间范围内的查询:

pt-query-digest slow.log --since '2017-01-07 09:30:00' --until '2017-01-07 10:00:00'> > slow_report3.log
4.分析指含有select语句的慢查询

pt-query-digest --filter '$event->{fingerprint} =~ m/^select/i' slow.log> slow_report4.log
5.针对某个用户的慢查询

pt-query-digest --filter '($event->{user} || "") =~ m/^root/i' slow.log> slow_report5.log
6.查询所有所有的全表扫描或full join的慢查询

pt-query-digest --filter '(($event->{Full_scan} || "") eq "yes") ||(($event->{Full_join} || "") eq "yes")' slow.log> slow_report6.log
7.把查询保存到query_review表

pt-query-digest --user=root –password=abc123 --review  h=localhost,D=test,t=query_review--create-review-table  slow.log
8.把查询保存到query_history表

pt-query-digest  --user=root –password=abc123 --review  h=localhost,D=test,t=query_history--create-review-table  slow.log_0001
pt-query-digest  --user=root –password=abc123 --review  h=localhost,D=test,t=query_history--create-review-table  slow.log_0002
9.通过tcpdump抓取mysql的tcp协议数据,然后再分析

tcpdump -s 65535 -x -nn -q -tttt -i any -c 1000 port 3306 > mysql.tcp.txt
pt-query-digest --type tcpdump mysql.tcp.txt> slow_report9.log
10.分析binlog

mysqlbinlog mysql-bin.000093 > mysql-bin000093.sql
pt-query-digest  --type=binlog  mysql-bin000093.sql > slow_report10.log
11.分析general log

pt-query-digest  --type=genlog  localhost.log > slow_report11.log

MySQL配置优化 

一、全局配置
(1)max_connections
最大连接数。默认值是151,最多2000。如果服务器的并发连接请求量比较大,建议调高此值,以增加并行连接数量。但是如果连接数越多,介于MySQL会为每个连接提供连接缓冲区,就会开销越多的内存,所以要适当调整该值。
查看最大连接数

mysql> SHOW VARIABLES LIKE 'max_connections';
查看响应的连接数

mysql> SHOW STATUS LIKE 'max%connections';
max_used_connections / max_connections * 100% (理想值≈85%) 
如果max_used_connections跟max_connections相同 那么就是max_connections设置过低或者超过服务器负载上限了,低于10%则设置过大。
(2)back_log
MySQL能暂存的连接数量,默认值是80,最多512,可设置为128。如果MySQL的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log。如果等待连接的数量超过back_log,将不被授予连接资源。当主要MySQL线程在一个很短时间内得到非常多的连接请求,这就起作用。
(3)key_buffer_size
索引缓冲区的大小,它决定索引处理的速度,尤其是索引读的速度。
通过检查状态值Key_read_requests和Key_reads,可以知道key_buffer_size设置是否合理。

复制代码
mysql> SHOW STATUS LIKE 'key_read%';
+-------------------+----------+
| Variable_name     | Value    |
+-------------------+----------+
| Key_read_requests | 90585564 |
| Key_reads         | 97031    |
+-------------------+----------+
复制代码
计算索引未命中缓存的概率:
key_cache_miss_rate = Key_reads / Key_read_requests * 100%,设置在1/1000左右较好
key_buffer_size只对MyISAM表起作用。即使你不使用MyISAM表,但是内部的临时磁盘表是MyISAM表,也要使用该值。
默认配置数值是8388608(8M),主机有4GB内存,可改为268435456(256M)
(4)query_cache_size
使用查询缓存(query cache),MySQL将查询结果存放在缓冲区中,今后对于同样的SELECT语句(区分大小写),将直接从缓冲区中读取结果。
最佳选项是将其从一开始就停用,设为0(现在MySQL 5.6的默认值)并利用其他方法加速查询:优化索引、增加拷贝分散负载或者启用额外的缓存(比如Redis或Memcached)。
通过检查状态值qcache_*,可以知道query_cache_size设置是否合理

复制代码
mysql> SHOW STATUS LIKE 'qcache%';
+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| Qcache_free_blocks      | 1        |
| Qcache_free_memory      | 1031360  |
| Qcache_hits             | 0        |
| Qcache_inserts          | 0        |
| Qcache_lowmem_prunes    | 0        |
| Qcache_not_cached       | 10302865 |
| Qcache_queries_in_cache | 0        |
| Qcache_total_blocks     | 1        |
+-------------------------+----------+
复制代码
查询缓存碎片率 = Qcache_free_blocks / Qcache_total_blocks * 100%
如果查询缓存碎片率超过20%,可以用FLUSH QUERY CACHE整理缓存碎片,或者试试减小query_cache_min_res_unit,如果你的查询都是小数据量的话。
查询缓存利用率 = (query_cache_size – Qcache_free_memory) / query_cache_size * 100%
查询缓存利用率在25%以下的话说明query_cache_size设置的过大,可适当减小;查询缓存利用率在80%以上而且Qcache_lowmem_prunes > 50的话说明query_cache_size可能有点小,要不就是碎片太多。
查询缓存命中率 = (Qcache_hits – Qcache_inserts) / Qcache_hits * 100%
如果Qcache_lowmem_prunes的值非常大,则表明经常出现缓冲不够的情况,如果Qcache_hits的值也非常大,则表明查询缓冲使用非常频繁,此时需要增加缓冲大小;如果Qcache_hits的值不大,则表明你的查询重复率很低,这种情况下使用查询缓冲反而会影响效率,那么可以考虑不用查询缓冲。此外,在SELECT语句中加入SQL_NO_CACHE可以明确表示不使用查询缓冲。
与查询缓冲有关的参数还有query_cache_type、query_cache_limit、query_cache_min_res_unit。
query_cache_type指定是否使用查询缓冲,可以设置为0、1、2,该变量是SESSION级的变量。
query_cache_limit指定单个查询能够使用的缓冲区大小,缺省为1M。
query_cache_min_res_unit指定分配缓冲区空间的最小单位,缺省为4K。检查状态值Qcache_free_blocks,如果该值非常大,则表明缓冲区中碎片很多,这就表明查询结果都比较小,此时需要减小query_cache_min_res_unit。
(5)read_buffer_size
是MySQL读入缓冲区的大小,将对表进行顺序扫描的请求将分配一个读入缓冲区,MySQL会为它分配一段内存缓冲区,read_buffer_size变量控制这一缓冲区的大小,如果对表的顺序扫描非常频繁,并你认为频繁扫描进行的太慢,可以通过增加该变量值以及内存缓冲区大小提高其性能。
默认数值是131072(128K),可改为16773120(16M)
(6)read_rnd_buffer_size
随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySQL会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySQL会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。
默认数值是262144(256K),可改为16777208(16M)
(7)sort_buffer_size
每个需要进行排序的线程分配该大小的一个缓冲区。增加这值加速ORDER BY或GROUP BY操作。
默认数值是10485760(1M),可改为16777208(16M)
(8)join_buffer_size
联合查询操作所能使用的缓冲区大小
read_buffer_size,read_rnd_buffer_size,sort_buffer_size,join_buffer_size为每个线程独占,也就是说,如果有100个线程连接,则占用为16M*100
(9)table_open_cache
表高速缓存的大小。每当MySQL访问一个表时,如果在表缓冲区中还有空间,该表就被打开并放入其中,这样可以更快地访问表内容。
通过检查峰值时间的状态值Open_tables和Opened_tables,可以决定是否需要增加table_cache的值。

复制代码
mysql> SHOW STATUS LIKE 'open%tables';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Open_tables   | 2000  |
| Opened_tables | 0     |
+---------------+-------+
复制代码
如果open_tables等于table_cache,并且opened_tables在不断增长,那么就需要增加table_cache的值了。注意,不能盲目地把table_cache设置成很大的值。如果设置得太高,可能会造成文件描述符不足,从而造成性能不稳定或者连接失败。
1G内存机器,推荐值是128-256。内存在4GB左右的服务器该参数可设置为256M或384M。
(10)max_heap_table_size
用户可以创建的内存表(memory table)的大小。这个值用来计算内存表的最大行数值。
这个变量和tmp_table_size一起限制了内部内存表的大小。如果某个内部heap(堆积)表大小超过tmp_table_size,MySQL可以根据需要自动将内存中的heap表改为基于硬盘的MyISAM表。
(11)tmp_table_size
临时表的大小,例如做高级GROUP BY操作生成的临时表。如果调高该值,MySQL同时将增加heap表的大小,可达到提高联接查询速度的效果,建议尽量优化查询,要确保查询过程中生成的临时表在内存中,避免临时表过大导致生成基于硬盘的MyISAM表。

复制代码
mysql> SHOW GLOBAL STATUS LIKE 'created_tmp%';
+-------------------------+----------+
| Variable_name           | Value    |
+-------------------------+----------+
| Created_tmp_disk_tables | 2884297  |
| Created_tmp_files       | 870      |
| Created_tmp_tables      | 15899696 |
+-------------------------+----------+
复制代码
每次创建临时表,Created_tmp_tables增加,如果临时表大小超过tmp_table_size,则是在磁盘上创建临时表,Created_tmp_disk_tables也增加。
Created_tmp_files表示MySQL服务创建的临时文件文件数,比较理想的配置是:
Created_tmp_disk_tables / Created_tmp_tables * 100% <= 25%
(12)thread_cache_size
线程缓存。当客户端断开之后,服务器处理此客户的线程将会缓存起来以响应下一个客户而不是销毁(前提是缓存数未达上限)。

复制代码
mysql> SHOW STATUS LIKE 'threads%';
+-------------------+---------+
| Variable_name     | Value   |
+-------------------+---------+
| Threads_cached    | 5       |
| Threads_connected | 13      |
| Threads_created   | 1095313 |
| Threads_running   | 1       |
+-------------------+---------+
复制代码
Threads_cached :代表当前此时此刻线程缓存中有多少空闲线程。如果过大,表明MySQL服务器一直在创建线程,这也是比较耗资源,可以适当增加thread_cache_size
Threads_connected :代表当前已建立连接的数量,因为一个连接就需要一个线程,所以也可以看成当前被使用的线程数。
Threads_created :代表从最近一次服务启动,已创建线程的数量。
Threads_running :代表当前激活的(非睡眠状态)线程数。并不是代表正在使用的线程数,有时候连接已建立,但是连接处于sleep状态,这里相对应的线程也是sleep状态。
建议设置接近Threads_connected值,再结合物理内存:1G-8;2G-16;3G-32 综合考虑一下值。
(13)interactive_timeout
一个交互连接在被服务器在关闭前等待行动的秒数。默认值是28800(8小时),可设置为7200。
(14)wait_timeout
一个非交互连接在被服务器在关闭前等待行动的秒数。要同时设置interactive_timeout和wait_timeout才会生效。

二、InnoDB配置
(1)innodb_buffer_pool_size
缓冲池的大小,缓存数据和索引,对InnoDB整体性能影响较大,相当于MyISAM的key_buffer_size。如果只用Innodb,可以把这个值设为内存的70%-80%。越大越好,这能保证你在大多数的读取操作时使用的是内存而不是硬盘。
(2)innodb_log_buffer_size  
尚未执行的事务的缓存大小,默认值为8M,一般8M-16M。如果你有很多事务的更新,插入或删除操作,通过这个参数会大量的节省了磁盘I/O。但是如果你的事务中包含有二进制大对象或者大文本字段的话,这点缓存很快就会被填满并触发额外的I/O操作。看看Innodb_log_waits状态变量,如果它不是0,应该增大这个值。但太大了也是浪费内存,因为1秒钟总会flush一次,所以不需要设到超过1秒的需求。
(3)innodb_flush_log_at_trx_commit
把log buffer的数据写入日志文件并flush磁盘的策略,该值对插入数据的速度影响非常大。取值分别为0、1(默认值)、2(推荐值)
0:事务提交时,不写入磁盘,而是每秒把log buffer的数据写入日志文件,并且flush(刷到磁盘)。速度最快,但不安全。mysqld进程的崩溃会导致上一秒钟所有事务数据的丢失。
1:每次事务提交时把log buffer的数据写入日志文件,并且flush(刷到磁盘)。最安全,但也最慢。确保了事务的ACID。
2:每次事务提交时把log buffer的数据写入日志文件,每秒flush(刷到磁盘)。速度较快,比0安全。操作系统崩溃或者系统断电会导致上一秒钟所有事务数据的丢失。
(4)innodb_log_file_size
在一个日志组每个日志文件的大小,用于确保写操作快速而可靠并且在崩溃时恢复。一般用64M-512M,具体取决于服务器的空间。大的文件提供更高的性能,但数据库恢复时会用更多的时间。
(5)innodb_additional_mem_pool_size
存储数据字典和其他内部数据结构的内存池大小。默认为1M,对于2G内存的机器,推荐值是20M,通常不用太大,应该与表结构的复杂度有关系。如果不够用,MySQL会在错误日志中写入一条警告信息。
(6)innodb_buffer_pool_instances
可以开启多个内存缓冲池,这样可以并行的内存读写。默认为8,一般为1-8。最常1s就会刷新一次,故不用太大。对于较大的事务,可以增大缓存大小。如果InnoDB缓存池被划分成多个区域,建议每个区域不小于1GB的空间。

MySQL事务 

一、事务(Transaction)及其ACID属性
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性:
1.原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
2.一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
3.隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
4.持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

二、并发事务处理带来的问题
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多的用户。但并发事务处理也会带来一些问题,主要包括以下几种情况。
1.更新丢失(Lost Update):
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新。例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的更改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题。
2.脏读(Dirty Reads):
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"。
3.不可重复读(Non-Repeatable Reads):一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
4.幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

三、事务隔离级别
在上面讲到的并发事务处理带来的问题中,“更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本上可分为以下两种:
1.在读取数据前,对其加锁,阻止其他事务对数据进行修改。
2.不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。
为了解决“隔离”与“并发”的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己的业务逻辑要求,通过选择不同的隔离级别来平衡 “隔离”与“并发”的矛盾。下表很好地概括了这4个隔离级别的特性:


MySQL 锁

一、概述
数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外。MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应用场景特点都不太一样,为了满足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。MySQL各存储引擎使用了三种类型(级别)的锁定机制:表级锁定,行级锁定和页级锁定。
1.表级锁定(table-level)
表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣。
使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。
2.行级锁定(row-level)
行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
使用行级锁定的主要是InnoDB存储引擎。
3.页级锁定(page-level)
页级锁定是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
在数据库实现资源锁定的过程中,随着锁定资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂。不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。
使用页级锁定的主要是BerkeleyDB存储引擎。
总的来说,MySQL这3种锁的特性可大致归纳如下:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;    
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

二、表级锁定
由于MyISAM存储引擎使用的锁定机制完全是由MySQL提供的表级锁定实现,所以下面我们将以MyISAM存储引擎作为示例存储引擎。
1.MySQL表级锁的锁模式
MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。锁模式的兼容性:
对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;
MyISAM表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
2.如何加表锁
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。
3.MyISAM表锁优化建议
对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁所带来的附加成本都要小,锁定本身所消耗的资源也是最少。但是由于锁定的颗粒度比较到,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。所以,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。由于锁定级别是不可能改变的了,所以我们首先需要尽可能让锁定的时间变短,然后就是让可能并发进行的操作尽可能的并发。
(1)查询表级锁争用情况
MySQL内部有两组专门的状态变量记录系统内部锁资源争用情况:

复制代码
mysql> show status like 'table%';
+----------------------------+---------+
| Variable_name              | Value   |
+----------------------------+---------+
| Table_locks_immediate      | 100     |
| Table_locks_waited         | 11      |
+----------------------------+---------+
复制代码
这里有两个状态变量记录MySQL内部表级锁定的情况,两个变量说明如下:
Table_locks_immediate:产生表级锁定的次数;
Table_locks_waited:出现表级锁定争用而发生等待的次数;
两个状态值都是从系统启动后开始记录,出现一次对应的事件则数量加1。如果这里的Table_locks_waited状态值比较高,那么说明系统中表级锁定争用现象比较严重,就需要进一步分析为什么会有较多的锁定资源争用了。
(2)缩短锁定时间
如何让锁定时间尽可能的短呢?唯一的办法就是让我们的Query执行时间尽可能的短。
a)尽两减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行;
b)尽可能的建立足够高效的索引,让数据检索更迅速;
c)尽量让MyISAM存储引擎的表只存放必要的信息,控制字段类型;
d)利用合适的机会优化MyISAM表数据文件。
(3)分离能并行的操作
说到MyISAM的表锁,而且是读写互相阻塞的表锁,可能有些人会认为在MyISAM存储引擎的表上就只能是完全的串行化,没办法再并行了。大家不要忘记了,MyISAM的存储引擎还有一个非常有用的特性,那就是ConcurrentInsert(并发插入)的特性。
MyISAM存储引擎有一个控制是否打开Concurrent Insert功能的参数选项:concurrent_insert,可以设置为0,1或者2。三个值的具体说明如下:
concurrent_insert=2,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录;
concurrent_insert=1,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置;
concurrent_insert=0,不允许并发插入。
可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。
(4)合理利用读写优先级
MyISAM存储引擎的是读写互相阻塞的,那么,一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?
答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前。
这是因为MySQL的表级锁定对于读和写是有不同优先级设定的,默认情况下是写优先级要大于读优先级。
所以,如果我们可以根据各自系统环境的差异决定读与写的优先级:
通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接读比写的优先级高。如果我们的系统是一个以读为主,可以设置此参数,如果以写为主,则不用设置;
通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。
虽然上面方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。
另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。
这里还要强调一点:一些需要长时间运行的查询操作,也会使写进程“饿死”,因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条SELECT语句来解决问题,因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。

三、行级锁定
行级锁定不是MySQL自己实现的锁定方式,而是由其他存储引擎自己所实现的,如广为大家所知的InnoDB存储引擎,以及MySQL的分布式存储引擎NDBCluster等都是实现了行级锁定。考虑到行级锁定君由各个存储引擎自行实现,而且具体实现也各有差别,而InnoDB是目前事务型存储引擎中使用最为广泛的存储引擎,所以这里我们就主要分析一下InnoDB的锁定特性。
1.InnoDB锁定模式及实现机制
考虑到行级锁定均由各个存储引擎自行实现,而且具体实现也各有差别,而InnoDB是目前事务型存储引擎中使用最为广泛的存储引擎,所以这里我们就主要分析一下InnoDB的锁定特性。
总的来说,InnoDB的锁定机制和Oracle数据库有不少相似之处。InnoDB的行级锁定同样分为两种类型,共享锁和排他锁,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。
当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。
但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。
而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。
如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。
意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。
所以,可以说InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),
我们可以通过以下表格来总结上面这四种所的共存逻辑关系:

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。

共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE
用SELECT ... IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。
但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE方式获得排他锁。
2.InnoDB行锁实现方式
InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。下面通过一些实际例子来加以说明。
(1)在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。
(2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
(4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
3.间隙锁(Next-Key锁)
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;
对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
例:
假如emp表中只有101条记录,其empid的值分别是 1,2,...,100,101,下面的SQL:

mysql> select * from emp where empid > 100 for update;
是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。
InnoDB使用间隙锁的目的:
(1)防止幻读,以满足相关隔离级别的要求。对于上面的例子,要是不使用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;
(2)为了满足其恢复和复制的需要。
很显然,在使用范围条件检索并锁定记录时,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。
除了间隙锁给InnoDB带来性能的负面影响之外,通过索引实现锁定的方式还存在其他几个较大的性能隐患:
(1)当Query无法利用索引的时候,InnoDB会放弃使用行级别锁定而改用表级别的锁定,造成并发性能的降低;
(2)当Query使用的索引并不包含所有过滤条件的时候,数据检索使用到的索引键所只想的数据可能有部分并不属于该Query的结果集的行列,但是也会被锁定,因为间隙锁锁定的是一个范围,而不是具体的索引键;
(3)当Query在使用索引定位数据的时候,如果使用的索引键一样但访问的数据行不同的时候(索引只是过滤条件的一部分),一样会被锁定。
因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁。
4.死锁
上文讲过,MyISAM表锁是deadlock free的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,当两个事务都需要获得对方持有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。
在InnoDB的事务管理和锁定机制中,有专门检测死锁的机制,会在系统中产生死锁之后的很短时间内就检测到该死锁的存在。当InnoDB检测到系统中产生了死锁之后,InnoDB会通过相应的判断来选这产生死锁的两个事务中较小的事务来回滚,而让另外一个较大的事务成功完成。
那InnoDB是以什么来为标准判定事务的大小的呢?MySQL官方手册中也提到了这个问题,实际上在InnoDB发现死锁之后,会计算出两个事务各自插入、更新或者删除的数据量来判定两个事务的大小。也就是说哪个事务所改变的记录条数越多,在死锁中就越不会被回滚掉。
但是有一点需要注意的就是,当产生死锁的场景中涉及到不止InnoDB存储引擎的时候,InnoDB是没办法检测到该死锁的,这时候就只能通过锁定超时限制参数InnoDB_lock_wait_timeout来解决。
需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。
通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。下面就通过实例来介绍几种避免死锁的常用方法:
(1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。
(2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。
(3)在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁。
(4)在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT...FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题。
(5)当隔离级别为READ COMMITTED时,如果两个线程都先执行SELECT...FOR UPDATE,判断是否存在符合条件的记录,如果没有,就插入记录。此时,只有一个线程能插入成功,另一个线程会出现锁等待,当第1个线程提交后,第2个线程会因主键重出错,但虽然这个线程出错了,却会获得一个排他锁。这时如果有第3个线程又来申请排他锁,也会出现死锁。对于这种情况,可以直接做插入操作,然后再捕获主键重异常,或者在遇到主键重错误时,总是执行ROLLBACK释放获得的排他锁。
5.什么时候使用表锁
对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个别特殊事务中,也可以考虑使用表级锁:
(1)事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。
(2)事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表了。
在InnoDB下,使用表锁要注意以下两点。
(1)使用LOCK TABLES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层──MySQL Server负责的,仅当autocommit=0、InnoDB_table_locks=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server也才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁,否则,InnoDB将无法自动检测并处理这种死锁。
(2)在用 LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。正确的方式见如下语句:
例如,如果需要写表t1并从表t读,可以按如下做:

SET AUTOCOMMIT=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
[do something with tables t1 and t2 here];
COMMIT;
UNLOCK TABLES;
6.InnoDB行锁优化建议
InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势了。但是,InnoDB的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让InnoDB的整体性能表现不仅不能比MyISAM高,甚至可能会更差。
(1)要想合理利用InnoDB的行级锁定,做到扬长避短,我们必须做好以下工作:
a)尽可能让所有的数据检索都通过索引来完成,从而避免InnoDB因为无法通过索引键加锁而升级为表级锁定;
b)合理设计索引,让InnoDB在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他Query的执行;
c)尽可能减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的记录;
d)尽量控制事务的大小,减少锁定的资源量和锁定时间长度;
e)在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少MySQL因为实现事务隔离级别所带来的附加成本。
(2)由于InnoDB的行级锁定和事务性,所以肯定会产生死锁,下面是一些比较常用的减少死锁产生概率的小建议:
a)类似业务模块中,尽可能按照相同的访问顺序来访问,防止产生死锁;
b)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
c)对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率。
(3)可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:

复制代码
mysql> show status like 'InnoDB_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| InnoDB_row_lock_current_waits | 0     |
| InnoDB_row_lock_time          | 0     |
| InnoDB_row_lock_time_avg      | 0     |
| InnoDB_row_lock_time_max      | 0     |
| InnoDB_row_lock_waits         | 0     |
+-------------------------------+-------+
复制代码
InnoDB 的行级锁定状态变量不仅记录了锁定等待次数,还记录了锁定总时长,每次平均时长,以及最大时长,此外还有一个非累积状态量显示了当前正在等待锁定的等待数量。对各个状态量的说明如下:
InnoDB_row_lock_current_waits:当前正在等待锁定的数量;
InnoDB_row_lock_time:从系统启动到现在锁定总时间长度;
InnoDB_row_lock_time_avg:每次等待所花平均时间;
InnoDB_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
InnoDB_row_lock_waits:系统启动后到现在总共等待的次数;
对于这5个状态变量,比较重要的主要是InnoDB_row_lock_time_avg(等待平均时长),InnoDB_row_lock_waits(等待总次数)以及InnoDB_row_lock_time(等待总时长)这三项。尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。
如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors 来进一步观察发生锁冲突的表、数据行等,并分析锁争用的原因。
锁冲突的表、数据行等,并分析锁争用的原因。具体方法如下:

mysql> create table InnoDB_monitor(a INT) engine=InnoDB;
然后就可以用下面的语句来进行查看:

mysql> show engine InnoDB status;
监视器可以通过发出下列语句来停止查看:

mysql> drop table InnoDB_monitor;
设置监视器后,会有详细的当前锁等待的信息,包括表名、锁类型、锁定记录的情况等,便于进行进一步的分析和问题的确定。
可能会有读者朋友问为什么要先创建一个叫InnoDB_monitor的表呢?因为创建该表实际上就是告诉InnoDB我们开始要监控他的细节状态了,
然后InnoDB就会将比较详细的事务以及锁定信息记录进入MySQL的errorlog中,以便我们后面做进一步分析使用。
打开监视器以后,默认情况下每15秒会向日志中记录监控的内容,如果长时间打开会导致.err文件变得非常的巨大,所以用户在确认问题原因之后,
要记得删除监控表以关闭监视器,或者通过使用“--console”选项来启动服务器以关闭写日志文件。

MySQL sql优化 

1.对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描 
	1.select id from t where num is null
	  最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库.
	2.备注、描述、评论之类的可以设置为 NULL,其他的,最好不要使用NULL。
 	  不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL也包含在内),都是占用 100个字符的空间的,
	  如果是varchar这样的变长字段, null 不占用空间。
	3.select id from t where num = 0
	  可以在num上设置默认值0,确保表中num列没有null值,然后再查询 

3.应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。

4.应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描 
	select id from t where num=10 or Name = 'admin'
	可以这样查询:
		select id from t where num = 10
		union all
		select id from t where Name = 'admin'

5.in 和 not in 也要慎用,否则会导致全表扫描 
	1.select id from t where num in(1,2,3)
	  对于连续的数值,能用 between 就不要用 in 了:
	2.select id from t where num between 1 and 3
	  很多时候用 exists 代替 in 是一个好的选择:
	3.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)
	
6.select 1和select 0进行优化
	当我们只关心数据表有多少记录行而不需要知道具体的字段值时,类似“select 1 from tblName”是一个很不错的SQL语句写法,
	它通常用于子查询。这样可以减少系统开销,提高运行效率,因为这样子写的SQL语句,数据库引擎就不会去检索数据表里一条条具体的记录和每条记录里
	一个个具体的字段值并将它们放到内存里,而是根据查询到有多少行存在就输出多少个“1”,每个“1”代表有1行记录,同时选用数字1还因为它所占用的内存空间最小,
	当然用数字0的效果也一样。在不需要知道具体的记录值是什么的情况下这种写法无疑更加可取。

	下面举例示范这种写法的常见用法:
	1.列出每个班的学生人数
		1.常规写法
			select class,count (*) as pax from students group by class;
		2.更优写法
			select class,count (1) as pax from students group by class;
	2.列出每个班最年轻的学生资料
		1.常规写法
			select a.* from students a where not exists(select b.sid from students b where b.sid=a.sid and b.date_birth>a.date_birth);
		2.更优写法
			select a.* from students a where not exists(select 1 from students b where b.sid=a.sid and b.date_birth>a.date_birth);
 
7.like模糊查询 也将导致全表扫描:
	select id from t where name like ‘%abc%’
	1.使用 字段名 like “%搜索内容%”或 字段名 like “%搜索内容”的话,即模糊查询LIKE中以“%”或“_”开头的话,是无法使用到索引进行快速查询的。
	2.使用 字段名 like “搜索内容%”,是可以使用到索引进行快速查询的。
	3.可使用FULLTEXT(全文索引),来替代模糊查询LIKE的使用,搜索引擎常用
	4.全文索引技术是目前搜索引擎的关键技术。试想在1M大小的文件中搜索一个词,可能需要几秒,在100M的文件中可能需要几十秒,
	  如果在更大的文件中搜索那么就需要更大的系统开销,这样的开销是不现实的。所以在这样的矛盾下出现了全文索引技术,有时候有人叫倒排文档技术。

8.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;
  它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。
  如下面语句将进行全表扫描:
	1.select id from t where num = @num
	  可以改为查询时强制使用索引:select id from t with(index(索引名)) where num = @num
	2.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
	  select id from t where num/2 = 100
	  应改为:
	  select id from t where num = 100*2
	
  "@"是:局部变量声明,如果没有"@"的字段代表是列名; 
  声明变量的格式:declare 变量名 数据类型和长度,
  声明变量:declare @name varchar(8) 
  变量赋值:set @name= '张三' 
  查询:select * from stuInfo where stuName = @name 
  还有@@error 等是全局变量,系统自定义的,我们只读,不能改!!

  @表示局部变量
	局部变量是用户可自定义的变量,它的作用范围仅在程序内部。在程序中通常用来储存从表中查询到的数据,或当作程序执行过程中暂存变量使用。
	局部变量必须以“@”开头,而且必须先用DECLARE命令说明后才可使用。
  @@表示全局变量
	全局变量是SQL Server系统内部使用的变量,其作用范围并不局限于某一程序,而是任何程序均可随时调用全局变量通常存储一些SQL Server的配置设定值和效能统计数据。
	用户可在程序中用全局变量来测试系统的设定值或Transact-SQL命令执行后的状态值。

9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。 
	select id from t where substring(name,1,3) = ’abc’       -–name以abc开头的id
	select id from t where datediff(day,createdate,’2005-11-30′) = 0    -–‘2005-11-30’    --生成的id
	应改为:
		select id from t where name like 'abc%'  # 使用 字段名 like “搜索内容%”,是可以使用到索引进行快速查询的
		select id from t where createdate >= '2005-11-30' and createdate < '2005-12-1'

10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,
  并且应尽可能的让字段顺序与索引顺序相一致。

12.不要写一些没有意义的查询,如需要生成一个空表结构:
	select col1,col2 into #t from t where 1=0
	这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
	create table #t(…)

13.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。

14.对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差。

15.select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。

16.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,
   所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。

17.应尽可能的避免更新 clustered 聚合索引数据列,因为 clustered 聚合索引数据列 的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,
   会耗费相当大的资源。若应用系统需要频繁更新 clustered 聚合索引数据列,那么需要考虑是否应将该索引建为 clustered 聚合索引。

18.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
   这是因为引擎在处理查询和连 接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

19.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

20.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

21.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。

22.避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使用,适当地使用它们可以使某些例程更有效。
   例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;
   如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。
   在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

29.尽量避免大事务操作,提高系统并发能力。

30.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
 
31.实际案例分析:拆分大的 DELETE 或 INSERT 语句,批量提交SQL语句
	如果你需要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你需要非常小心,要避免你的操作让你的整个网站停止相应。因为这两个操作是会锁表的,
	表一锁住了,别的操作都进不来了。
  	Apache 会有很多的子进程或线程。所以,其工作起来相当有效率,而我们的服务器也不希望有太多的子进程,线程和数据库链接,这是极大的占服务器资源的事情,尤其是内存。
  	如果你把你的表锁上一段时间,比如30秒钟,那么对于一个有很高访问量的站点来说,这30秒所积累的访问进程/线程,数据库链接,打开的文件数,
	可能不仅仅会让你的WEB服务崩溃,还可能会让你的整台服务器马上挂了。
  	所以,如果你有一个大的处理,你一定把其拆分,使用 LIMIT oracle(rownum),sqlserver(top)条件是一个好的方法。下面是一个mysql示例:
		while(1)
		{
			//每次只做1000条
			mysql_query(“delete from logs where log_date <= ’2012-11-01’ limit 1000”);
			if(mysql_affected_rows() == 0)
			{
     			//删除完成,退出!
     			break;
  			}

			//每次暂停一段时间,释放表让其他进程/线程访问。
			usleep(50000)
		}
一、SQL语句优化
(1)使用limit对查询结果的记录进行限定
(2)避免select *,将需要查找的字段列出来
(3)使用连接(join)来代替子查询
(4)拆分大的delete或insert语句

二、选择合适的数据类型
(1)使用可存下数据的最小的数据类型,整型 < date,time < char,varchar < blob
(2)使用简单的数据类型,整型比字符处理开销更小,因为字符串的比较更复杂。如,int类型存储时间类型,bigint类型转ip函数
(3)使用合理的字段属性长度,固定长度的表会更快。使用enum、char而不是varchar
(4)尽可能使用not null定义字段
(5)尽量少用text,非用不可最好分表

三、选择合适的索引列
(1)查询频繁的列,在where,group by,order by,on从句中出现的列
(2)where条件中<,<=,=,>,>=,between,in,以及like 字符串+通配符(%)出现的列
(3)长度小的列,索引字段越小越好,因为数据库的存储单位是页,一页中能存下的数据越多越好
(4)离散度大(不同的值多)的列,放在联合索引前面。查看离散度,通过统计不同的列值来实现,count越大,离散程度越高:

mysql> SELECT COUNT(DISTINCT column_name) FROM table_name;
四、使用命令分析
(1)SHOW查看状态
1.显示状态信息

mysql> SHOW [SESSION|GLOBAL] STATUS LIKE '%Status_name%';
session(默认):取出当前窗口的执行
global:从mysql启动到现在
(a)查看查询次数(插入次数com_insert、修改次数com_insert、删除次数com_delete)

mysql> SHOW STATUS LIKE 'com_select';
(b)查看连接数(登录次数)

mysql> SHOW STATUS LIKE 'connections';
(c)数据库运行时间

mysql> SHOW STATUS LIKE 'uptime';
(d)查看慢查询次数

mysql> SHOW STATUS LIKE 'slow_queries';
(e)查看索引使用的情况:

mysql> SHOW STATUS LIKE 'handler_read%';
handler_read_key:这个值越高越好,越高表示使用索引查询到的次数。
handler_read_rnd_next:这个值越高,说明查询低效。
2.显示系统变量

mysql> SHOW VARIABLES LIKE '%Variables_name%';
3.显示InnoDB存储引擎的状态

mysql> SHOW ENGINE INNODB STATUS;
(2)EXPLAIN分析查询

mysql> EXPLAIN SELECT column_name FROM table_name;
explain查询sql执行计划,各列含义:
table:表名;
type:连接的类型
    -const:主键、索引;
    -eq_reg:主键、索引的范围查找;
    -ref:连接的查找(join)
    -range:索引的范围查找;
    -index:索引的扫描;
    -all:全表扫描;
possible_keys:可能用到的索引;
key:实际使用的索引;
key_len:索引的长度,越短越好;
ref:索引的哪一列被使用了,常数较好;
rows:mysql认为必须检查的用来返回请求数据的行数;
extra:using filesort、using temporary(常出现在使用order by时)时需要优化。
    -Using filesort  额外排序。看到这个的时候,查询就需要优化了
    -Using temporary 使用了临时表。看到这个的时候,也需要优化
(3)PROFILING分析SQL语句
1.开启profile。查看当前SQL执行时间

mysql> SET PROFILING=ON; 
mysql> SHOW profiles;
2.查看所有用户的当前连接。包括执行状态、是否锁表等

mysql> SHOW processlist;
(4)PROCEDURE ANALYSE()取得建议
通过分析select查询结果对现有的表的每一列给出优化的建议

mysql> SELECT column_name FROM table_name PROCEDURE ANALYSE();
(5)OPTIMIZE TABLE回收闲置的数据库空间

mysql> OPTIMIZE TABLE table_name;
对于MyISAM表,当表上的数据行被删除时,所占据的磁盘空间并没有立即被回收,使用命令后这些空间将被回收,并且对磁盘上的数据行进行重排(注意:是磁盘上,而非数据库)。
对于InnoDB表,OPTIMIZE TABLE被映射到ALTER TABLE上,这会重建表。重建操作能更新索引统计数据并释放成簇索引中的未使用的空间。
只需在批量删除数据行之后,或定期(每周一次或每月一次)进行一次数据表优化操作即可,只对那些特定的表运行。
(6)REPAIR TABLE修复被破坏的表

mysql> REPAIR TABLE table_name;
(7)CHECK TABLE检查表是否有错误

mysql> CHECK TABLE table_name;

null、not null 

Null 貌似在哪里都是个头疼的问题,比如 Java 里让人头疼的 NullPointerException,为了避免猝不及防的空指针异常,千百年来程序猿们不得不在代码里小心翼翼的各种 if 判断,麻烦而又臃肿,为此 java8 引入了 Optional 来避免这一问题。

下面咱们要聊的是 MySQL 里的 null,在大量的 MySQL 优化文章和书籍里都提到了字段尽可能用NOT NULL,而不是NULL,除非特殊情况。但却都只给结论不说明原因,犹如鸡汤不给勺子一样,让不少初学者对这个结论半信半疑或者云里雾里。本文今天就详细的剖析下使用 Null 的原因,并给出一些不用 Null 的理由。

1、NULL 为什么这么多人用?

NULL是创建数据表时默认的,初级或不知情的或怕麻烦的程序员不会注意这点。

很多人员都以为not null 需要更多空间,其实这不是重点。

重点是很多程序员觉得NULL在开发中不用去判断插入数据,写sql语句的时候更方便快捷。

2、是不是以讹传讹?

MySQL 官网文档:

NULL columns require additional space in the rowto record whether their values are NULL. For MyISAM tables, each NULL columntakes one bit extra, rounded up to the nearest byte.

Mysql难以优化引用可空列查询,它会使索引、索引统计和值更加复杂。可空列需要更多的存储空间,还需要mysql内部进行特殊处理。可空列被索引后,每条记录都需要一个额外的字节,还能导致MYisam 中固定大小的索引变成可变大小的索引。

—— 出自《高性能mysql第二版》

照此分析,还真不是以讹传讹,这是有理论依据和出处的。

3、给我一个不用 Null 的理由?

(1)所有使用NULL值的情况,都可以通过一个有意义的值的表示,这样有利于代码的可读性和可维护性,并能从约束上增强业务数据的规范性。

(2)NULL值到非NULL的更新无法做到原地更新,更容易发生索引分裂,从而影响性能。

注意:但把NULL列改为NOT NULL带来的性能提示很小,除非确定它带来了问题,否则不要把它当成优先的优化措施,最重要的是使用的列的类型的适当性。

(3)NULL值在timestamp类型下容易出问题,特别是没有启用参数explicit_defaults_for_timestamp

(4)NOT IN、!= 等负向条件查询在有 NULL 值的情况下返回永远为空结果,查询容易出错。

create table table_2 (`id` INT (11) NOT NULL,user_name varchar(20) NOT NULL)create table table_3 (`id` INT (11) NOT NULL,user_name varchar(20))insert into table_2 values (4,"zhaoliu_2_1"),(2,"lisi_2_1"),(3,"wangmazi_2_1"),(1,"zhangsan_2"),(2,"lisi_2_2"),(4,"zhaoliu_2_2"),(3,"wangmazi_2_2")insert into table_3 values (1,"zhaoliu_2_1"),(2, null)-- 
1、NOT IN子查询在有NULL值的情况下返回永远为空结果
2、单列索引不存null值,复合索引不存全为null的值,如果列允许为null,可能会得到“不符合预期”的结果集-- 如果name允许为null,索引不存储null值,结果集中不会包含这些记录。所以,请使用not null约束以及默认值。
3、如果在两个字段进行拼接:比如题号+分数,首先要各字段进行非null判断,否则只要任意一个字段为空都会造成拼接的结果为null。
4、如果有 Null column 存在的情况下,count(Null column)需要格外注意,null 值不会参与统计。
5、注意 Null 字段的判断方式, = null 将会得到错误的结果。
(5)Null 列需要更多的存储空间:需要一个额外字节作为判断是否为 NULL 的标志位。

alter table table_3 add index idx_user_name (user_name);alter table table_2 add index idx_user_name (user_name);
explain select * from table_2 where user_name='zhaoliu_2_1';explain select * from table_3 where user_name='zhaoliu_2_1';

可以看到同样的 varchar(20) 长度,table_2 要比 table_3 索引长度大,这是因为:

两张表的字符集不一样,且字段一个为 NULL 一个非 NULL。

key_len 的计算规则和三个因素有关:数据类型、字符编码、是否为 NULL

key_len 62 == 20*3(utf8 3字节) + 2 (存储 varchar 变长字符长度 2字节,定长字段无需额外的字节)

key_len 83 == 20*4(utf8mb4 4字节) + 1 (是否为 Null 的标识) + 2 (存储 varchar 变长字符长度 2字节,定长字段无需额外的字节)

所以说索引字段最好不要为NULL,因为NULL会使索引、索引统计和值更加复杂,并且需要额外一个字节的存储空间。基于以上这些理由和原因,我想咱们不用 Null 的理由应该是够了

MYSQL-性能优化篇
1、为什么要进行数据库优化?
1、避免网站页面出现访问错误
由于数据库连接timeout产生页面5xx错误
由于慢查询造成页面无法加载
由于阻塞造成数据无法提交
2、增加数据库的稳定性
很多数据库问题都是由于低效的查询引起的
3、优化用户体验
流畅页面的访问速度
良好的网站功能体验
2、mysql数据库优化
可以从哪几个方面进行数据库的优化?如下图所示:

A、SQL及索引优化
根据需求写出良好的SQL,并创建有效的索引,实现某一种需求可以多种写法,这时候我们就要选择一种效率最高的写法。这个时候就要了解sql优化
B、数据库表结构优化
根据数据库的范式,设计表结构,表结构设计的好直接关系到写SQL语句。
C、系统配置优化
大多数运行在Linux机器上,如tcp连接数的限制、打开文件数的限制、安全性的限制,因此我们要对这些配置进行相应的优化。
D、硬件配置优化
选择适合数据库服务的cpu,更快的IO,更高的内存;cpu并不是越多越好,某些数据库版本有最大的限制,IO操作并不是减少阻塞。
注:通过上图可以看出,该金字塔中,优化的成本从下而上逐渐增高,而优化的效果会逐渐降低。
SQL及索引优化
1、mysql安装与卸载(linux在线安装与卸载)
2、数据库版本选择
		1、查看数据库的版本

 

select @@version;

2、准备数据

网址:https://dev.mysql.com/doc/sakila/en/sakila-installation.html

sakila-db.zip压缩包所包含的文件如下解释

加载数据

步骤如下图所示

3、表结构关系

注:该表结构关系是用工具生成的。

 

  1. 如何发现有问题的SQL

MySQL慢查日志的开启方式和存储格式

1、检查慢查日志是否开启:

show variables like 'slow_query_log'

show variables like 'slow_query_log'  

//查看是否开启慢查询日志

 

set global slow_query_log_file=' /usr/share/mysql/sql_log/mysql-slow.log'

//慢查询日志的位置

 

set global log_queries_not_using_indexes=on;

//开启慢查询日志

 

set global long_query_time=1;  

//大于1秒钟的数据记录到慢日志中,如果设置为默认0,则会有大量的信息存储在磁盘中,磁盘很容易满掉

2、查看所有日志的变量信息

show variables like '%log%'

mysql> show variables like '%log%';
+-----------------------------------------+------------------------------------+
| Variable_name                           | Value                              |
+-----------------------------------------+------------------------------------+
| back_log                                | 80                                 |
| binlog_cache_size                       | 32768                              |
| binlog_checksum                         | CRC32                              |
| binlog_direct_non_transactional_updates | OFF                                |
| binlog_error_action                     | IGNORE_ERROR                       |
| binlog_format                           | STATEMENT                          |
| binlog_gtid_simple_recovery             | OFF                                |
| binlog_max_flush_queue_time             | 0                                  |
| binlog_order_commits                    | ON                                 |
| binlog_row_image                        | FULL                               |
| binlog_rows_query_log_events            | OFF                                |
| binlog_stmt_cache_size                  | 32768                              |
| binlogging_impossible_mode              | IGNORE_ERROR                       |
| expire_logs_days                        | 0                                  |
| general_log                             | OFF                                |
| general_log_file                        | /var/lib/mysql/mysql-host.log      |
| innodb_api_enable_binlog                | OFF                                |
| innodb_flush_log_at_timeout             | 1                                  |
| innodb_flush_log_at_trx_commit          | 1                                  |
| innodb_locks_unsafe_for_binlog          | OFF                                |
| innodb_log_buffer_size                  | 8388608                            |
| innodb_log_compressed_pages             | ON                                 |
| innodb_log_file_size                    | 50331648                           |
| innodb_log_files_in_group               | 2                                  |
| innodb_log_group_home_dir               | ./                                 |
| innodb_mirrored_log_groups              | 1                                  |
| innodb_online_alter_log_max_size        | 134217728                          |
| innodb_undo_logs                        | 128                                |
| log_bin                                 | OFF                                |
| log_bin_basename                        |                                    |
| log_bin_index                           |                                    |
| log_bin_trust_function_creators         | OFF                                |
| log_bin_use_v1_row_events               | OFF                                |
| log_error                               | /var/log/mysqld.log                |
| log_output                              | FILE                               |
| log_queries_not_using_indexes           | ON                                 |
| log_slave_updates                       | OFF                                |
| log_slow_admin_statements               | OFF                                |
| log_slow_slave_statements               | OFF                                |
| log_throttle_queries_not_using_indexes  | 0                                  |
| log_warnings                            | 1                                  |
| max_binlog_cache_size                   | 18446744073709547520               |
| max_binlog_size                         | 1073741824                         |
| max_binlog_stmt_cache_size              | 18446744073709547520               |
| max_relay_log_size                      | 0                                  |
| relay_log                               |                                    |
| relay_log_basename                      |                                    |
| relay_log_index                         |                                    |
| relay_log_info_file                     | relay-log.info                     |
| relay_log_info_repository               | FILE                               |
| relay_log_purge                         | ON                                 |
| relay_log_recovery                      | OFF                                |
| relay_log_space_limit                   | 0                                  |
| simplified_binlog_gtid_recovery         | OFF                                |
| slow_query_log                          | OFF                                |
| slow_query_log_file                     | /var/lib/mysql/mysql-host-slow.log |
| sql_log_bin                             | ON                                 |
| sql_log_off                             | OFF                                |
| sync_binlog                             | 0                                  |
| sync_relay_log                          | 10000                              |
| sync_relay_log_info                     | 10000                              |
+-----------------------------------------+------------------------------------+
61 rows in set (0.01 sec)
开启慢查日志:

开启慢查日志:

show variables like 'slow_query_log'  

//查看是否开启慢查询日志

 

set global slow_query_log_file=' /var/lib/mysql/mysql-host-slow.log '

//慢查询日志的位置

 

set global log_queries_not_using_indexes=on;

//开启慢查询日志

 

set global long_query_time=1;  

//大于1秒钟的数据记录到慢日志中,如果设置为默认0,则会有大量的信息存储在磁盘中,磁盘很容易满掉

验证慢查询日志是否开启:

在mysql操作中,

Show databases;
Use sakila;
select * from store;
select * from staff; 


监听日志文件,看是否写入
tail -50f /var/lib/mysql/mysql-host-slow.log

3、MySQL慢查日志的存储格式
如下图所示:

说明:
	1、# Time: 180526  1:06:54 -------查询的执行时间
	2、# User@Host: root[root] @ localhost []  Id:     4 -------执行sql的主机信息
	3、# Query_time: 0.000401  Lock_time: 0.000105 Rows_sent: 2  Rows_examined: 2-------SQL的执行信息:
			Query_time:SQL的查询时间
			Lock_time:锁定时间
			Rows_sent:所发送的行数
			Rows_examined:锁扫描的行数
	4、SET timestamp=1527268014; -------SQL执行时间
	5、select * from staff; -------SQL的执行内容


4、MySQL慢查日志分析工具(mysqldumpslow)
1、介绍
如何进行查看慢查询日志,如果开启了慢查询日志,就会生成很多的数据,然后我们就可以通过对日志的分析,生成分析报表,然后通过报表进行优化。

2、用法
接下来我们查看一下这个工具的用法:

注意:在mysql数据库所在的服务器上,而不是在mysql>命令行中

该工具如何使用:mysqldumpslow -h

查看verbose信息
Mysqldumpslow -v

查看慢查询日志的前10个,mysqldumpslow 分析的结果如下

mysqldumpslow -t 10 /var/lib/mysql/mysql-host-slow.log

如上图两条就是分析的结果,每条结果都显示是执行时间,锁定时间,发送的行数,扫描的行数

这个工具是最常用的工具,通过安装mysql进行附带安装,但是该工具统计的结果比较少,对我们的优化锁表现的数据还是比较少。

5、MySQL慢查日志分析工具(pt-query-digest)
1、介绍及作用
	作为一名优秀的mysql dba也需要有掌握几个好用的mysql管理工具,所以我也一直在整理和查找一些能够便于管理mysql的利器。以后的一段时间内,将会花一大部分的精力去搜索这些工具。
性 能的管理一直都是摆在第一位的,dba的很多工作管理层都看不到也没有办法衡量价值,但是如果一个系统慢的跟蜗牛一样,dba通过监控调优把系统从崩溃边 缘重新拉回到高铁时代。这种价值和触动应该是巨大的。(很多企业的领导认为系统跑不动了就需要换更快的CPU、更大的内存、更快的存储,而且这还不是少 数,所以DBA的价值也一直体现不出来,薪水自然也就不会很高)
mysql 的日志是跟踪mysql性能瓶颈的最快和最直接的方式了,系统性能出现瓶颈的时候,首先要打开慢查询日志,进行跟踪;这段时间关于慢查询日志的管理和查看 已经整理过两篇文章了,不经意间又发现了一个查看慢查询日志的工具:mk-query-digest,这个工具网上号称mysql dba必须掌握的十大工具之首。


2、安装pt-query-digest工具
	1.1、快速安装(注:必须先要安装wget)

wget https://www.percona.com/downloads/percona-toolkit/2.2.16/RPM/percona-toolkit-2.2.16-1.noarch.rpm && yum localinstall -y  percona-toolkit-2.2.16-1.noarch.rpm

1.2、检查是否安装完成:
		 命令行中输入:pt-summary
			显示如下图所示:说明安装成功!输入【[root@node03 mysql]# pt-query-digest --help】

1.3、工具使用简介:

1查看服务器信息

命令:pt-summary

2、查看磁盘开销使用信息

命令:pt-diskstats

 

3、查看mysql数据库信息

命令:pt-mysql-summary --user=root --password=admin

4、分析慢查询日志

命令:pt-query-digest /data/mysql/data/db-3-12-slow.log

 

5、查找mysql的从库和同步状态

命令:pt-slave-find --host=localhost --user=root --password=123456

 

6、查看mysql的死锁信息

pt-deadlock-logger --user=root --password=123456 localhost

 

7、从慢查询日志中分析索引使用情况

pt-index-usage slow_20131009.log

 

8、查找数据库表中重复的索引

pt-duplicate-key-checker --host=localhost --user=root --password=admin

 

9、查看mysql表和文件的当前活动IO开销

pt-ioprofile

 

10、查看不同mysql配置文件的差异

pt-config-diff /etc/my.cnf /etc/my_master.cnf

 

11、pt-find查找mysql表和执行命令,示例如下

查找数据库里大于2G的表:

pt-find --user=root --password=123456 --tablesize +2G

查找10天前创建,MyISAM引擎的表:

pt-find --user=root --password=123456 --ctime +10 --engine MyISAM

查看表和索引大小并排序

pt-find --user=root --password=123456 --printf "%T\t%D.%N\n" | sort -rn

 

 

12、pt-kill 杀掉符合标准的mysql进程

显示查询时间大于60秒的查询

pt-kill --user=root --password=123456 --busy-time 60 --print

kill掉大于60秒的查询

 pt-kill --user=root --password=123456 --busy-time 60 --kill

 

13、查看mysql授权

1pt-show-grants --user=root --password=123456

2pt-show-grants --user=root --password=123456 --separate –revoke

 

14、验证数据库复制的完整性

pt-table-checksum --user=root --password=123456

 

15、附录:

6、如何通过慢查日志发现有问题的SQL
	
1、查询次数多且每次查询占用时间长的sql
通常为pt-query-digest分析的前几个查询;该工具可以很清楚的看出每个SQL执行的次数及百分比等信息,执行的次数多,占比比较大的SQL
2、IO大的sql
		注意pt-query-digest分析中的Rows examine项。扫描的行数越多,IO越大。
3、未命中的索引的SQL
		注意pt-query-digest分析中的Rows examine 和Rows Send的对比。说明该SQL的索引命中率不高,对于这种SQL,我们要重点进行关注。

7、通过explain查询分析SQL的执行计划
1、使用explain查询SQL的执行计划
SQL的执行计划侧面反映出了SQL的执行效率,具体执行方式如下所示:
在执行的SQL前面加上explain关键词即可;

2、每个字段的说明:
1)、id列数字越大越先执行,如果说数字一样大,那么就从上往下依次执行,id列为null的就表是这是一个结果集,不需要使用它来进行查询。
 
2)、select_type列常见的有:

A:simple:表示不需要union操作或者不包含子查询的简单select查询。有连接查询时,外层的查询为simple,且只有一个

B:primary:一个需要union操作或者含有子查询的select,位于最外层的单位查询的select_type即为primary。且只有一个

C:union:union连接的两个select查询,第一个查询是dervied派生表,除了第一个表外,第二个以后的表select_type都是union

D:dependent union:与union一样,出现在union 或union all语句中,但是这个查询要受到外部查询的影响

E:union result:包含union的结果集,在union和union all语句中,因为它不需要参与查询,所以id字段为null

F:subquery:除了from字句中包含的子查询外,其他地方出现的子查询都可能是subquery

G:dependent subquery:与dependent union类似,表示这个subquery的查询要受到外部表查询的影响

H:derived:from字句中出现的子查询,也叫做派生表,其他数据库中可能叫做内联视图或嵌套select
 
3)、table
显示的查询表名,如果查询使用了别名,那么这里显示的是别名,如果不涉及对数据表的操作,那么这显示为null,如果显示为尖括号括起来的<derived N>就表示这个是临时表,后边的N就是执行计划中的id,表示结果来自于这个查询产生。如果是尖括号括起来的<union M,N>,与<derived N>类似,也是一个临时表,表示这个结果来自于union查询的id为M,N的结果集。
 
4)、type
依次从好到差:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,ALL,除了all之外,其他的type都可以使用到索引,除了index_merge之外,其他的type只可以用到一个索引

A:system:表中只有一行数据或者是空表,且只能用于myisam和memory表。如果是Innodb引擎表,type列在这个情况通常都是all或者index

B:const:使用唯一索引或者主键,返回记录一定是1行记录的等值where条件时,通常type是const。其他数据库也叫做唯一索引扫描

C:eq_ref:出现在要连接过个表的查询计划中,驱动表只返回一行数据,且这行数据是第二个表的主键或者唯一索引,且必须为not null,唯一索引和主键是多列时,只有所有的列都用作比较时才会出现eq_ref

D:ref:不像eq_ref那样要求连接顺序,也没有主键和唯一索引的要求,只要使用相等条件检索时就可能出现,常见与辅助索引的等值查找。或者多列主键、唯一索引中,使用第一个列之外的列作为等值查找也会出现,总之,返回数据不唯一的等值查找就可能出现。

E:fulltext:全文索引检索,要注意,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql不管代价,优先选择使用全文索引

F:ref_or_null:与ref方法类似,只是增加了null值的比较。实际用的不多。

G:unique_subquery:用于where中的in形式子查询,子查询返回不重复值唯一值

H:index_subquery:用于in形式子查询使用到了辅助索引或者in常数列表,子查询可能返回重复值,可以使用索引将子查询去重。

I:range:索引范围扫描,常见于使用>,<,is null,between ,in ,like等运算符的查询中。

J:index_merge:表示查询使用了两个以上的索引,最后取交集或者并集,常见and ,or的条件使用了不同的索引,官方排序这个在ref_or_null之后,但是实际上由于要读取所个索引,性能可能大部分时间都不如range

K:index:索引全表扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组的查询。

L:all:这个就是全表扫描数据文件,然后再在server层进行过滤返回符合要求的记录。
 
5)、possible_keys
查询可能使用到的索引都会在这里列出来
 
6)、key
查询真正使用到的索引,select_type为index_merge时,这里可能出现两个以上的索引,其他的select_type这里只会出现一个。
 
7)、key_len
用于处理查询的索引长度,如果是单列索引,那就整个索引长度算进去,如果是多列索引,那么查询不一定都能使用到所有的列,具体使用到了多少个列的索引,这里就会计算进去,没有使用到的列,这里不会计算进去。留意下这个列的值,算一下你的多列索引总长度就知道有没有使用到所有的列了。要注意,mysql的ICP特性使用到的索引不会计入其中。另外,key_len只计算where条件用到的索引长度,而排序和分组就算用到了索引,也不会计算到key_len中。
 
8)、ref
如果是使用的常数等值查询,这里会显示const,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func
 
9)、rows
这里是执行计划中估算的扫描行数,不是精确值
 
10)、extra
这个列可以显示的信息非常多,有几十种,常用的有

A:distinct:在select部分使用了distinc关键字

B:no tables used:不带from字句的查询或者From dual查询

C:使用not in()形式子查询或not exists运算符的连接查询,这种叫做反连接。即,一般连接查询是先查询内表,再查询外表,反连接就是先查询外表,再查询内表。

D:using filesort:排序时无法使用到索引时,就会出现这个。常见于order by和group by语句中

E:using index:查询时不需要回表查询,直接通过索引就可以获取查询的数据。

F:using join buffer(block nested loop),using join buffer(batched key accss):5.6.x之后的版本优化关联查询的BNL,BKA特性。主要是减少内表的循环数量以及比较顺序地扫描查询。

G:using sort_union,using_union,using intersect,using sort_intersection:
using intersect:表示使用and的各个索引的条件时,该信息表示是从处理结果获取交集
using union:表示使用or连接各个使用索引的条件时,该信息表示从处理结果获取并集
using sort_union和using sort_intersection:与前面两个对应的类似,只是他们是出现在用and和or查询信息量大时,先查询主键,然后进行排序合并后,才能读取记录并返回。

H:using temporary:表示使用了临时表存储中间结果。临时表可以是内存临时表和磁盘临时表,执行计划中看不出来,需要查看status变量,used_tmp_table,used_tmp_disk_table才能看出来。

I:using where:表示存储引擎返回的记录并不是所有的都满足查询条件,需要在server层进行过滤。查询条件中分为限制条件和检查条件,5.6之前,存储引擎只能根据限制条件扫描数据并返回,然后server层根据检查条件进行过滤再返回真正符合查询的数据。5.6.x之后支持ICP特性,可以把检查条件也下推到存储引擎层,不符合检查条件和限制条件的数据,直接不读取,这样就大大减少了存储引擎扫描的记录数量。extra列显示using index condition

J:firstmatch(tb_name):5.6.x开始引入的优化子查询的新特性之一,常见于where字句含有in()类型的子查询。如果内表的数据量比较大,就可能出现这个

K:loosescan(m..n):5.6.x之后引入的优化子查询的新特性之一,在in()类型的子查询中,子查询返回的可能有重复记录时,就可能出现这个
 
除了这些之外,还有很多查询数据字典库,执行计划过程中就发现不可能存在结果的一些提示信息
 
11)、filtered
使用explain extended时会出现这个列,5.7之后的版本默认就有这个字段,不需要使用explain extended了。这个字段表示存储引擎返回的数据在server层过滤后,剩下多少满足查询的记录数量的比例,注意是百分比,不是具体记录数。

附图:

3、具体慢查询的优化案例

1、函数Max()的优化

用途:查询最后支付时间-优化max()函数

语句:

 

select max(payment_date) from payment;

 

执行计划:

explain select max(payment_date) from payment;

可以看到显示的执行计划,并不是很高效,可以拖慢服务器的效率,如何优化了?

创建索引

create index inx_paydate on payment(payment_date);

 

索引是顺序操作的,不需要扫描表,执行效率就会比较恒定,

 

2、函数Count()的优化

需求:在一条SQL中同事查处2006年和2007年电影的数量

 

错误的方式:

语句:

select count(release_year='2006' or release_year='2007') from film;

2006和2007年分别是多少,判断不出来

 

  select count(*) from film where release_year='2006' or release_year='2007';

 

正确的编写方式:

select count(release_year='2006' or null) as '06films',count(release_year='2007' or null) as '07films' from film;

区别:count(*)和count(id)

创建表并插入语句

 

 create table t(id int);

 

 insert into t values(1),(2),(null);

Count(*):select count(*)from t;

Count(id):select count(id)from t;

说明:

Count(id)是不包含null的值

Count(*)是包含null的值

 

3、子查询的优化

 

子查询是我们在开发过程中经常使用的一种方式,在通常情况下,需要把子查询优化为join查询但在优化是需要注意关联键是否有一对多的关系,要注意重复数据。

查看我们所创建的t表

 

show create table t;

接下来我们创建一个t1表

create table t1(tid int);

并插入一条数据

 

我们要进行一个子查询,需求:查询t表中id在t1表中tid的所有数据;

select * from t where t.id in (select t1.tid from t1);

接下来我们用join的操作来进行操作

select id from t join t1 on t.id =t1.tid;

通过上面结果来看,查询的结果是一致的,我们就将子查询的方式优化为join操作。

 

接下来,我们在t1表中再插入一条数据

insert into t1 values (1);

 

select * from t1;

在这种情况下,如果我们使用子查询方式进行查询,返回的结果就是如下图所示:

如果使用join方式进行查找,如下图所示:

在这种情况下出现了一对多的关系,会出现数据的重复,我们为了方式数据重复,不得不使用distinct关键词进行去重操作

select distinct id from t join t1 on t.id =t1.tid;

注意:这个一对多的关系是我们开发过程中遇到的一个坑,出现数据重复,需要大家注意一下。

 

 

例子:查询sandra出演的所有影片:

explain select title,release_year,length

 from film

 where film_id in (

 select film_id from film_actor where actor_id in (

 select actor_id from actor where first_name='sandra'));

 

4、group by的优化

最好使用同一表中的列,

需求:每个演员所参演影片的数量-(影片表和演员表) 

explain select actor.first_name,actor.last_name,count(*)

from sakila.film_actor

inner join sakila.actor using(actor_id)

group by film_actor.actor_id;

优化后的SQL:

explain select actor.first_name,actor.last_name,c.cnt

from sakila.actor inner join (

select actor_id,count(*) as cnt from sakila.film_actor group by actor_id

)as c using(actor_id);

说明:从上面的执行计划来看,这种优化后的方式没有使用临时文件和文件排序的方式了,取而代之的是使用了索引。查询效率老高了。

这个时候我们表中的数据比较大,会大量的占用IO操作,优化了sql执行的效率,节省了服务器的资源,因此我们就需要优化。

注意:
1、mysql 中using关键词的作用:也就是说要使用using,那么表a和表b必须要有相同的列。
2、在用Join进行多表联合查询时,我们通常使用On来建立两个表的关系。其实还有一个更方便的关键字,那就是Using。
3、如果两个表的关联字段名是一样的,就可以使用Using来建立关系,简洁明了。

5、Limit查询的优化
Limit常用于分页处理,时长会伴随order by从句使用,因此大多时候回使用Filesorts这样会造成大量的IO问题。
例子:
	需求:查询影片id和描述信息,并根据主题进行排序,取出从序号50条开始的5条数据。

select film_id,description from sakila.film order by title limit 50,5;
执行的结果:

在查看一下它的执行计划:

对于这种操作,我们该用什么样的优化方式了?

优化步骤1:

使用有索引的列或主键进行order by操作,因为大家知道,innodb是按照主键的逻辑顺序进行排序的。可以避免很多的IO操作。

select film_id,description from sakila.film order by film_id limit 50,5;

查看一下执行计划

那如果我们获取从500行开始的5条记录,执行计划又是什么样的了?

explain select film_id,description from sakila.film order by film_id limit 500,5\G

随着我们翻页越往后,IO操作会越来越大的,如果一个表有几千万行数据,翻页越后面,会越来越慢,因此我们要进一步的来优化。

 

优化步骤2、记录上次返回的主键, 在下次查询时使用主键过滤。(说明:避免了数据量大时扫描过多的记录

上次limit是50,5的操作,因此我们在这次优化过程需要使用上次的索引记录值,

select film_id,description from sakila.film  where film_id >55 and film_id<=60 order by film_id limit 1,5;

查看执行计划:

结论:扫描行数不变,执行计划是很固定,效率也是很固定的

注意事项:

主键要顺序排序并连续的,如果主键中间空缺了某一列,或者某几列,会出现列出数据不足5行的数据;如果不连续的情况,建立一个附加的列index_id列,保证这一列数据要自增的,并添加索引即可。

 

6、索引的优化

1、什么是索引?

索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。

数据库使用索引以找到特定值,然后顺指针找到包含该值的行。在表中建立索引,然后在索引中找到符合查询条件的索引值,最后通过保存在索引中的ROWID(相当于页码)快速找到表中对应的记录。索引的建立是表中比较有指向性的字段,相当于目录,比如说行政区域代码,同一个地域的行政区域代码都是相同的,那么给这一列加上索引,避免让它重复扫描,从而达到优化的目的!

2、如何创建索引

在执行CREATE TABLE语句时可以创建索引,也可以单独用CREATE INDEX或ALTER TABLE来为表增加索引。

1ALTER 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允许在单个语句中更改多个表,因此可以在同时创建多个索引。

2CREATE 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)

4删除索引

可利用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索引。

 

如果从表中删除了某列,则索引会受到影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。

   5、查看索引

 

mysql> show index from tblname;

 

mysql> show keys from tblname;

   6、什么情况下,使用索引了?

1、表的主关键字

2、自动建立唯一索引

3、表的字段唯一约束

4、直接条件查询的字段(在SQL中用于条件约束的字段)

5、查询中与其它表关联的字段

6、查询中排序的字段(排序的字段如果通过索引去访问那将大大提高排序速度)

7、查询中统计或分组统计的字段

8、表记录太少(如果一个表只有5条记录,采用索引去访问记录的话,那首先需访问索引表,再通过索引表访问数据表,一般索引表与数据表不在同一个数据块)

9、经常插入、删除、修改的表(对一些经常处理的业务表应在查询允许的情况下尽量减少索引)

10、数据重复且分布平均的表字段(假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。)

11、经常和主字段一块查询但主字段索引值比较多的表字段

12、对千万级MySQL数据库建立索引的事项及提高性能的手段

3、如何选择合适的列建立索引

1、在where从句,group by从句,order by从句,on从句中虚线的列添加索引

2、索引字段越小越好(因为数据库数据存储单位是以“页”为单位的,数据存储的越多,IO也会越大)

3、离散度大的列放到联合索引的前面

例子:

select * from payment where staff_id =2 and customer_id =584;

注意:

是index(staff_idcustomer_id)好,还是index(customer_idstaff_id)好

那我们怎么进行验证离散度好了?

 

A、我们先查看一下表结构

desc payment;

B、分别查看这两个字段中不同的id的数量,数量越多,则表明离散程度越大:因此可以通过下图看出:customer_id 离散程度大。

结论:由于customer_id 离散程度大,使用index(customer_idstaff_id)好

 

C、mysql联合索引

 

①命名规则 :表名_字段名

1、需要加索引的字段,要在where条件中

2、数据量少的字段不需要加索引

3、如果where条件中是OR关系,加索引不起作用

4、符合最左原则

什么是联合索引

  1. 两个或更多个列上的索引被称作联合索引,又被称为是复合索引。
  2. 利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引 不同于使用两个单独的索引。复合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。如果您知 道姓,电话簿将非常有用;如果您知道姓和名,电话簿则更为有用,但如果您只知道名不姓,电话簿将没有用处。

 

所以说创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。

 

4、索引优化SQL的方法

1、索引的维护及优化(重复及冗余索引)

增加索引会有利于查询效率,但会降低insert,update,delete的效率,但实际上往往不是这样的,过多的索引会不但会影响使用效率,同时会影响查询效率,这是由于数据库进行查询分析时,首先要选择使用哪一个索引进行查询,如果索引过多,分析过程就会越慢,这样同样的减少查询的效率,因此我们要知道如何增加,有时候要知道维护和删除不需要的索引

 

2、如何找到重复和冗余的索引

重复索引:

重复索引是指相同的列以相同的顺序简历的同类型的索引,如下表中的 primary key和ID列上的索引就是重复索引

create table test(

id int not null primary key,

name varchar(10) not null,

title varchar(50) not null,

unique(id)

)engine=innodb;

 

冗余索引:

冗余索引是指多个索引的前缀列相同,或是在联合索引中包含了主键的索引,下面这个例子中key(name,id)就是一个冗余索引。

 

create table test(

id int not null primary key,

name varchar(10) not null,

title varchar(50) not null,

key(name,id)

)engine=innodb;

说明:对于innodb来说,每一个索引后面,实际上都会包含主键,这时候我们建立的联合索引,又人为的把主键包含进去,那么这个时候就是一个冗余索引。

 

3、如何查找重复索引

工具:使用pt-duplicate-key-checker工具检查重复及冗余索引

pt-duplicate-key-checker -uroot -padmin -h 127.0.0.1

4、索引维护的方法

由于业务变更,某些索引是后续不需要使用的,就要进行杀出。

在mysql中,目前只能通过慢查询日志配合pt-index-usage工具来进行索引使用情况的分析;

pt-index-usage -uroot -padmin /var/lib/mysql/mysql-host-slow.log

附:https://www.percona.com/downloads/

5、注意事项

设计好MySql的索引可以让你的数据库飞起来,大大的提高数据库效率。设计MySql索引的时候有一下几点注意:

 

1,创建索引

 

对于查询占主要的应用来说,索引显得尤为重要。很多时候性能问题很简单的就是因为我们忘了添加索引而造成的,或者说没有添加更为有效的索引导致。如果不加

 

索引的话,那么查找任何哪怕只是一条特定的数据都会进行一次全表扫描,如果一张表的数据量很大而符合条件的结果又很少,那么不加索引会引起致命的性能下降。

但是也不是什么情况都非得建索引不可,比如性别可能就只有两个值,建索引不仅没什么优势,还会影响到更新速度,这被称为过度索引。

 

2,复合索引

比如有一条语句是这样的:select * from users where area=’beijing’ and age=22;

 

如果我们是在area和age上分别创建单个索引的话,由于mysql查询每次只能使用一个索引,所以虽然这样已经相对不做索引时全表扫描提高了很多效

 

率,但是如果在area、age两列上创建复合索引的话将带来更高的效率。如果我们创建了(area, age,salary)的复合索引,那么其实相当于创建了(area,age,salary)、(area,age)、(area)三个索引,这被称为最佳左前缀特性。

因此我们在创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减。

 

3,索引不会包含有NULL值的列

 

只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。

 

4,使用短索引

 

对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的 列,如果在前10 个或20 个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

 

5,排序的索引问题

 

mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

 

6,like语句操作

 

一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。

 

7,不要在列上进行运算

 

select * from users where

 

YEAR(adddate)

 

8,不使用NOT IN和操作

 

NOT IN和操作都不会使用索引将进行全表扫描。NOT IN可以NOT EXISTS代替,id3则可使用id>3 or id

 

 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 自定义皮肤 设计师:あずにゃん 返回首页