****************** 如有侵权请提示删除 *******************
MySQL的主要配置文件:
①二进制日志log-bin:用于主从复制。
②错误日志log-error:默认关闭,记录严重的警告和错误信息,每次启动和关闭的详细信息等。
③查询日志show-log:默认关闭,记录查询的sql语句,如果开启会降低mysql的整体性能,因为记录日志也是需要消耗系统资源的。
④frm文件:存放表结构。
⑤myd文件:存放表数据。
⑥myi文件:存放表索引。
特别提出MySQL中的重要配置文件:Windows下名为my.ini,Linux下为/etc/my.cnf。对于服务器的调优相关过程都在改配置文件中,需要特别掌握。
MySQL是架构非常优良,主要体现在存储引擎上。MySQL是插件式的存储引擎,它可以将查询处理和其他的系统任务以及数据的存储提取相分离。
MySQL的逻辑框架主要分为四层:
①连接层;
②服务层(主要进行sql语句相关的操作);
③引擎层(注意引擎层是可拔插的);
④存储层。
通过分层和可插拔式的架构,可以根据不同的生产环境构建最优的系统。
存储引擎Storage engine:MySQL中的数据、索引以及其他对象是如何存储的,是一套文件系统的实现。
常用的存储引擎有以下:
~ | MyISAM | Innodb |
---|---|---|
存储结构 | 每张表被存放在三个文件:frm-表格定义、MYD(MYData)-数据文件、MYI(MYIndex)-索引文件 | 所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB |
存储空间 | MyISAM可被压缩,存储空间较小 | InnoDB的表需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引 |
可移植性、备份及恢复 | 由于MyISAM的数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作 | 免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了 |
文件格式 | 数据和索引是分别存储的,数据.MYD,索引.MYI | 数据和索引是集中存储的,.ibd |
记录存储顺序 | 按记录插入顺序保存 | 按主键大小有序插入 |
外键 | 不支持 | 支持 |
事务 | 不支持 | 支持(MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一) |
锁支持 (锁是避免资源争用的一个机制,MySQL锁对用户几乎是透明的) | 最小的粒度是表级锁,一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞 | 行级锁定、表级锁定,锁定力度小并发能力高,这也是 MySQL 将默认存储引擎从 MyISAM 变成InnoDB 的重要原因之一 |
SELECT | MyISAM更优 | ~ |
INSERT、UPDATE、DELETE | InnoDB更优 | ~ |
select count(*) | myisam更快,因为myisam内部维护了一个计数器,可以直接调取。速度很快 | InnoDB不保存表的具体行数,所以执行该语句时需要全表扫描 |
索引的实现方式 | B+树索引,myisam 是堆表 | B+树索引,Innodb 是索引组织表 |
哈希索引 | 不支持 | 支持 |
全文索引 | 支持 | 不支持 |
插入缓冲(insert buffer)
二次写(double write)
自适应哈希索引(ahi)
预读(read ahead)
如果没有特别的需求,使用默认的Innodb即可。
MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。
Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。
Percona 为mysql 数据库服务进行了改进,提升了在高负载情况下的IInnoDB的性能,提供了很多性能诊断工具,参数命令等。
该公司新建了一款XtraDB引擎,可完全替代InnoDB,性能和并发更好。
阿里大部分mysql 数据库使用的Percona原型加以修改
Alisql AliRedis
索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。
1、索引是在存储引擎中实现的,也就是说不同的存储引擎,会使用不同的索引
2、MyISAM和InnoDB存储引擎:只支持BTREE索引, 也就是说默认使用BTREE,不能够更换
3、MEMORY/HEAP存储引擎:支持HASH和BTREE索引
①BTREE索引;②Hash索引;③Full-Text索引;④R-Tree索引。
这里重点介绍以下两种:
BTree索引的限制
**Hash索引的限制
# 直接创建索引
create [unique] index indexname on tablename(columnname(length));
# 修改表结构的方式添加索引
ALTER Table table_name ADD INDEX index_name ON (column(length));
#创建表的时候同时创建索引
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))
);
注:如果是char、varchar类型的字段,length可以小于字段实际长度;如果是blob、text类型,必须指定length。
#创建唯一索引
create UNIQUE INDEX indexName ON table(column(length));
#修改表结构
ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
#创建表的时候直接指定
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))
);
);
#一般是在建表的时候同时创建主键索引,一个表只能有一个主键,不允许有空值
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) NOT NULL ,
PRIMARY KEY (`id`)
);
#建表后,添加主键索引
alter table table_name add primary key(column_name);
ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);
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) --主要用来查找文本中的关键字,而不是直接与索引中的值相比较
);
# 格式一
DROP INDEX 索引名 ON 表名;
# 格式二
ALTER TABLE 表名 DROP INDEX 索引名
SHOW INDEX FROM table_name;
主键自动建立唯一索引。
频繁作为查询条件的字段。
查询中与其他表关联的字段,外键关系建立索引。
高并发下趋向创建组合索引。
查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度。
查询中统计或分组字段。
②不需要创建索引的情况
表记录太少。(数据量太少MySQL自己就可以搞定了)
经常增删改的表。
数据重复且平均分配的字段,如国籍、性别,不适合创建索引。
频繁更新的字段不适合建立索引。
Where条件里用不到的字段不创建索引。
1、Mysql Query OPtimizer 优化器
2、Mysql常见瓶颈
explain select id,wuthor_id from article where category_id = 1 and comments > 1 order by views desc linit 1;
# 如果将 category_id、comments、views 三个字段一起建在联合索引中,那么
#按照Btree索引的工作原理,先排序category_id,但由于comments 条件是一个范围(即range),mysql 无法利用索引再对后面的views 部分进行检索,即range类型查询字段后面的索引会失效
# 所以这里的索引只需要category_id、views两个字段一起建联合索引
explain select * from class left join book on class.card = book.card;
# 结论 type 有all
# 在左连接中,左边一定都有,条件用于确定如何从右表搜索,所以需要在右表建立索引
select * from class
left join book on class.card = book.card
left join phone on book.card = phone.card;
# alter table phone add index z ('card');
# alter table book add index y ('card');
# 索引最好设置在需要经常查询的字段中
# 尽可能减少join语句中的NestedLoop的循环总次数;永远用小结果集驱动大的结果集
# 优先优化NestedLoop的内层循环
# 保证Join语句中被驱动表上Join条件字段已经被索引;
# 当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要吝啬JoinBuffer的设置
# 建表
create table staffs(
id int PRIMARY key auto_increment,
name VARCHAR(24) not null default '' comment '姓名',
age int not null default 0 comment '年龄',
pos VARCHAR(20) not null default '' comment '职位',
add_time TIMESTAMP not null default CURRENT_TIMESTAMP comment '入职时间'
)charset utf8 comment '员工记录数';
insert into staffs(name,age,pos,add_time) values ('z3',22,'manager',NOW());
insert into staffs(name,age,pos,add_time) values ('July',23,'dev',NOW());
insert into staffs(name,age,pos,add_time) values ('2000',22,'dev',NOW());
alter table staffs add index idx_staffs_nameAgePos(name,age,pos); ```
1. 全值匹配
2. 最佳左前缀法则(带头索引不能死,中间索引不能断)
3. 不要在索引上做任何操作(计算、函数、自动/手动类型转换),不然会导致索引失效而转向全表扫描
4. mysql存储引擎不能继续使用索引中范围条件(bettween、<、>、in等)右边的列
5. 尽量使用覆盖索引(只查询索引的列(索引列和查询列一致)),减少select *
6. 索引字段上使用(!= 或者 < >)判断时,会导致索引失效而转向全表扫描
7. 索引字段上使用 is null / is not null 判断时,会导致索引失效而转向全表扫描
8. 索引字段使用like以通配符开头(‘%字符串’)时,会导致索引失效而转向全表扫描
要么%只写右边,要么使用覆盖索引可以解决
9. 索引字段是字符串,但查询时不加单引号,会导致索引失效而转向全表扫描
10. 少使用 or 时,会导致索引失效而转向全表扫描
[MySQL高级 之 索引失效与优化详解](https://blog.csdn.net/wuseyukui/article/details/72312574)
#### 优化口诀:
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE百分写最右,覆盖索引不写星;
不等空值还有or,索引失效要少用;
#### 优化建议
1. 对于单键索引,尽量选择针对当前query过滤性更好的索引
2. 在选择组合索引时,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。
3. 在选择组合索引时,尽量选择可以包含当前query中的where子句中更多字段的索引
4. 尽可能通过分析统计信息和调整query的写法来达到选择索引的目的
#### 查询截取分析
- 观察生产环境慢SQL产生的情况
- 开启慢查询日志,设置阙值,比如超过5秒就是慢SQL,并抓取出来
- explain + 慢sql分析
- show profile 查询sql在mysql服务器里面的执行细节和生命周期情况
- SQL数据库服务器的参数调优
### 查询优化
#### 1. 永远小表驱动大表
```sql
#当B表数据集小于A表的数据集时,用in优于exists
select * from A where id in (select id from B)
#当A表的数据集小于B表时,用exists优于in
select * from A where exists (select 1 from B where B.id = A.id)
#A、B表的id字段应该建立索引
# Mysql 两种排序:文件排序或扫描有序索引排序
#Mysql 能为排序和查询使用相同的索引
#假设 索引 a_b_c(a,b,c)
#order by 能使用最左前缀
order by a
order by a,b
order by a,b,c
order by a DESC,b DESC,c DESC
#如果where使用索引的最左前缀定义为常量,则order by 能使用索引
where a = const order by b,c
where a = const and b = const order by c
where a= const and b > const order by b,c
#不能使用索引进行排序的如下:
order by a asc, b desc , c desc /*排序不一致*/
where g= const order by b,c /*丢失a索引*/
where a= const order by c /*丢失b索引,索引断裂*/
where a= const order by a,d /*d不是索引的一部分*/
where a in (...) order by b,c /*对于排序来说,对个相等条件也是范围查询*/
优化与order by 类似
定义:
前言:使用脚本进行大数据量的批量插入,对特定情况下测试数据集的建立非常有用。
#1.创建tb_dept_bigdata(部门表)。
create table tb_dept_bigdata(
id int unsigned primary key auto_increment,
deptno mediumint unsigned not null default 0,
dname varchar(20) not null default '',
loc varchar(13) not null default ''
)engine=innodb default charset=utf8 comment='部门表';
#2.创建tb_emp_bigdata(员工表)。
create table tb_emp_bigdata(
id int unsigned primary key auto_increment,
empno mediumint unsigned not null default 0 comment '编号',/*编号*/
empname varchar(20) not null default '' comment '名字',/*名字*/
job varchar(9) not null default '' comment '工作',/*工作*/
mgr mediumint unsigned not null default 0 comment '上级编号',/*上级编号*/
hiredate date not null comment '入职时间',/*入职时间*/
sal decimal(7,2) not null comment '薪水',/*薪水*/
comm decimal(7,2) not null comment '红利',/*红利*/
deptno mediumint unsigned not null default 0 comment '部门编号'/*部门编号*/
)engine=innodb default charset=utf8 comment='员工表';
#由于在创建函数时,可能会报:This function has none of DETERMINISTIC.....因此我们需开启函数创建的信任功能。
show variables like 'log_bin_trust_function_creators';
set global log_bin_trust_function_creators=1;
# 这样设置参数,mysql重启后,参数会消失
#所以,也可通过在my.cnf中永久配置的方式开启该功能
#windows 下my.ini 加上log_bin_trust_function_creators=1
#Linux 下 /etc/my.cnf 下my.cnf 加上log_bin_trust_function_creators=1;
#1.创建随机生成字符串的函数。
delimiter $$
drop function if exists rand_string;
create function rand_string(n int) returns varchar(255)
begin
declare chars_str varchar(52) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
declare return_str varchar(255) default '';
declare i int default 0;
while i<n do
set return_str=concat(return_str,substring(chars_str,floor(1+rand()*52),1));
set i=i+1;
end while;
return return_str;
end $$
#2.创建随机生成编号的函数。
delimiter $$
drop function if exists rand_num;
create function rand_num() returns int(5)
begin
declare i int default 0;
set i=floor(100+rand()*100);
return i;
end $$
#1.创建往tb_dept_bigdata表中插入数据的存储过程。
delimiter $$
drop procedure if exists insert_dept;
create procedure insert_dept(in start int(10),in max_num int(10))
begin
declare i int default 0;
set autocommit=0;
repeat
set i=i+1;
insert into tb_dept_bigdata (deptno,dname,loc) values(rand_num(),rand_string(10),rand_string(8));
until i=max_num
end repeat;
commit;
end $$
#2.创建往tb_emp_bigdata表中插入数据的存储过程。
delimiter $$
drop procedure if exists insert_emp;
create procedure insert_emp(in start int(10),in max_num int(10))
begin
declare i int default 0;
set autocommit=0;
repeat
set i=i+1;
insert into tb_emp_bigdata (empno,empname,job,mgr,hiredate,sal,comm,deptno) values((start+i),rand_string(6),'developer',0001,curdate(),2000,400,rand_num());
until i=max_num
end repeat;
commit;
end $$
#1.首先执行随机生成字符串的函数。
#2.然后执行随机生成编号的函数。
#3.查看函数是否创建成功。
show FUNCTION status;
#4.执行插入数据的存储过程,并查看其创建情况。
#恢复分好结束标识
delimiter;
#首先执行insert_dept存储过程
call insert_dept(100,100);
SELECT count(*) from tb_dept_bigdata;
#然后执行insert_emp存储过程
delimiter;
call insert_emp(100,300);
SELECT count(*) from tb_emp_bigdata;
#5.执行存储过程,插入数据。
# 删除函数
drop function rand_num;
drop function rand_string;
#删除存储过程
drop procedure insert_dept;
drop procedure insert_emp;
前言:Show Profile是mysql提供的可以用来分析当前会话中sql语句执行的资源消耗情况的工具,可用于sql调优的测量。默认情况下处于关闭状态,并保存最近15次的运行结果。
show variables like 'profiling';
set profiling=on;
show variables like 'profiling';
select *from tb_emp_bigdata group by id%10 limit 150000;
select *from tb_emp_bigdata group by id%20 order by 5;
show profiles;
show profile cpu,block io for query Query_ID;/*Query_ID为#3步骤中show profiles列表中的Query_ID*/
#比如执行:show profile cpu,block io for query 15;
show profile cpu,block io for query 15;
5.show profile的常用查询参数。
①ALL:显示所有的开销信息。
②BLOCK IO:显示块IO开销。
③CONTEXT SWITCHES:上下文切换开销。
④CPU:显示CPU开销信息。
⑤IPC:显示发送和接收开销信息。
⑥MEMORY:显示内存开销信息。
⑦PAGE FAULTS:显示页面错误开销信息。
⑧SOURCE:显示和Source_function,Source_file,Source_line相关的开销信息。
⑨SWAPS:显示交换次数开销信息。
②Creating tmp table:创建临时表。先拷贝数据到临时表,用完后再删除临时表。
③Copying to tmp table on disk:把内存中临时表复制到磁盘上,危险!!!
④locked。
如果在show profile诊断结果中出现了以上4条结果中的任何一条,则sql语句需要优化。
show profile默认是关闭的,并且开启后只存活于当前会话,也就说每次使用前都需要开启。
通过show profiles查看sql语句的耗时时间,然后通过show profile命令对耗时时间长的sql语句进行诊断。
注意show profile诊断结果中出现相关字段的含义,判断是否需要优化sql语句。
可更多的关注MySQL官方文档,获取更多的知识。
前言:全局查询日志用于保存所有的sql执行记录,该功能主要用于测试环境,在生产环境中永远不要开启该功能。
# 开启全局查询日志,测试时开启,生成环境绝不能开启
general_log=1
general_log_file=/usr/local/mysql-5.7.22/data/log/globalquerylog/log_glocalquery.log
注:对my.cnf文件配置后,需重启mysql。
#1通过命令查看全局查询日志是否开启成功。
show variables like '%general%';
#2查看全log_globalquery.log文件中的内容。
cat log_glocalquery.log
set global general log=1;
set global log_output='TABLE';
通过以上配置,执行过的sql语句将会记录到mysql库中general_log表里。
SELECT * FROM mysql.general_log;
①通过命令方式开启该功能,重启mysql后失效。
②全局查询日志只用在测试环境,切记生产环境中永远不要开启该功能。
数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外。MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应用场景特点都不太一样,为了满足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。MySQL各存储引擎使用了三种类型(级别)的锁定机制:表级锁定,行级锁定和页级锁定。
表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并发度大打折扣。
使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。
行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
使用行级锁定的主要是InnoDB存储引擎。
页级锁定是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
在数据库实现资源锁定的过程中,随着锁定资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂。不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。
使用页级锁定的主要是BerkeleyDB存储引擎。
总的来说,MySQL这3种锁的特性可大致归纳如下:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
适用:从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。
引擎 | 行锁 | 表锁 | 页锁 |
---|---|---|---|
MyISAM | 不 | 支持 | 不 |
BDB | 不 | 支持 | 支持 |
InnoDB | 支持 | 支持 | 不 |
由于MyISAM存储引擎使用的锁定机制完全是由MySQL提供的表级锁定实现,所以下面我们将以MyISAM存储引擎作为示例存储引擎。
MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。
锁模式的兼容性:
对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;
MyISAM表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
MyISAM在执行查询语句**(SELECT)前**,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。
# 手动增加表锁
lock table 表名字 read(write), 表名字2 read(write),其他;
#解锁
unlock table 表名;
#查看表上加的锁,**In_use显示不为0,则表示表被加锁**
show open tables;
对于MyISAM存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁所带来的附加成本都要小,锁定本身所消耗的资源也是最少。但是由于锁定的颗粒度比较大,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。所以,在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。
思路:
由于锁定级别是不可能改变的了,所以我们首先需要尽可能让锁定的时间变短,然后就是让可能并发进行的操作尽可能的并发。
MySQL内部有两组专门的状态变量记录系统内部锁资源争用情况:
检查分析table_locks_waited 和 table_locks_immediate 状态变量
#使用如下命令查看是否有表被锁定
show open tables where In_use>0;
#使用如下命令分析表锁
show status like 'table%';
两个状态值都是从系统启动后开始记录,出现一次对应的事件则数量加1。如果这里的Table_locks_waited状态值比较高,那么说明系统中表级锁定争用现象比较严重,就需要进一步分析为什么会有较多的锁定资源争用了。
如何让锁定时间尽可能的短呢?唯一的办法就是让我们的Query执行时间尽可能的短。
a) 尽量减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行;
b) 尽可能的建立足够高效的索引,让数据检索更迅速;
c) 尽量让MyISAM存储引擎的表只存放必要的信息,控制字段类型;
d) 利用合适的机会优化MyISAM表数据文件。
说到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语句来整理空间碎片,收回因删除记录而产生的中间空洞。
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的锁定特性。
总的来说,InnoDB的锁定机制和Oracle数据库有不少相似之处。InnoDB的行级锁定同样分为两种类型,共享锁和排他锁,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。
当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。所以,可以说InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),我们可以通过以下表格来总结上面这四种所的共存逻辑关系:
模式 | 共享锁(S) | 排他锁(X) | 意向共享锁(IS) | 意向排他锁(IX) |
---|---|---|---|---|
共享锁(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方式获得排他锁。
InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。下面通过一些实际例子来加以说明。
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,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使用间隙锁的目的:
很显然,在使用范围条件检索并锁定记录时,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。
除了间隙锁给InnoDB带来性能的负面影响之外,通过索引实现锁定的方式还存在其他几个较大的性能隐患:
因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁。
上文讲过,MyISAM表锁是deadlock free的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,当两个事务都需要获得对方持有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。
在InnoDB的事务管理和锁定机制中,有专门检测死锁的机制,会在系统中产生死锁之后的很短时间内就检测到该死锁的存在。当InnoDB检测到系统中产生了死锁之后,InnoDB会通过相应的判断来选这产生死锁的两个事务中较小的事务来回滚,而让另外一个较大的事务成功完成。
那InnoDB是以什么来为标准判定事务的大小的呢?MySQL官方手册中也提到了这个问题,实际上在InnoDB发现死锁之后,会计算出两个事务各自插入、更新或者删除的数据量来判定两个事务的大小。也就是说哪个事务所改变的记录条数越多,在死锁中就越不会被回滚掉。
但是有一点需要注意的就是,当产生死锁的场景中涉及到不止InnoDB存储引擎的时候,InnoDB是没办法检测到该死锁的,这时候就只能通过锁定超时限制参数InnoDB_lock_wait_timeout来解决。
需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。
通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。下面就通过实例来介绍几种避免死锁的常用方法:
对于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;
InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势了。但是,InnoDB的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让InnoDB的整体性能表现不仅不能比MyISAM高,甚至可能会更差。
(1)要想合理利用InnoDB的行级锁定,做到扬长避短,我们必须做好以下工作:
①尽可能让所有数据都通过索引来完成,避免无索引行升级为表锁。
②合理设计索引,尽量缩小锁的范围。
③尽可能使用较少的检索条件,避免间隙锁。
④尽量控制事务大小,减少锁定资源量和时间长度。
⑤尽可能降低事务隔离级别。
(2)由于InnoDB的行级锁定和事务性,所以肯定会产生死锁,下面是一些比较常用的减少死锁产生概率的小建议:
(3)可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:
show status like 'innodb_row_lock%';
各个状态量说明:
①Innodb_row_lock_current_waits:当前正在等待锁定的数量。
②Innodb_row_lock_time:从系统启动到现在锁定的时长。(重要)
③Innodb_row_lock_time_avg:每次等待锁所花平均时间。(重要)
④Innodb_row_lock_time_max:从系统启动到现在锁等待最长的一次所花的时间。
⑤Innodb_row_lock_waits:系统启动后到现在总共等待锁的次数。(重要)
前言:本章主要讲解MySQL主从复制的操作步骤。由于环境限制,主机使用Windows环境,从机使用用Linux环境。另外MySQL的版本最好一致,笔者采用的MySQL5.7.22版本,具体安装过程请查询相关资料。
slave会从master读取binlog来进行数据同步。主要有以下三个步骤:
①master将改变记录到二进制日志(binary log),这些记录过程叫做二进制日志事件(binary log events)。
②slave将master的binary log events拷贝到中继日志(relay log)。
③slave重做中继日志中的事件,将改变应用到自己的数据库中。MySQL的复制是异步且串行化的。
①每个slave只能有一个master。(一对一)
②每个slave只能有一个唯一的服务器ID。
③每个master可以有多个slave。(一对多)
在主从复制过程中,最大的问题就是延时。
MySQL最好版本一致且后台以服务运行。并且保证主机与从机互相ping通。主从配置都在(mysqld)结点下,都是小写。
①server-id=1,主机服务器id。(必须)
②必须启用二进制文件。
log-bin="E:\devSoft\mysql-5.7.22-winx64\data\mysql-bin"
配置该项后,重新启动mysql服务,出现mysql-bin.00001和mysql-bin.index文件
③启用错误日志。(可选)
log_error ="E:\devSoft\mysql-5.7.22-winx64\data\log\errorlog\log_error.log"
④根目录、数据目录。(可选)
#mysql安装根目录
basedir ="E:\devSoft\mysql-5.7.22-winx64"
#mysql数据文件所在位置
datadir ="E:\devSoft\mysql-5.7.22-winx64\data"
⑤临时目录。(可选)
tmpdir ="E:\devSoft\mysql-5.7.22-winx64\"
⑥read-only=0,表示主机读写都可以。
⑦可设置不需要复制的数据库。(可选)
binlog-ignore-db=mysql
⑧可设置需要复制的数据库。(可选)
binlog-do-db=databasename
#3.从机修改my.cnf配置文件
①从服务器ID。(必须)
②启用二进制日志。(可选)
#4.主机与从机都关闭防火墙,其实也可配置ip规则,但关闭防火墙更快速。
#5.在Windows主机上建立账户并授权给slave。
a.首先在主机上创建账户:
#%表示任何客户端都可以连接
grant all privileges on *.* to slaveaccount(用户名)@"%(或者指定ip)" identified by '你想设置的密码' with grant option;
b.然后刷新权限表:
flush privileges;
c.然后授权给slave:
GRANT REPLICATION SLAVE ON *.* TO 'slaveaccount(上面创建的用户名)'@'从机数据库ip' IDENTIFIED BY '你想设置的密码'
d.利用b步骤命令再次刷新权限表。
show mastet status;
File和Position这两个字段非常重要,File告诉从机需要从哪个文件进行复制,Position告诉从机从文件的哪个位置开始复制,在从机上配置时需用到。执行完此操作后,尽量不要在操作主服务器MySQL,防止主服务器状态变化(File和Position状态变化)。
CHANGE MASTER TO MASTER_HOST='主机IP',
MASTER_USER='salveaccount',
MASTER_PASSWORD='主机授权的密码',
MASTER_LOG_FILE='File名字',
MASTER_LOG_POS=Position数字;
start slave;
启动复制功能后,需要查看主从复制配置是否成功。
show slave status\G
注:只有当Slave_IO_Running:Yes和Slave_SQL_Running:Yes,这两个都为Yes的时候,主从复制配置才成功。
#9.进行测试
①首先在主机上建立数据库并插入数据。
②在从机中查看是否有相应数据库。
通过从机上查看相应数据,可知主从复制配置成功。
#10.停止从服务复制功能
stop slave;
#2.这里将主从机的防火墙都关闭是为了更好的演示,实际生产环境中一般不会出现windows主机和linux从机这种情况,因此不应该关闭防火墙,而是根据具体情况配置防火墙规则。
FROM
ON
WHERE
GROUP BY
HAVING
SELECT
DISTINCT
ORDER BY
LIMIT
参考:
MySQL高级知识(一)——基础 - developer_chan - 博客园