MySQL性能调优,SQL优化、索引优化
当查询超过一定的时间没有返回结果的时候,才会记录到慢查询日志中。默认不开启。
采样的时候手工开启。可以帮助我们找出执行慢的 SQL 语句
show variables like 'slow_query_log';
2.查看执行慢于多少秒的 SQL 会记录到日志文件中
show variables like 'long_query_time';
3.可以使用模糊搜索,查看所有含有 query 的变量信息
show variables like '%query%';
my.ini(Linux 下文件名为 my.cnf),查找到[mysqld]区段,增加日志的配置。
Windows 下路径一般为 C:\ProgramData\MySQL\MySQL Server 5.7\my.ini",可以在启动参数中查看使用的是那个配置文件
#--是否开启慢查询日志 1或ON开启慢查询日志,0或OFF关闭慢查询日志
slow_query_log=1
# --指定保存路径及文件名,默认为数据文件目录,
slow_query_log_file="bxg_mysql_slow.log"
# --指定多少秒返回查询的结果为慢查询
long_query_time=1
# --记录所有没有使用到索引的查询语句
log_queries_not_using_indexes=1
#--记录那些由于查找了多于 1000 次而引发的慢查询
min_examined_row_limit=1000
# --记录那些慢的 optimize table,analyze table 和 alter table 语句
log_slow_admin_statements=1
#--记录由 Slave 所产生的慢查询
log_slow_slave_statements=1
datadir=C:/ProgramData/MySQL/MySQL Server 5.7\Data --数据文件目录注
注意:修改以上参数,需要重新启动数据库服务才会生效。
方式二:令行修改慢查询配置(立即生效无需重启,但重启之后会自动失效,因为每次重启都是从配置文件加载配置信息的)
set global slow_query_log=1; #1或ON开启慢查询日志,0或OFF关闭慢查询日志
set global slow_query_log_file='bxg_mysql_slow.log';
set long_query_time=1;
set global log_queries_not_using_indexes=1;
set global min_examined_row_limit=1000;
set global log_slow_admin_statements=1;
set global log_slow_slave_statements=1;
时间、主机信息、执行信息、执行时间、执行内容
Query Cache 会缓存 select 查询,安装时默认是开启的,但是如果对表进行 INSERT, UPDATE, DELETE, TRUNCATE, ALTER TABLE, DROP TABLE, or DROP DATABASE 等操作时,之前的缓存会无效并且删除。这样一定程度上也会影响我们数据库的性能。所以对一些频繁的变动表的情况开启缓存是不明智的。还有一种情况我们测试数据库性能的时候也要关闭缓存,避免缓存对我们测试数据的影响。
查询 查询缓存是否开启
show VARIABLES like '%query_cache_type%';
查看缓存命中情况
show status like '%qcache%';
关闭缓存有两种放法,一种临时的,一种永久的。
1.临时的直接在命令行执行
set global query_cache_size=0;
set global query_cache_type=0;
注意:如果配置文件中为关闭缓存的话,不能通过命令开启缓存
2.永久的修改配置文件 my.cnf ,添加下面的配置即可。
query_cache_type=0
query_cache_size=0
另外,我们还可以通过 sql_no_cache 关键字在 sql 语句中直接禁用缓存,在开启缓存的情况下我们对 sql 语句做一些改动
Select sql_no_cache count(*) from test; -- 不缓存
Select sql_cache count(*) from test; -- 缓存(也可以不加,默认缓存已经开启了)
-- 用户表
CREATE TABLE `person` (
`id` BIGINT (20) UNSIGNED NOT NULL,
`fname` VARCHAR (100) NOT NULL,
`lname` VARCHAR (100) NOT NULL,
`age` TINYINT (3) UNSIGNED NOT NULL,
`sex` TINYINT (1) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8;
-- 用户部门表
CREATE TABLE `department` (
`id` BIGINT (20) UNSIGNED NOT NULL,
`department` VARCHAR (100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8;
-- 用户住址表
CREATE TABLE `address` (
`id` BIGINT (20) UNSIGNED NOT NULL,
`address` VARCHAR (100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8;
delimiter $$
drop procedure if exists generate;
CREATE DEFINER=`root`@`localhost` PROCEDURE `generate`(IN num INT)
BEGIN
DECLARE chars VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
DECLARE fname VARCHAR(10) DEFAULT '';
DECLARE lname VARCHAR(25) DEFAULT '';
DECLARE id int UNSIGNED; DECLARE len int; set id=1;
DELETE from person;
WHILE id <= num DO
set len = FLOOR(1 + RAND()*10);
set fname = '';
WHILE len > 0 DO
SET fname = CONCAT(fname,substring(chars,FLOOR(1 + RAND()*62),1)); SET len = len - 1; END WHILE;
set len = FLOOR(1+RAND()*25);
set lname = '';
WHILE len > 0 DO
SET lname = CONCAT(lname,SUBSTR(chars,FLOOR(1 + RAND()*62),1)); SET len = len - 1;
END WHILE;
INSERT into person VALUES (id,fname,lname, FLOOR(RAND()*100),
FLOOR(RAND()*2)); set id = id + 1; END WHILE; END $$
delimiter $$
drop procedure if exists genDepAdd;
CREATE DEFINER=`root`@`localhost` PROCEDURE `genDepAdd`(IN num INT)
BEGIN
DECLARE chars VARCHAR(100) DEFAULT '行政技术研发财务人事开发公关推广营销咨询客服运营测试';
DECLARE chars2 VARCHAR(100) DEFAULT '北京上海青岛重庆成都安徽福建浙江杭州深圳温州内蒙古天津河北西安三期';
DECLARE depart VARCHAR(10) DEFAULT '';
DECLARE address VARCHAR(25) DEFAULT '';
DECLARE id int UNSIGNED; DECLARE len int; set id=1;
WHILE id <= num DO
set len = FLOOR(2 + RAND()*2);
set depart = '';
WHILE len > 0 DO
SET depart = CONCAT(depart,substring(chars,FLOOR(1 + RAND()*26),1)); SET len = len - 1; END WHILE;
set depart=CONCAT(depart,'部');
set len = FLOOR(6+RAND()*18);
set address = '';
WHILE len > 0 DO
SET address = CONCAT(address,SUBSTR(chars2,FLOOR(1 + RAND()*33),1)); SET len = len - 1;
END WHILE;
INSERT into department VALUES (id,depart); INSERT into address VALUES (id,address);
set id = id + 1; END WHILE;
END $$
-- 为了提高速度,可以暂停事务。测试添加 100 万随机数据,大概 600s 左右时间。
-- 停掉事务
set autocommit = 0;
-- 调用存储过程
call generate(1000000);
-- call genDepAdd(1000000);
-- 重启事务
set autocommit = 1;
对比 MyIsam:当创建表时选择 MyIsam 格式,插入数据会很慢,仅仅 3000 条数据就需要 2 分钟的时间,由此可见 MyIsam 和 InnoDB 的差距还是很大的。另外在执行过程中可以发现 MyIsam 插入的数据可以在表中实时看到,而 InnoDB 做了事务最终一次提交,所以数据不能实时看到,只有存储过程全部执行完成后才可以看到数据。
Windows 下需要下载安装 perl 编译器,下载地址:http://pan.baidu.com/s/1i3GLKAp
使用参考:https://www.cnblogs.com/moss_tan_jun/p/8025504.html
mysqldumpslow --help可显示其参数的使用
经常使用的参数:
-s,是order的顺序
al 平均锁定时间
ar 平均返回记录时间
at 平均查询时间(默认)
c 计数
l 锁定时间
r 返回记录
t 查询时间
-t,是top n的意思,即为返回前面多少条的数据
-g,后边可以写一个正则匹配模式,大小写不敏感的
是 daniel-nichter 用 perl 写的一个脚本,专门用于处理分析 Mysql 的日志而存在。通过 Mysql 的日志主要分为:General log,slow log,binary log 三种。通 过 query 日志,我们可以分析业务的逻辑,业务特点。通过 slow log,我们可以找到服务器的瓶颈。通过 binary log,我们可以恢复数据。Mysqlsla 可以处理其中的任意日志。
使用参考:https://yq.aliyun.com/articles/59260
pt-query-digest 是用于分析 mysql 慢查询的一个工具,它可以分析 binlog、General log、slowlog,也可以通过 SHOWPROCESSLIST 或者通过 tcpdump 抓取的 MySQL 协议
数据来进行分析。可以把分析结果输出到文件中,分析过程是先对查询语句的条件进行参数化,然后对参数化以后的查询进行分组统计,统计出各查询的执行时间、次数、占比等,可以借助分析结果找出问题进行优化。
使用参考:https://blog.csdn.net/seteor/article/details/24017913
EXPLAIN SELECT ……
EXPLAIN EXTENDED SELECT ……
EXPLAIN PARTITIONS SELECT ……
字段 |
说明 |
Id |
查询中执行select子句或操作表的顺序 |
Select_type |
所使用的SELECT查询类型,包括以下常见类型: SIMPLE、 PRIMARY、SUBQUERY、UNION、DERIVED、UNIONRESULT、DEPENDENT、 UNCACHEABLE |
table |
所使用的的数据表的名字 |
type |
表示MySQL在表中找到所需行的方式,又称“访问类型”。取值按优劣排序为 NULL>system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL |
Possible_keys |
可能使用哪个索引在表中找到记录 |
key |
实际使用的索引 |
Key_len |
索引中使用的字节数 |
ref |
显示索引的哪一列被使用了 |
rows |
估算的找到所需的记录所需要读取的行数 |
filtered |
通过条件过滤出的行数的百分比估计值 |
extra |
包含不适合在其他列中显示但十分重要的额外信息 |
包含一组数字,表示查询中执行 select 子句或操作表的顺序,id 相同执行顺序由上至下。如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
所使用的 SELECT 查询类型,包括以下常见类型:
查询类型 | 说明 |
SIMPLE | 表示为简单的 SELECT,查询中不包含子查询或者 UNION |
PRIMARY | 查询中若包含任何复杂的子部分,最外层查询则被标记为 PRIMARY |
SUBQUERY | 在 SELECT 或 WHERE 列 表 中 包 含 了 子 查 询 , 该 子 查 询 被 标 记 为SUBQUERY |
UNION | 表连接中的第二个或后面的 select 语句,若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION。 |
DERIVED | DERIVED(衍生)用来表示包含在 from 子句中的子查询的 select。若 UNION 包含在 FROM 子句的子查询中,外层 SELECT 将被标记为 DERIVED。mysql 会递归执行并将结果放到一个临时表中。服务器内部称为"派生表",因为该临时表是从子查询中派生出来的 |
UNION RESULT | 从 UNION 表获取结果的 SELECT 被标记为 UNION RESULT |
DEPENDENT | 意味着 select 依赖于外层查询中发现的数据。 |
UNCACHEABLE | 意味着 select 中的某些特性阻止结果被缓存于一个 item_cache中。 |
所使用的的数据表的名字,他们按被读取的先后顺序排列。
表 示 MySQL 在 表 中 找 到 所 需 行 的 方 式 , 又 称 “ 访 问 类 型 ” 。 取 值 按 优 劣 排 序 为
NULL>system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL。一般来说,得保证查询至少达到 range 级别,最好能达到 ref。
指出 MySQL 能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用
显示 MySQL 在查询中实际使用的索引,若没有使用索引,显示为 NULL
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len 显示的值为索引字段的最大可能长度,并非实际使用长度。如果键是 NULL,则长度为 NULL。
显示索引的哪一列被使用了,有时候会是一个常量:表示哪些列或常量被用于用于查找索引列上的值,可能值为库.表.字段、常量、null。
MySQL 根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数。
显示了通过条件过滤出的行数的百分比估计值。
包含不适合在其他列中显示但十分重要的额外信息,提供了与关联操作有关的信息,没有则什么都不写。
额外信息类型 | 说明 |
Using index | 该值表示相应的 select 操作中使用了覆盖索引(Covering Index)。MySQL 可以利用索引返回 select 列表中的字段,而不必根据索引再次读取数据文件包含所有满足查询需要的数据的索引称为覆盖索引(Covering Index)。注意:如果要使用覆盖索引,一定要注意 select 列表中只取出需要的列,不可 select *,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。 |
Using where | 表示 mysql 服务器将在存储引擎检索行后再进行过滤。许多 where 条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验,因此不是所有带 where 字句的查询都会显示"Using where"。有时"Using where"的出现就是一个暗示:查询可受益与不同的索引。 |
Using temporary | 表示 MySQL 需要使用临时表来存储结果集,常见于排序和分组查询。这个值表示使用了内部临时(基于内存的)表。一个查询可能用到多个临时表。有很多原因都会导致 MySQL 在执行查询期间创建临时表。两个常见的原因是在来自不同表的上使用了 DISTINCT,或者使用了不同的 ORDER BY 和 GROUP BY 列。可以强制指定一个临时表使用基于磁盘的 MyISAM 存储引擎。这样做的原因主要有两个:1)内部临时表占用的 空 间 超 过 min(tmp_table_size , max_heap_table_size) 系 统 变 量 的 限 制 ; 2) 使 用 了TEXT/BLOB 列。 |
Using filesort | MySQL 中无法利用索引完成的排序操作称为“文件排序” |
Using join buffer | 改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。 |
Impossible where | 这个值强调了 where 语句会导致没有符合条件的行。 |
Select tables optimized away | 这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行. |
Index merges | 当 MySQL 决定要在一个给定的表上使用超过一个索引的时候,就会出现以下格式中的一个,详细说明使用的索引以及合并的类型。
|
要想优化一条 Query,就须要清楚这条 Query 的性能瓶颈到底在哪里,是消耗的 CPU 计算太多,还是需要的 IO 操作太多?要想能够清楚地了解这些信息,可以通过 Query Profiler 功能得到。
Query Profiler 是 MYSQL 自带的一种 query 诊断分析工具,通过它可以分析出一条SQL 语句的性能瓶颈在什么地方。通常我们是使用的 explain,以及 slow query log 都无法做到精确分析,但是 Query Profiler 却可以定位出一条 SQL 语句执行的各种资源消耗情况,比如 CPU,IO 等,以及该 SQL 执行所耗费的时间等。
1.通过执行“set profiling”命令,可以开启关闭 QueryProfiler 功能
SET global profiling=on;
2.查看相关变量
show VARIABLES like '%profiling%';
3.设置保存数量默认 15 条,最大值为 100
set profiling_history_size=100;
4.在开启 Query Profiler 功能之后,MySQL 就会自动记录所有执行的 Query 的 profile 信息,下面执行 n 条 Query 作为测试
select * from person limit 10000,100;
show profiles;
show profile cpu,block io for query 1000367;
show profile ALL for query 1000367;
|BLOCK IO :块设备 IO 输入输出次数
|CONTEXT SWITCHES:上下文切换相关开销
|CPU:用户和系统的 CPU 使用情况
|IPC:显示发送和接收消息的相关消耗
|MEMORY:内存消耗情况(该版本 is not currently implemented)
|PAGE FAULTS:显示主要和次要页面故障相关的开销
|SOURCE:显示和 Source_function,Source_file,Source_line 相关的开销信息
|SWAPS:显示交换次数相关的开销注意:profiling 被应用在每一个会话中,当前会话关闭后,profiling 统计的信息将丢失。
查上一个查询的代价,而且它是 io_cost 和 cpu_cost 的开销总和,它通常也是我们评价一个查询的执行效率的一个常用指标。last_query_cost 对于简单的查询可以精确的得到计算,但于包含子查询或 union 的复杂查询值是 0。
show status like 'last_query_cost';
这种方法有一点要注意,就是三条 sql 语句要尽量连一起执行,不然误差太大,根本不准。
set @d=now(); select id from person where lname='x8RJWmQX'; select timestampdiff(second,@d,now());
如 果 是 用 命 令 行 来 执 行 的 话 , 有 一 点 要 注 意 ,就是在select timestampdiff(second,@d,now());后面,一定要多 copy 一个空行,不然最后一个 sql 要你自己按回车执行,这样就不准了。
show processlist;
MySQL 中的数据用各种不同的技术存储在文件(或者内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术,你能够获得额外的速度或者功能,从而改善你的应用的整体功能。
这些不同的技术以及配套的相关功能在 MySQL 中被称作存储引擎(也称作表类型)。
MySQL 默认配置了许多不同的存储引擎,可以预先设置或者在 MySQL 服务器中启用。你可以选择适用于服务器、数据库和表格的存储引擎,以便在选择如何存储你的信息、如何检索这些信息以及你需要你的数据结合什么性能和功能的时候为你提供最大的灵活性。
数据库引擎是用于存储、处理和保护数据的核心服务。利用数据库引擎可控制访问权限并快速处理事务,从而满足企业内大多数需要处理大量数据的应用程序的要求。 使用数据库引擎创建用于联机事务处理或联机分析处理数据的关系数据库。这包括创建用于存储数据的表和用于查看、管理和保护数据安全的数据库对象(如索引、视图和存储过程)。
. MyIsam , 2. InnoDB, 3. Memory, 4. Blackhole, 5. CSV, 6. Performance_Schema, 7. Archive, 8. Federated , 9 Mrg_Myisam。在实际工作中用的比
较多的是 MyIsam 和 InnoDB,而 InnoDB 更因为各方面的优越性更是获得了更多的青睐。尤其是 MySQL 8.0 版本发布后,其中具有重大意义的是官方废弃了 MyISAM 存储引擎。
参考:https://www.cnblogs.com/sunsky303/p/8274586.html
show ENGINES;
在[mysqld]后面添加 default-storage-engine=InnoDB,重启服务,数据库默认的引擎修改为 InnoDB。
CREATE TABLE person2 (
id INT PRIMARY KEY,
NAME VARCHAR (50)
) ENGINE = MyISAM;
alter table person2 ENGINE =InnoDB;
person2为测试表名
show table status where name='person2';
show create table person2;
使用第三方数据库管理工具。
官方对 InnoDB 的讲解
Innodb 引擎提供了对数据库 ACID 事务(原子性 Atomicity、一致性 Consistency、隔离性 Isolation、持久性 Durability)的支持,并且实现了 SQL 标准的四种隔离级别,关于数据库事务与其隔离级别的内容请见数据库事务与其隔离级别这类型的文章。该引擎还提供了行级锁和外键约束,它的设计目标是处理大容量数据库系统,它本身其实就是基于
MySQL 后台的完整数据库系统,MySQL 运行时 Innodb 会在内存中建立缓冲池,用于缓冲数据和索引。但是该引擎不支持 FULLTEXT 类型的索引,而且它没有保存表的行数,当
SELECT COUNT(*) FROM TABLE 时需要扫描全表。当需要使用数据库事务时,该引擎当然是首选。由于锁的粒度更小,写操作不会锁定全表,所以在并发较高时,使用 Innodb 引擎会提升效率。但是使用行级锁也不是绝对的,如果在执行一个 SQL 语句时 MySQL 不能确定要扫描的范围,InnoDB 表同样会锁全表。
-- 这个就是 select 锁表的一种,不明确主键。增删改查都可能会导致锁全表。
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
概述:
MyIASM 没有提供对数据库事务的支持,也不支持行级锁和外键,因此当 INSERT(插
入)或 UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。
MyIsam 存储引擎独立于操作系统,也就是可以在 windows 上使用,也可以比较简单的将数据转移到 linux 操作系统上去。
意味着:引擎在创建表的时候,会创建三个文件,一个是.frm 文件用于存储表的定义,
一个是.MYD 文件用于存储表的数据,另一个是.MYI 文件,存储的是索引。操作系统对大文件的操作是比较慢的,这样将表分为三个文件,那么.MYD 这个文件单独来存放数据自然可以优化数据库的查询等操作。有索引管理和字段管理。MyISAM 还使用一种表格锁定的机制,来优化多个并发的读写操作,其代价是你需要经常运行 OPTIMIZE TABLE 命令,来恢复被更新机制所浪费的空间。
以下两点必须使用 InnoDB:
对比之下,MyISAM 的使用场景:
MySQL 能够允许你在表这一层应用数据库引擎,所以你可以只对需要事务处理的表格来进行性能优化,而把不需要事务处理的表格交给更加轻便的 MyISAM 引擎。对于MySQL 而言,灵活性才是关键。
从数据库里读出越多的数据,那么查询就会变得越慢。并且如果你的数据库服务器和WEB 服务器是两台独立的服务器的话,这还会增加网络传输的负载。
应尽量避免在 where 子句中使用!=或<>操作符,否则引擎放弃使用索引而进行全表扫描。
注意:当where条件是主键时使用!=或<>操作符引擎依然会使用索引
注意:当where条件不是主键时使用!=或<>操作符引擎放弃使用索引而进行全表扫描。
对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
采用 OR 语句:
select * from person where fname ='LVc1oJjd' or fname='bjRdlVo';
采用 UNION 语句,返回的结果同上面的一样,但是速度要快些:
union和union all的区别:
Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;
Union All:对两个结果集进行并集操作,包括重复行,不进行排序;
注意:UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。
注意:如果or两边的查询列是一样的话,那么用union则反倒不如用or的执行速度快。所以优化前需要自己测试
OR 语句的结果
UNION 语句的结果
我们来比较下重要指标,发现主要差别是 type 和 ref 这两项。type 显示的是访问类型,是较为重要的一个指标,结果值从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
UNION 语句的 type 值为 一般为 ref,OR 语句的 type 值为 range,可以看到这是一个很明显的差距。
UNION 语句的 ref 值为 const,OR 语句的 type 值为 null,const 表示是常量值引用,非常快。
这两项的差距就说明了 UNION 要优于 OR,从我们的直观感觉上也可以理解,虽然这两个方式都用到了索引,但 UNION 是用一个明确的值到索引中查找,目标非常明确, OR 需要对比两个值,目标相对要模糊一些,所以 OR 在恍惚中落后了。
前置百分号会导致索引失效
select * from person where fname like '%LVc1o%' ;
下面走索引
select * from person where fname like 'LVc1o%' ;
如果在 where 子句中使用参数,也会导致全表扫描。因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select * from person where substring(fname,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%’ select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1′
不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(…)
很多时候用 exists 代替 in 是一个好的选择:
mysql中的in语句是把外表和内表作hash 连接,而exists语句是对外表作loop循环,每次loop循环再对内表进行查询。一直大家都认为exists比in语句的效率要高,这种说法其实是不准确的。这个是要区分环境的。如果查询的两个表大小相当,那么用in和exists差别不大。 如果两个表中一个较小,一个是大表,则子查询表大的用exists,子查询表小的用in
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)
若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会 逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
在新建临时表时,如果 一次 性插 入数 据量很大, 那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先 create table,然后 insert。
如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置SET NOCOUNT OFF 。 无 需 在 执 行 存 储 过 程 和 触 发 器 的 每 个 语 句 后 向 客 户 端 发 送
DONEINPROC 消息。
尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索
引而进行全表扫描。如:
select id from t where num is null
可以在 num 上设置默认值 0,确保表中 num 列没有 null 值,然后这样查询: select id from t where num=0
在 Mysql5.7 版本中该条建议已经不用考虑了,因为 null 判断也能使用索引了。
最有效的办法是让驱动表的结果集尽可能地小,“永远用小结果集驱动大结果集”。
比如,当两个表(表 A 和表 B)Join 时,如果表 A 通过 WHERE 条件过滤后有 10 条
记录,而表 B 有 20 条记录。如果选择表 A 作为驱动表,也就是被驱动表的结果集为 20,那么我们通过 Join 条件对被驱动表(表 B)的比较过滤就会进行 10 次。反之,如果选择表 B 作为驱动表,则须要进行 20 次对表 A 的比较过滤。
不仅在数据库的 Join 中应该这样做,实际上在优化程序语言时也有类似的优化原则。内层循环是循环中执行次数最多的,每次循环节约很少的资源,就能在整个循环中节约很多的资源
其目的正是基于上面两点的考虑,只有让被驱动表的 Join 条件字段被索引了,才能保证循环中每次查询都能够消耗较少的资源,这也正是内层循环的实际优化方法
当无法保证被驱动表的 Join 条件字段被索引且内存资源充足时,不要太吝惜 Join
Buffer 的设置。在 Join 是 All、Index、range 或 index_merge 类型的特殊情况下,Join Buffer 才能派上用场。在这种情况下,Join Buffer 的大小将对整个 Join 语句的消耗起到非常关键的作用
使用 limit 进行分页,翻到 10000 多页后效率低。原因在于 limit offset 会逐行查找,是先查询再跳过。
select * from person limit 999900,100; -- 慢了,大概需要 0.4 秒多
方式一:业务逻辑方面优化,不允许翻过 100 页,例如百度一般可以翻到 70 页左右
方式二:技术方面优化
select * from person where id>999900 limit 100;
这样就非常快,0.001s 左右,因为使用了 id 索引
但这样用有前提,id 是连续的,中间的数据不能删,否则 id 为 999900 的并不是第999900 个记录。
方式三:技术方面优化
如果必须用 limit offset 查询,就用延迟关联 select id from person limit 999900 ,100;
这样只查询 id 列,实现了索引覆盖,就会很快
select p.* from person p inner join (select id from person limit 999900 ,100) as tmp on p.id=tmp.id;
通过内连接再获取分页后每条记录的详细信息
这个问题须要从对整个系统的影响来考虑。哪个 Query 的优化能给系统整体带来更大
的收益,就更须要优化。一般来说,高并发低消耗的影响 > 低并发高消耗
假设有一个 Query 每小时执行 10000 次,每次需要 20 个 IO,而另外一个 Query 每小时执行 10 次,每次需要 20000 个 IO。
两个 Query 每小时所消耗的 IO 总数目是一样的,都是 200000 IO/小时假设优化第一个 Query,从 20 个 IO 降低到 18 个 IO,也就是降低了 2 个 IO,则节省了 2×10000 = 20000 (IO/小时)
而如果希望通过优化第二个 Query 达到相同的效果,必须要让每个 Query 减少 20000
/ 10 = 2000 IO 可以看出第一个 Query 节省 2 个 IO 即可达到第二个 Query 节省 2000 个 IO 相同的效果
原理和上面一样,只要让第一个 Query 节省一小块资源,就可以让整个系统节省出一大块资源,尤其是在排序、分组这些对 CPU 消耗比较多的操作中更加明显
一个频繁执行的高并发 Query 的危险性比一个低并发的 Query 要大很多,当一个低并发的 Query 执行计划有误时,所带来的影响只是该 Query 请求者的体验会变差,对整体系统的影响并不会特别突出,但是,如果一个高并发的 Query 执行计划有误,它带来的后果很可能就是灾难性的。
什么是索引?为什么要建立索引?
索引用于快速找出在某个列中有一特定值的行,不使用索引 MySQL 必须从第一条记录开始读完整个表,直到找出相关的行,表越大查询数据所花费的时间就越多,如果表中查询的列有一个索引,MySQL 能够快速到达一个位置去搜索数据文件,而不必查看所有数据,那么将会节省很大一部分时间。
例如:有一张 person 表,其中有 2W 条记录,记录着 2W 个人的信息。有一个 Phone 的字段记录每个人的电话号码,现在想要查询出电话号码为 xxxx 的人的信息。
如果没有索引,那么将从表中第一条记录一条条往下遍历,直到找到该条信息为止。
如果有了索引,那么会将该 Phone 字段,通过一定的方法进行存储,好让查询该字段上的信息时,能够快速找到对应的数据,而不必在遍历 2W 条数据了。其中 MySQL 中的索引的存储类型有两种:BTREE、HASH。 也就是用树或者 Hash 值来存储该字段,要知道其中详细是如何查找的,需要一定的算法知识了。
B-Tree 索引,它是目前关系型数据库中查找数据最为常用和有效的索引,大多数存储引擎都支持这种索引。使用 B-Tree 这个术语,是因为 MySQL 在 CREATE TABLE 或其它语句中使用了这个关键字,但实际上不同的存储引擎可能使用不同的数据结构,比如
InnoDB 就是使用的 B+Tree。
B+Tree 中的 B 是指 balance,意为平衡。需要注意的是,B+树索引并不能找到一个
给定键值的具体行,它找到的只是被查找数据行所在的页,接着数据库会把页读入到内存,
再在内存中进行查找,最后得到要查找的数据。
聚簇索引保证关键字的值相近的元组存储的物理位置也相同(所以字符串类型不宜建立聚簇索引,特别是随机字符串,会使得系统进行大量的移动操作),且一个表只能有一个聚簇索引。因为由存储引擎实现索引,所以,并不是所有的引擎都支持聚簇索引。
优点
缺点
通过上面说的优点和缺点,我们应该可以知道,并不是每个字段度设置索引就好,也不是索引越多越好,而是需要自己合理的使用。
并不是所有索引对查询都有效,SQL 是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL 查询可能不会去利用索引,如一表中有字段 sex,male、female 几乎各一半,那么即使在 sex 上建了索引也对查询效率起不了作用。
索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数较好不要超过 6 个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
应尽可能的避免更新 clustered 索引数据列,mysql 默认的 clustered 索引为主键,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
对经常更新的表就避免对其进行过多的索引,对经常用于查询的字段应该创建索引。
数据量小的表最好不要使用索引,因为由于数据较少,可能查询全部数据花费的时间比遍历索引的时间还要短,索引就可能不会产生优化效果。
在一不同值少的列上(字段上)不要建立索引,比如在学生表的"性别"字段上只有男,女两个不同值。相反的,在一个字段上不同值较多可以根据需要建立索引。
索引是在存储引擎中实现的,也就是说不同的存储引擎,会使用不同的索引。MyISAM 和 InnoDB 存储引擎:只支持 BTREE 索引,也就是说默认使用 BTREE,不能够更换,
MySQL5.7 中 InnoDB 可以支持 HASH 索引;MEMORY/HEAP 存储引擎:支持 HASH 和
BTREE 索引。索引可划分为单列索引(其中包括普通索引、唯一索引、主键索引)、组合索引、全文索引、空间索引,其中单列索引是一个索引只包含单个列,但一个表中可以有多个单列索引。
MySQL 中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,
纯粹为了查询数据更快一点。Index(xx) 或者 key(xx)
索引列中的值必须是唯一的,但是允许为空值,UNIQUE INDEX UniqIdx(xx)
是一种特殊的唯一索引,不允许有空值。PRIMARY KEY(id)
在表中的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀集合。INDEX MultiIdx(id,name,age)
由 id、name 和 age3 个字段构成的索引,索引行中就按 id/name/age 的顺序存放,索引可以索引下面字段组合(id,name,age)、(id,name)或者(id)。如果要查询的字段不构成索引最左面的前缀,那么就不会是用索引,比如,age 或者(name,age)组合就不会使用索引查询
全文索引,只有在 MyISAM 引擎上才能使用,只能在 CHAR,VARCHAR,TEXT 类型字段上使用全文索引。全文索引就是在一堆文字中,通过其中的某个关键字等,就能找到该字段所属的记录行,比如有"你是个大牛,神人 ..." 通过大牛,可能就可以找到该条记录。
这里说的是可能,因为全文索引的使用涉及了很多细节,我们只需要知道这个大概意思。
FULLTEXT INDEX FullTxtIdx(info) SELECT * FROM t4 WHERE MATCH(info) AGAINST('gorlr');
只有在 MyISAM 引擎上才能使用,空间索引是对空间数据类型的字段建立的索引,MySQL 中的空间数据类型有四种,GEOMETRY、POINT、LINESTRING、POLYGON。
在创建空间索引时,使用 SPATIAL 关键字。创建空间索引的列,必须将其声明为 NOT NULL。。SPATIAL INDEX spatIdx(g)
全值匹配我最爱,最左前缀要遵守;带头大哥不能死,中间兄弟不能断;索引列上少计算,范围之后全失效;
Like 百分写最右,覆盖索引不写星;不等空值还有 or,索引失效要少用;
VAR 引号不可丢,SQL 高级也不难!
参考:https://blog.csdn.net/zjy15203167987/article/details/81812370
参考: https://www.jianshu.com/p/d5b2f645d657
如果索引包含满足查询的所有数据,就称为覆盖索引。覆盖索引是一种非常强大的工具,能大大提高查询性能。只需要读取索引而不用读取数据有以下一些优点:
覆盖索引不能是任何索引,只有 B-TREE 索引存储相应的值。而且不同的存储引擎实现覆盖索引的方式都不同,并不是所有存储引擎都支持覆盖索引(Memory 和 Falcon 就不支持)。
对于索引覆盖查询(index-covered query),使用 EXPLAIN 时,可以在 Extra 一列中看到“Using index”。
产品中有一张图片表,数据量将近 100 万条,有一条相关的查询语句,由于执行频次较高,想针对此语句进行优化。表结构很简单,主要字段:
user_id 用户 ID
picname 图片名称
smallimg 小图名称
一个用户会有多条图片记录,现在有一个根据 user_id 建立的索引:uid,查询语句也很简单。取得某用户的图片集合
select picname, smallimg from pics where user_id = xxx;
执行查询语句(为了查看真实执行时间,强制不使用缓存)
select SQL_NO_CACHE picname, smallimg from pics where user_id=17853;
执行了 10 次,平均耗时在 40ms 左右。使用 explain 进行分析
explain select SQL_NO_CACHE picname, smallimg from pics where user_id=17853
使用了 user_id 的索引,并且是 const 常数查找,表示性能已经很好了
因为这个语句太简单,sql 本身没有什么优化空间,就考虑了索引。修改索引结构,建立一个(user_id,picname,smallimg)的联合索引:uid_pic。重新执行 10 次,平均耗时降到了 30ms 左右。使用 explain 进行分析
看到使用的索引变成了刚刚建立的联合索引,并且 Extra 部分显示使用了'Using Index'
总结
'Using Index'的意思是“覆盖索引”,它是使上面 sql 性能提升的关键。一个包含查询所需字段的索引称为“覆盖索引”,MySQL 只需要通过索引就可以返回查询所需要的数据,而不必在查到索引之后进行回表操作,减少 IO,提高了效率。
例如上面的 sql,查询条件是 user_id,可以使用联合索引,要查询的字段是 picname smallimg,这两个字段也在联合索引中,这就实现了“覆盖索引”,可以根据这个联合索引一次性完成查询工作,所以提升了性能
InnoDB 存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面带来的性能损耗可能比表级锁定要更高一些,但是在整体并发处理能力方面是要远远优于 MyISAM 的表级锁定的。当系统并发量较高的时候,InnoDB 的整体性能和 MyISAM 相比就会有比较明显的优势了。但是当我们使用不当的时候,可能会让 InnoDB 的整体性能表现不仅不比MyISAM 高,甚至可能会更差。
建议:
我们应该为数据库里的每张表都设置一个 ID 做为其主键,而且最好的是一个 INT 型的(推荐使用 UNSIGNED),并设置上自动增加的 AUTO_INCREMENT 标志。
就算是你users 表 有 一 个 主 键 叫 “email” 的 字 段 , 你也别让它成为主键 。 使用VARCHAR 类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的 ID 来构造你的数据结构。而且,在 MySQL 数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区……
在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比 如:有一个“学生表”有学生的 ID,有一个“课程表”有课程 ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生 ID 和课 程 ID 叫“外键”其共同组成主键。
ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。
如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。MySQL 也有一个“建议”(见第十条)告诉你怎么去重新组织你的表结构。当你有一个VARCHAR 字 段 时 , 这 个 建 议 会 告 诉 你 把 其 改 成 ENUM 类 型 。 使 用 PROCEDURE ANALYSE() 你可以得到相关的建议。
如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。
固定长度的表会提高性能,因为 MySQL 搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。使用“垂直分割”技术(见下一条),你可以分割你的表成为两个一个是定长的,一个则是不定长的。
除非你有一个很特别的原因去使用NULL值,你应该总是让你的字段保持 NOT NULL。这看起来好像有点争议,请往下看。
首先,问问你自己“Empty”和“NULL”有多大的区别(如果是 INT,那就是 0 和 NULL)? 如果你觉得它们之间没有什么区别,那么你就不要使用 NULL。不要以为 NULL 不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。 当然,这里并不是说你就不能使用 NULL 了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL 值。
InnoDB 引擎已经在多方面超越了 MyIsam 引擎,没有特殊需求的情况下建议选择InnoDB 引擎。
innodb_buffer_pool_size 参数指定了 InnoDB 可以使用的内存总量。建议设置为物理内存的 80%,因为要给操作系统留有空间。
如果你的内存是 32GB,可以设置为大约 25GB
innodb_buffer_pool_size = 25600M 注意:
innodb_buffer_pool_size 的 值大于1G 时 , innodb_buffer_pool_instances 会把InnoDB 的缓存池划分成多个实例。
多个缓冲池的好处:
多个线程同时访问缓冲池时可能会遇到瓶颈,而多个缓冲池则可以最小化这个冲突官方建议的 buffer 数量:每个 buffer pool 实例至少要 1G 例 如 内 存 为 32GB , innodb_buffer_pool_size 为 25GB , 那 么 合 适 的 方 案 就 是
25600M / 24 = 1.06GB innodb_buffer_pool_instances = 24
在 MySQL 中,排序算法分为两种,一是只加载排序字段到内存,排序完成后再到表中取其他字段,二是加载所有需要的字段到内存,显然第二种节省了 IO 操作,所以更快。决定使用哪种算法是通过参数 max_length_for_sort_data 来决定的,当所有返回字段的最大长度小于这个参数值时,MySQL 就会选择第二种算法,反之使用第一种。所以,如果有充足的内存让 MySQL 存放须要返回的非排序字段,就可以加大这个参数的值来让
MySQL 选择第二种排序算法。当内存不是很充裕时,不能简单地通过强行加大上面的参数来强迫 MySQL 去使用高效算法,否则可能会造成 MySQL 不得不将数据分成很多段,然后进行排序,这样可能会得 不 偿 失 , 此 时 就 须 要 去 掉 不 必 要 的 返 回 字 段 , 让 返 回 结 果 长 度 适 应 max_length_for_sort_data 参数的限制。
增 大 sort_buffer_size 并 不 是 为 了 让 MySQL 选 择 第 二 种 排 序 算 法 , 而是为了让MySQL 尽量减少在排序过程中对须要排序的数据进行分段,因为分段会造成 MySQL 不得不使用临时表来进行交换排序。
充分利用 Mysql 的查询缓存机制,业务中很多 SQL 都会重复执行的,当然现在很多数据层框架中也有缓存功能,但数据层框架中的缓存属于应用级别的,在数据被外部更新时会导致缓存数据过期问题。
发现网站页面打开非常慢,对处理过程简单记录了一下。
1.7.2.1. 找问题
首先登录服务器使用 top 查看当前进程信息,发现排名第一的是 mysql,占用 cpu
达到了 100% 以上,这就明确了是 mysql 的问题。
登录 mysql,使用 show processlist 查看下当前执行状态,发现了大量 LOCK 操作,
也有多个 Copying to tmp table 的操作,说明有 sql 出现了问题,操作过于复杂,对临时表使用频繁,把其他操作阻塞了。
1.7.2.2. 解决思路
找到了问题后,把处理方向确定为检查和修改配置、sql 优化。
1.7.2.3. 修改 mysql 配置
临时表既然涉及了到了临时表,就先查看下目前临时表的信息查看临时表的使用状态
show global status like 'created_tmp%'; 发现 created_tmp_disk_tables 值过高,需要增加此值。
再看一下现在临时表的大小
show variables like '%tmp_table_size%';
在现在值的基础上增加一些,重新设置临时表大小线程缓存数看当前线程情况
show global status like 'Thread%';
发现 threads_created 的值过大,表明 MySQL 服务器一直在创建线程查看当前值
show variables like 'thread_cache_size'; 此参数需要调高打开表数量查看打开表的情况
show global status like 'open%tables%'; 发现 opened_tables 数量过大,说明 table_cache 的值可能太小。
查看当前值
show variables like 'table_cache'; 此参数需要调高最大连接数查看当前允许的最大连接数 show variables like 'max_connections'; 查看服务器连接数的峰值
show global status like 'Max_used_connections'; 峰值还没到最大限制,不需要修改
join buffer 和 sort buffer 查看现有值
SELECT @@sort_buffer_size; SELECT @@join_buffer_size; 是默认值,需要修改修改配置确定了要修改的参数后,修改 my.cnf ,例如
table_cache = 64 sort_buffer_size = 8M join_buffer_size = 4M thread_cache_size = 300 thread_concurrency = 8 tmp_table_size = 246M
1.7.2.4. sql 优化从 show processlist 结果集中找出主要的复杂语句,对其进行 explain 和 profile 分析,进行索引优化,把复杂的 sql 根据业务拆分为多个小的 sql。
MySQL 主从复制是指数据可以从一个 MySQL 数据库服务器主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。
主库 master 发生故障后,可以马上切换到从库 slave,降低服务风险。
可以把写操作放在 master,读取操作放在 slave,减轻单一数据库的操作压力
随着系统中业务访问量的增大,如果是单机部署数据库,就会导致 I/O 访问频率过高。有了主从复制,增加多个数据存储节点,将负载分布在多个从节点上,降低单机磁盘 I/O 访问的频率,提高单个机器的 I/O 性能。
master 记录下自己的操作日志,授权从服务器可以读取操作日志,slave 会开启两个线程。
1、IO 线程
负责连接 master 连接成功后,睡眠并等待 master 产生新的事件,有新的就保存到自己的中继日志中,中继日志通常位于操作系统的缓存中,所以开销很小。
2、sql 进程
负责执行中继日志中的 sql 操作,这样 slave 的内容就和 master 的一致了。
前提条件:停止对 master 数据库的操作,把 master 中的数据库全部导入到 slave,使两边数据库完全一致。
修改 master 的配置文件,使用二进制日志,指定 server-id,重启服务。目的是让各自都有了自己的唯一标示,并以二进制文件格式进行交流。Centos 中路径为 /etc/my.cnf。
[mysqld]
Log_bin=mysql-bin //[必须]启用二进制日志 server-id=10//[必须]服务器唯一 ID,默认是 1,一般取 IP 最后一段配置完成后需要重启 mysqlserver 才能生效。
systemctl restart mysqld
mysql -uroot -p
mysql>grant replication slave on *.* to 'root'@'%' identified by '123456';
"*.*"表示对所有库的所有操作,“%”表示所有客户端都可能连,也可用具体客户端 IP 代替,如 192.168.33.11,加强安全。
mysql>show master status;
其中 file、position 字段需要记录下值,mysql-bin.000001 是用于主从复制的文件名,437 是日志文件内的最新位置。
修改配置文件 my.cnf,使用二进制日志,指定 server-id,重新启动服务。
[mysqld]
Log_bin=mysql-bin
Server_id=11
登陆从服务器 mysql 命令行,使用之前创建的用户和 master 的日志文件及其位置。 slave 中使用被授权用户信息及日志文件信息,进行指向 master。这时已经建立了和 master 的联系,明确了从哪儿读取日志文件。
mysql>change master to master_host='192.168.33.10',master_user='root',master_password='123456',master_log_file='mysql-bin.000001',master_log_pos=437;
//注意不要断开,“437”无单引号。
执行启动 slave 的命令,开始主从复制
mysql>start slave;
mysql> show slave status\G;
结果中有两个重要数据项:
通常是两边的数据库不是完全对应的,需要确保 master 上的库及到目前为止的最新记录都复制到 slave 上了。
当 IO 线程和 SQL 线程都正常后,到 master 中随意测试下插入、修改、删除操作,同时到 slave 中检查。
Create database mastertest;
Use mastertest;
CREATE TABLE `test` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Insert into test(name) values(‘boxuegu1test’);
Insert into test(name) values(‘boxuegu2test’);
Insert into test(name) values(‘boxuegu3test’);
slave 下验证
show databases; Use mastertest;
Show tables;
Select * from test;
Mysql Utilities 是一个 Mysql 的工具箱(基于 python ),里面有不少好用的小工具,其中的 mysqlreplicate 命令,可以让我们通过一个命令就能快速配置好主从复制环境。
http://dev.mysql.com/downloads/utilities/1.5.html
http://dev.mysql.com/doc/mysql-utilities/1.6/en/utils-overview.html
mysqlreplicate \
--master=root:[email protected]:3306 \
--slave=root:[email protected]:3306 \
--rpl-user=replutil:111111
--master 指定主库的连接信息
--slave 指定从库的连接信息
--rpl-user 指定用于复制的用户信息,这个用户需要提前在 master 上创建好,例如:
grant ALL PRIVILEGES on *.* to replutil@"192.168.31.101" Identified by "111111"; 注意,创建用户时,其中的从库 IP 要明确,不要用 '%'。
可以看到,总共只需要两步:
1.8.5.2.几秒钟就完成了主从配置。
下载解压 Mysql Utilities,进入解压后的目录,执行编译安装命令
python ./setup.py build
python ./setup.py install
执行完成后,就可以使用其中的工具命令了
配置好了 Mysql 的主从复制结构后,我们希望实现读写分离,把读操作分散到从服务器中,并且对多个从服务器能实现负载均衡。读写分离和负载均衡是 Mysql 集群的基础需求,MaxScale 就可以帮着我们方便的实现这些功能。
MaxScale 是 Mysql 的 兄 弟 公 司 MariaDB 开 发 的 , 现在 已 经 发 展 得 非 常成 熟 。MaxScale 是插件式结构,允许用户开发适合自己的插件。
MaxScale 目前提供的插件功能分为 5 类
提供了登录认证功能,MaxScale 会读取并缓存数据库中 user 表中的信息,当有连接进来时,先从缓存信息中进行验证,如果没有此用户,会从后端数据库中更新信息,再次进行验证
包括客户端连接协议,和连接数据库的协议。
决定如何把客户端的请求转发给后端数据库服务器,读写分离和负载均衡的功能就是由这个模块实现的。
对各个数据库服务器进行监控,例如发现某个数据库服务器响应很慢,那么就不向其转发请求了。
提供简单的数据库防火墙功能,可以对 SQL 进行过滤和容错。
配置一主二从集群环境
准备 3 台服务器,安装Mysql,配置一主二从的复制结构。主从复制的配置过程参加上一节内容。
最好在另一台服务器上安装,如果资源不足,可以和某个 Mysql 放在一起。MaxScale 的下载地址,最新版本 2.3.4,实例中使用的是 1.4.5:
下载地址:https://downloads.mariadb.com/files/MaxScale
根据自己的服务器选择合适的安装包,以 centos 7 为例安装步骤如下:
yum install libaio.x86_64 libaio-devel.x86_64 novacom-server.x86_64 libedit -y wget https://downloads.mariadb.com/MaxScale/1.4.5/centos/7/x86_64/maxscale-1.4.5-1.centos.7.x86_64.rpm
rpm -ivh maxscale-1.4.5-1.centos.7.x86_64.rpm
在开始配置之前,需要在 master 中为 MaxScale 创建两个用户,用于监控模块和路由模块:创建监控用户
create user 'maxmon'@'%' identified by '123456'; grant replication slave,replication client on *.* to 'maxmon'@'%';
create user 'maxrou'@'%' identified by '123456';
grant select on mysql.* to 'maxrou'@'%';
flush privileges;
vi /etc/maxscale.cnf
找到 [server1] 部分,修改其中的 address 和 port,指向 master 的 IP 和端口复制 2 次 [server1] 的整块儿内容,改为 [server2] 与 [server3],同样修改其中的 address 和 port,分别指向 slave1 和 slave2
找到 [MySQL Monitor] 部分,修改 servers 为 server1,server2,server3,修改 user 和 passwd 为之前创建的监控用户的信息(maxmon,123456)
找到 [Read-Write Service] 部分,修改 servers 为 server1,server2,server3,修改 user 和 passwd 为之前创建的路由用户的信息(maxrou,123456)
由于我们使用了 [Read-Write Service],需要删除另一个服务 [Read-Only Service],删除其整块儿内容即可,底部还有一个[Read-Only Client]也需要删除。
配置完成,保存并退出编辑器。
启动 MaxScale
maxscale --config=/etc/maxscale.cnf
查看 MaxScale 的响应端口是否已经就绪
netstat -ntelp
4006 是 Read-Write Listener 使用的端口,用于连接 MaxScale
6603 是 MaxAdmin Listener 使用的端口,用于 MaxScale 管理器
登 录 MaxScale 管 理 器 , 查 看 一 下 数 据 库 连 接 状 态 , 默 认 的 用 户 名 和 密 码 是
admin/mariadb
maxadmin --user=admin --password=mariadb
MaxScale> list servers
可以看到,MaxScale 已经连接到了 master 和 slave
先在 master 上创建一个测试用户
create user 'rtest'@'%' identified by '111111'; grant ALL PRIVILEGES on *.* to 'rtest'@'%';
使用 Mysql 客户端到连接 MaxScale
mysql -urtest -p'111111' -h'192.168.33.11' -P4006
执行查看数据库服务器名的操作来知道当前实际所在的数据库
Select @@hostname;
start transaction;
Select @@hostname;
roolback;
Select @@hostname;
开启事务后,就自动路由到了 master,普通的查询操作,是在 slave 上。
可参考:https://www.cnblogs.com/luckcs/articles/2543607.html
MySQLProxy 是 MySQL 官方提供的一个数据库代理层产品,和 MySQLServer 一样,相同是一个基于 GPL 开源协议的开源产品。可用来监视、分析或者传输他们之间的通讯信息,具备的功能主要有连接路由、Query 分析、Query 过滤和修改、负载均衡以及主要的
HA 机制等。
实际上 MySQLProxy 本身并不具有上述全部的这些功能,而是提供了实现上述功能的基础。要实现这些功能,还须要通过我们自行编写 LUA 脚本来实现。
MySQLProxy 实际上是在 client 请求与 MySQLServer 之间建立了一个连接池。全部 client 请求都是发向 MySQLProxy,然后经由 MySQLProxy 进行对应的分析。推断出是读操作还是写操作,分发至对应的 MySQLServer 上。对于多节点 Slave 集群,也能够起做到负载均衡的效果。以下是 MySQLProxy 的基本架构图:
当数据量达到一定程度时,我们处于性能考虑就需要将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面,以达到分散单台设备负载的效果。数据的切分同一时候还能够提高系统的总体可用性,由于单台设备 Crash 之后。仅仅有总体数据的某部分不可用,而不是全部的数据。
数据的切分(Sharding)依据其切分规则的类型,能够分为垂直切分、水平切分、联合切分模式。
一种是依照不同的表(或者 Schema)来切分到不同的数据库(主机)之上,这样的切能够称之为数据的垂直(纵向)切分。
垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低。相互影响非常小,业务逻辑非常清晰的系统。在这样的系统中,能够非常容易做到将不同业务模块所使用的表分拆到不同的数据库中。依据不同的表来进行拆分。对应用程序的影响也更小,拆分规则也会比较简单清晰。
一个架构设计较好的应用系统。其总体功能肯定是由非常多个功能模块所组成的。而
每一个功能模块所须要的数据对应到数据库中就是一个或者多个表。而在架构设计中,各个功能模块相互之间的交互点越统一越少,系统的耦合度就越低,系统各个模块的维护性以及扩展性也就越好。这样的系统实现数据的垂直切分也就越容易,不同功能模块的数据存放于不同的数据库主机中,能够非常容易就避免掉跨数据库的 Join 存在。
当然很多情况下系统的耦合度没有那么低,我们就必须依据实际的应用场景进行评估权衡。决定是迁就应用程序将须要 Join 的表的相关某快都存放在同一个数据库中,还是让应用程序做很多其它的事情,也就是程序全然通过模块接口取得不同数据库中的数据,然后在程序中完毕 Join 操作。
系统功能能够基本分为四个功能模块:用户,群组消息,相册以及事件。分别对应为例如以下这些表:
初略一看,没有哪一个模块能够脱离其它模块独立存在,模块与模块之间都存在着关系。莫非无法切分?
当然不是,我们再略微深入分析一下,能够发现,尽管各个模块所使用的表之间都有关联,可是关联关系还算比较清晰,也比较简单。
◆群组讨论模块和用户模块之间主要存在通过用户或者是群组关系来进行关联。一般关联的时候都会是通过用户的 id 或者 nick_name 以及 group 的 id 来进行关联。通过模块之间的接口实现不会带来太多麻烦。
◆相册模块仅仅与用户模块存在通过用户的关联。这两个模块之间的关联基本就有通过用户 id 关联的内容。简单清晰,接口明白;
◆ 事件模块与各个模块可能都有关联,可是都仅仅关注其各个模块中对象的 ID 信息,
相同能够做到非常容易分拆。所以。我们第一步能够将数据库依照功能模块相关的表进行一次垂直拆分。每一个模块所涉及的表单独到一个数据库中,模块与模块之间的表关联都在应用系统端通过藉口来处理。例如以下图所看到的:
通过这样的垂直切分之后。之前仅仅能通过一个数据库来提供的服务。就被分拆成四个数据库来提供服务,服务能力自然是添加几倍了。
◆ 数据库的拆分简单明了,拆分规则明白;
◆ 应用程序模块清晰明白,整合容易。
◆ 数据维护方便易行,容易定位。
◆ 部分表关联无法在数据库级别完毕。须要在程序中完毕。
◆ 对于访问极其频繁且数据量超大的表仍然存在性能瓶颈,不一定能满足要求。
◆ 事务处理相对更为复杂;切分达到一定程度之后,扩展性会遇到限制;
◆ 过读切分可能会带来系统过渡复杂而难以维护。
依据表中的数据的逻辑关系,将同一个表中的数据依照某种条件拆分到多台数据库
(主机)上面,这样的切分称之为数据的水平(横向)切分。一般来说,简单的水平切分主要是将某个访问极其平庸的表再依照某个字段的某种规则来分散到多个表之中。每一个表中包括一部分数据。
水平切分于垂直切分相比。相对来说略微复杂一些。由于要将同一个表中的不同数据
拆分到不同的数据库中,对于应用程序来说,拆分规则本身就较依据表名来拆分更为复杂,后期的数据维护也会更为复杂一些。
如依据某个数字类型字段基于特定数目取模,某个时间类型字段的范围,或者是某个
字符类型字段的 hash 值。假设整个系统中大部分核心表都能够通过某个字段来进行关联。那这个字段自然是一个进行水平分区的上上之选了。
一般来说,像如今互联网非常火爆的 Web2.0 类型的站点。基本上大部分数据都能够通过会员用户信息关联上,可能非常多核心表都非常适合通过会员 ID 来进行数据的水平切分。切分之后基本上不会出现各个库之间的交互。
全部数据都是和用户关联的。那么我们就能够依据用户来进行水平拆分,将不同用户的数据切分到不同的数据库中。当然,唯一有点差别的是用户模块中的 groups 表和用户没有直接关系。所以 groups 不能依据用户来进行水平拆分。对于这样的特殊情况下的表,我们全然能够独立出来。单独放在一个独立的数据库中。所以,对于我们的演示样例数据库来说,大部分的表都能够依据用户 ID 来进行水平的切分。不同用户相关的数据进行切分之后存放在不同的数据库中。如将全部用户 ID 通过 5 取模然后分别存放于五个不同的数据库中。
每一个和用户 ID 关联上的表都能够这样切分。这样,基本上每一个用户相关的数据。
都在同一个数据库中,即使是须要关联,也能够非常简单的关联上。我们能够通过下图来更为直观的展示水平切分相关信息:
◆ 表关联基本能够在数据库端全部完毕;
◆ 不会存在某些超大型数据量和高负载的表遇到瓶颈的问题;
◆ 应用程序端总体架构修改相对较少;
◆ 事务处理相对简单;
◆ 仅仅要切分规则能够定义好。基本上较难遇到扩展性限制;
◆ 切分规则相对更为复杂,非常难抽象出一个能够满足整个数据库的切分规则;
◆ 后期数据的维护难度有所添加,人为手工定位数据更困难;
◆ 应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。
当我们某个(或者某些)表的数据量和访问量特别的大,通过垂直切分将其放在独立的设备上后仍然无法满足性能要求,这时候我们就必须将垂直切分和水平切分相结合。先垂直切分,然后再水平切分,才能解决这样的超大型表的性能问题。
每一个应用系统的负载都是一步一步增长上来的,在开始遇到性能瓶颈的时候,大多数架构师和 DBA 都会选择先进行数据的垂直拆分,由于这样的成本最小。最符合这个时期所追求的最大投入产出比。然而随着业务的不断扩张,系统负载的持续增长,在系统稳定一段时期之后,经过了垂直拆分之后的数据库集群可能又再一次不堪重负,遇到了性能瓶颈。这时候我们就必须要通过数据的水平切分的优势,来解决这里所遇到的问题。并且,我们全然不必在使用数据水平切分的时候,推倒之前进行数据垂直切分的成果,而是在其基础上利用水平切分的优势来避开垂直切分的弊端。而水平拆分的弊端(规则难以统一)也已经被之前的垂直切分解决掉了,让水平拆分能够进行的得心应手。
假设在最开始。我们进行了数据的垂直切分,然而随着业务的不断增长,数据库系统遇到了瓶颈,我们选择重构数据库集群的架构。怎样重构?我们选择了在垂直切分的基础上再进行水平拆分。在经历过垂直拆分后的各个数据库集群中的每一个都仅仅有一个功能模块。而每一个功能模块中的全部表基本上都会与某个字段进行关联。如用户模块全部都能够通过用户 ID 进行切分,群组讨论模块则都通过群组 ID 来切分。相册模块则依据相册 ID 来进切分。最后的事件通知信息表考虑到数据的时限性(仅仅会访问近期某个事件段的信息),则考虑按时间来切分。
下图展示了切分后的整个架构
实际上,在非常多大型的应用系统中,垂直切分和水平切这两种数据的切分方法基本上都是并存的。并且经常在不断的交替进行,以不断的添加系统的扩展能力。我们在应对不同的应用场景的时候,也须要充分考虑到这两种切分方法各自的局限,以及各自的优势。
在不同的时期(负载压力)使用不同的结合方式。
◆ 能够充分利用垂直切分和水平切分各自的优势而避免各自的缺陷;让系统扩展性得到最大化提升。
◆ 数据库系统架构比较复杂。维护难度更大。
◆ 应用程序架构也相对更复杂;
我们已经非常清晰了通过数据库的数据切分能够极大的提高系统的扩展性,可是数据库中的数据在经过垂直和(或)水平切分被存放在不同的数据库主机之后,应用系统面临的最大问题就是怎样来让这些数据源得到较好的整合。
数据的整合非常难依靠数据库本身来达到这个效果,尽管 MySQL 存在 Federated 存储引擎,能够解决部分相似的问题。可是在实际应用场景中却非常难较好的运用。那我们该怎样来整合这些分散在各个 MySQL 主机上面的数据源呢?总的来说,存在两种解决思路:
可能 90%以上的人在面对上面这两种解决思路的时候都会倾向于选择第二种,尤其是
系统不断变得庞大复杂的时候,尽管短期内须要付出的成本可能会相对更大一些,可是对整个系统的扩展性来说,是非常有帮助的。所以,对于第一种解决思路我这里就不准备过多的分析,以下我重点分析一下在另外一种解决思路中的一些解决方式。
通过自行开发中间代理层能够最大程度的应对自身应用的特点,最大化的定制非常多个性化需求,在面对变化的时候也能够灵活的应对。当然,选择自行开发享受让个性化定制最大化的乐趣的同一时候,自然也须要投入很多其它的成本来进行前期研发以及后期的持续升级改进工作,并且本身的技术门槛可能也比简单的 Web 应用要更高一些。
基于 MySQLProxy 自行编写 LUA 脚本实现数据切分相关代理功能。
Amoeba 是一个基于 Java 开发的,专注于解决分布式数据库数据源整合 Proxy 程序的开源框架,基于 GPL3 开源协议。眼下 Amoeba 已经具有 Query 路由、Query 过滤、读写分离、负载均衡以及 HA 机制等相关内容。
Amoeba 主要解决的以下几个问题:
我们能够看出,Amoeba 所做的事情,正好就是我们通过数据切分来提升数据库的扩展性所须要的。
Amoeba 并非一个代理层的 Proxy 程序,而是一个开发数据库代理层 Proxy 程序的开
发 框 架 , 眼 下 基 于 Amoeba 所 开 发 的 Proxy 程 序 有 AmoebaForMySQL 和
AmoebaForAladin 两个。
AmoebaForMySQL 主要是专门针对 MySQL 数据库的解决方式,前端应用程序请求的协议以及后端连接的数据源数据库都必须是 MySQL。对于 client 的不论什么应用程序来说,AmoebaForMySQL 和一个 MySQL 数据库没有什么差别。不论什么使用 MySQL 协
议的 client 请求,都能够被 AmoebaForMySQL 解析并进行对应的处理。下如能够告诉我们 AmoebaForMySQL 的架构信息(出自 Amoeba 开发人员博客):
AmoebaForAladin 则是一个适用更为广泛,功能更为强大的 Proxy 程序。
他能够同一时候连接不同数据库的数据源为前端应用程序提供服务,可是仅仅接受符合 MySQL 协议的 client 应用程序请求。也就是说,仅仅要前端应用程序通过 MySQL 协议连接上来之后,AmoebaForAladin 会自己主动分析 Query 语句,依据 Query 语句中所请求的数据来自己主动识别出该所 Query 的数据源是在什么类型数据库的哪一个物理主机上面。下图展示了 AmoebaForAladin 的架构细节(出自 Amoeba 开发人员博客):
咋 一 看 , 两 者 好 像 全 然 一 样 。 细 看 之 后 才 会 发 现 两 者 主 要 的 差 别 仅 在 于 通 过
MySQLProtocalAdapter 处理之后。依据分析结果推断出数据源数据库。然后选择特定的
JDBC 驱动和对应协议连接后端数据库。
事实上通过上面两个架构图大家可能也已经发现了 Amoeba 的特点了,他仅仅仅仅是一个开发框架。我们除了选择他已经提供的 ForMySQL 和 ForAladin 这两款产品之外。还能够基于自身的需求进行对应的二次开发。得到更适应我们自己应用特点的 Proxy 程序。
当对于使用 MySQL 数据库来说。不论是 AmoebaForMySQL 还是 AmoebaForAladin 都能够非常好的使用。当然,考虑到不论什么一个系统越是复杂,其性能肯定就会有一定的损失,维护成本自然也会相对更高一些。所以,对于仅仅须要使用 MySQL 数据库的时候,我还是建议使用 AmoebaForMySQL。
AmoebaForMySQL 的使用非常简单,全部的配置文件都是标准的 XML 文件,总共同拥有四个配置文件。分别为:
◆amoeba.xml:主配置文件,配置全部数据源以及 Amoeba 自身的參数设置。
◆rule.xml:配置全部 Query 路由规则的信息。
◆functionMap.xml:配置用于解析 Query 中的函数所对应的 Java 实现类;
◆ rullFunctionMap.xml:配置路由规则中须要使用到的特定函数的实现类;
假设您的规则不是太复杂,基本上仅须要使用到上面四个配置文件里的前面两个就可完毕全部工作。Proxy 程序经常使用的功能如读写分离。负载均衡等配置都在 amoeba.xml 中进行。此外。Amoeba 已经支持了实现数据的垂直切分和水平切分的自己主动路由。路由规则能够在 rule.xml 进行设置。
眼下 Amoeba 少有欠缺的主要就是其在线管理功能以及对事务的支持了,以前在与相关开发人员的沟通过程中提出过相关的建议,希望能够提供一个能够进行在线维护管理的命令行管理工具,方便在线维护使用,得到的反馈是管理专门的管理模块已经纳入开发日程了。另外在事务支持方面临时还是 Amoeba 无法做到的,即使 client 应用在提交给
Amoeba 的请求是包括事务信息的,Amoeba 也会忽略事务相关信息。当然,在经过不断完好之后,我相信事务支持肯定是 Amoeba 重点考虑添加的 feature。
关 于 Amoeba 更 为 具 体 的 用 法 读 者 朋 友 能 够 通 过 Amoeba 开 发 人 员 博 客
(http://amoeba.sf.net)上面提供的使用手冊获取,这里就不再细述了。
和前面的 MySQLProxy 以及 Amoeba 一样,HiveDB 相同是一个基于 Java 针对 MySQL 数据库的提供数据切分及整合的开源框架,仅仅是眼下的 HiveDB 仅仅支持数据的水平切分。
主要解决大数据量下数据库的扩展性及数据的高性能访问问题,同一时候支持数据的冗余及主要的 HA 机制。
HiveDB 的实现机制与 MySQLProxy 和 Amoeba 有一定的差异,他并非借助 MySQL 的 Replication 功能来实现数据的冗余,而是自行实现了数据冗余机制,而其底层主要是基于 HibernateShards 来实现的数据切分工作。
在 HiveDB 中,通过用户自己定义的各种 Partitionkeys(事实上就是制定数据切分规则),将数据分散到多个 MySQLServer 中。在访问的时候。在执行 Query 请求的时候。会自己主动分析过滤条件,并行从多个 MySQLServer 中读取数据,并合并结果集返回给 client 应用程序。
单纯从功能方面来讲,HiveDB 可能并不如 MySQLProxy 和 Amoeba 那样强大,可是其数据切分的思路与前面二者并无本质差异。此外,HiveDB 并不仅仅仅仅是一个开源爱好者所共享的内容,而是存在商业公司支持的开源项目。
以下是 HiveDB 官方站点上面一章图片,描写叙述了 HiveDB 怎样来组织数据的基本
信息,尽管不能具体的表现出太多架构方面的信息,可是也基本能够展示出其在数据切分方面独特的一面了。
参见:http://www.songwie.com/articlelist/11
在服务器的 BIOS 设置中,可调整下面的几个配置,目的是发挥 CPU 最大性能,或者避免经典的 NUMA 问题:
内存设置菜单中,启用 Node Interleaving,避免 NUMA 问题;
下面几个是按照 IOPS 性能提升的幅度排序,对于磁盘 I/O 可优化的一些措施:
在文件系统层,下面几个措施可明显提升 IOPS 性能:
针对关键内核参数设定合适的值,目的是为了减少 swap 的倾向,并且让内存和磁盘
I/O 不会出现大幅波动,导致瞬间波峰负载: