基于MySQL版本5.5
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司。
MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有的数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
MySQL是开源的,所以并不需要支付额外的费用。
MySQL支持大型的数据库,可以处理拥有上千万条记录的大型数据库。
MySQL使用标准的SQL数据语言形式。
MySQL可以允许于多个系统上,并且支持多种语言。这些编程语言包括C、C++,Python,Java、Perl、PHP、Ruby和TCL等。
MySQL对PHP有很好的支持,PHP是目前最流行的Web开发语言。
MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件8TB。
MySQL是可以定制的,采用了GPL协议。可以修改源码来开发自己的MySQL系统。
完整的MySQL优化需要很深的功底,大公司甚至有专门的DBA
参照Linux常用软件的安装-MySQL的安装。
连接MySQL,输入SQL查看字符集
show variables like 'character%';
show variables like '%char%';
log-bin=mysql-bin
错误日志log-error:默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等。
查询日志log:默认关闭,记录查询的SQL语句,如果开启会降低MySQL的整体性能,因为记录日志也是需要消耗系统资源的
数据文件
配置文件
Windows系统文件名:my.ini文件
Linux系统文件名:/etc/my.cnf文件
和其他数据相比,MySQL有点域中不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离 。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
MySQL逻辑架构图:
-- 查看MySQL已提供的存储引擎
show engines;
-- 查看MySQL当前默认的存储引擎
show variables like '%storage_engine%';
对比项 | MyISAM | InNoDB |
---|---|---|
主外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 | 行锁,操作时只锁某一行,不对其他行有影响,适合高并发的操作 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引,还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
表空间 | 小 | 大 |
关注点 | 性能 | 事务 |
默认安装 | 是 | 是 |
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
可以看出索引的本质:索引是数据结构。
索引的目的在于提高查询效率,可以类比字典。例如:
如果要查“mysql”这个单次,肯定需要先定位到m字母,再往下找到y字母,再找剩下的sql。
如果没有索引,那么可能需要从a-z寻找。如果想再找Java开头的单词或者Oracle的单词,这种查询效率非常慢。
**排好序的快速查找数据结构就是索引。**在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例:
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。
为了加快Col2的查找,可以维护一个右边所示的二叉查找数,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说索引本身也很大,不可能全部存储在内存中,因此在索引往往以索引文件的形式存储在硬盘上。
我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉树)结构组织的索引。其中聚集索引,次要索引,覆盖索引、符合索引、前缀索引、唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈希索引(hash index)等。
单值索引:即一个索引只包含单个列,一个表可以有多个单列索引
唯一索引:索引列的值必须唯一,但允许有空值
复合索引:即一个索引包含多个列
基本语法:
-- [] 内的内容可以不填
-- 如果是CHAR、VARCHAR类型,length可以小于字段实际长度
-- 如果是BLOB和TEXT类型,必须指定length
CREATE [UNIQUE] INDEX indexName ON tableName(columnname(length))
ALTER tableName ADD [UNIQUE] INDEX [indexName] On (columnname(length));
DROP INDEX [indexName] on tableName;
SHOW INDEX FROM tableName;
-- 有以下四种方式来添加数据表的索引
-- 1.该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL
ALTER TABLE tableName ADD PRIMARY KEY (column_list);
-- 2.该语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)
ALTER TABLE tableName ADD UNIQUE indexName (column_list);
-- 3.添加普通索引,索引值可以出现多次
ALTER TABLE tableName ADD INDEX indexName (column_list);
-- 4.改语句指定了索引为FULLTEXT,用于全文检索
ALTER TABLE tableName ADD FULLTEXT indexName (column_list);
检索原理:
【初始化介绍】
一颗B+树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据线(深蓝色表示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
真实的数据存在于叶子节点。 即3、5、9、10、13、15、28、29、36、60、75、79、90、99.
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项 ,如17、35并不真实存在于数据表中。
【查找过程】
如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不急,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26到30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总共三次IO。
真实的情况是,三层的B+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
MySQL中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中手机到的统计信息,为客户端请求的Query提供他认为最优的执行计划(它认为最优的数据检索方式,但不见得是DBA认为是最优的,这部分最耗费时间)
当客户端向MySQL请求一条Query,命令解析器模块完成请求分类,区别出是SELECT并转发给MySQL Query Optimizer时,MySQL Query Optimizer首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对Query中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。然后分析Query中的Hint信息(如果有),看显示Hint信息是否可以完全确定Query的执行计划。如果没有Hint或Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。
EXPLATIN + SQL语句
执行计划包含的信息:
select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况:
查询的类型,主要用于区别普通查询、联合查询、子查询等的复杂查询。类别如下:
显示这一行的数据是关于那张表的
type显示的是访问类型,是较为重要的一个指标,结果值从最好到最差依次是:
system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL,常见的排序为:system>const>eq_ref>ref>range>index>ALL。一般来说,得保证查询至少达到range级别,最好能达到ref
类型:
显示可能应用在这张表中的索引,一个或多个。
查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。
实际使用的索引。如果为NULL,则没有使用索引。
查询中若使用了覆盖索引,则该索引和查询的select字段重叠。
表示索引中使用的字节数,可通过该列计算查询的索引的长度。在不损失精确性的情况下,长度越短越好。
key_len显示的值为索引字段的最大长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。
显示索引的那一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
包含不适合在其他列中显示但十分重要的额外信息。
CREATE TABLE IF NOT EXISTS `article` (
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`author_id` INT(10) UNSIGNED NOT NULL,
`category_id` INT(10) UNSIGNED NOT NULL,
`views` INT(10) UNSIGNED NOT NULL,
`comments` INT(10) UNSIGNED NOT NULL,
`title` VARBINARY(255) NOT NULL,
`content` TEXT NOT NULL);
INSERT INTO `article`(`author_id`,`category_id`,`views`,`comments`,`title`,`content`) VALUES (1,1,1,1,'1','1'), (2,2,2,2,'2','2'), (1,1,3,3,'3','3');
-- 查询 category_id为1,且comments大于1的情况下,views最多的article_id。
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1
-- 结论:很显然,type是ALL,即最坏的情况。Extra里还出现了Using filesort,也是最坏的情况。优化是必须的。
-- 开始优化
-- 1.1 新建索引+删除索引
-- ALTER TABLE `article` ADD INDEX idx_article_ccv(`category_id`,`comments`,`views`);
CREATE INDEX idx_article_ccv ON article(category_id,comments,views);
-- 再次执行后,解决了全表扫描,但还是出现了Using filesort。删除索引
DROP INDEX idx_article_ccv ON article
-- 1.2 第二次EXPLAIN
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments = 3 ORDER BY views DESC LIMIT 1
-- 结论:
-- type变成了range,这是可以接受的。但是extra里使用Using filesort仍是无法接受的。
-- 但是我们已经建立了索引,为什么没用?
-- 这是因为按照BTree索引的工作原理,
-- 先排序category_id,
-- 如果遇到相同的category_id则再排序comments,如果遇到相同的comments则再排序views,
-- 当comments字段在联合索引里处于中间位置时,
-- 因comments > 1条件是一个范围值(所谓range)
-- MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效
-- 1.3 第2次新建索引
CREATE INDEX idx_article_cv ON article(category_id,views);
-- 1.4 第三次EXPLAIN
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1
-- 结论:可以看到type变成了ref,Extra中的Using filesort也消失了,结果很理想
DROP INDEX idx_article_cv ON article
CREATE TABLE IF NOT EXISTS `class`(
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(`id`)
);
CREATE TABLE IF NOT EXISTS `book`(
`bookId` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(`bookId`)
);
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES (FLOOR(1+(RAND()*20)));
-- 下面开始explain分析
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card=book.card;
-- 结论:type有All
-- 添加索引优化,左连接的情况下,加在右表
ALTER TABLE `book` ADD INDEX card (`card`);
-- 第2次EXPLAIN,可以看到第二行的type编程了ref,rows也变了,优化比较明细
-- 测试主表的索引,先删除右表的索引
DROP INDEX card on book;
ALTER TABLE `class` ADD INDEX card (`card`);
-- 再次EXPLAIN,可以发现rows没有变化
-- 这是由左连接特性决定的。LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引
-- 同理,如果是右连接,RIGHT JOIN条件用于确定如何从左表搜索行,右边一定都有,所以左边是我们的关键点,一定需要建立索引。
CREATE TABLE IF NOT EXISTS `phone`(
`phoneId` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(`phoneId`)
);
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES (FLOOR(1+(RAND()*20)));
-- 分析三表联合查询
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card LEFT JOIN phone ON book.card = phone.card
-- 添加索引
ALTER TABLE `book` ADD INDEX Y (`card`);
ALTER TABLE `phone` ADD INDEX Z (`card`);
-- 再次分析,后两行的type为ref且总rows优化很好,效果不错。因此索引最好设置在需要经常查询的字段中。
-- 结论:JOIN语句的优化
-- 尽可能减少JOIN语句中的NestedLoop的循环总次数:“永远用小结果集驱动打的结果集”。
-- 优先优化NestedLoop的内存循环
-- 保证JOIN语句中被驱动表上JOIN条件字段已被索引
-- 当无法保证被驱动表的JOIN条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer的设置
CREATE TABLE `staffs` (
`id` int(11) NOT NULL,
`name` varchar(24) NOT NULL COMMENT '姓名',
`age` int(11) NOT NULL COMMENT '年龄',
`pos` varchar(24) NOT NULL COMMENT '职位',
`add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工记录表';
INSERT INTO `staffs` (`name`, `age`, `pos`, `add_time`) VALUES ( '2000', 23, 'dev', NOW());
INSERT INTO `staffs` (`name`, `age`, `pos`, `add_time`) VALUES ( 'july', 23, 'dev', NOW());
INSERT INTO `staffs` (`name`, `age`, `pos`, `add_time`) VALUES ('z3', 22, 'manager', '2023-06-11 20:20:22');
ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name,age,pos)
全值匹配我最爱
最佳左前缀法则:如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不挑过索引中的列 。
-- 测试语句
EXPLAIN SELECT * FROM staffs WHERE age = 23 AND pos = 'dev'
-- 结果:全表扫描,未使用索引
-- 测试语句
EXPLAIN SELECT * FROM staffs WHERE age = 23
-- 结果:全表扫描,未使用索引
-- 测试语句
EXPLAIN SELECT * FROM staffs WHERE name = 'july'
-- 结果:使用到了索引
-- 测试语句
EXPLAIN SELECT * FROM staffs WHERE name = 'july' and pos = 'dev'
-- 结果:使用了部分索引
-- 总结:最佳左前缀法则,带头大哥不能死,中间部分不能断
Where语句 | 索引是否被使用 |
---|---|
where a = 3 | Y,使用到a |
where a = 3 and b = 5 | Y,使用到a,b |
where a = 3 and b = 4 and c = 5 | Y,使用到a,b,c |
where b = 3 或者 where b = 3 and c = 4 或者 where c = 4 | N |
where a = 3 and c = 5 | 使用到a,但是c不可以,b中间断了 |
where a = 3 and b > 4 and c = 5 | 使用到a和b,c不能用在范围之后,b断了 |
where a = 3 and b like ‘kk%’ and c = 4 | Y,使用到a,b,c |
where a = 3 and b like ‘%kk’ and c = 4 | Y,只使用到a |
where a = 3 and b like ‘%kk%’ and c = 4 | Y,只使用到a |
where a = 3 and b like ‘k%kk%’ and c = 4 | Y,使用到a,b,c |
CREATE TABLE `test03` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`c1` char(10) DEFAULT NULL,
`c2` char(10) DEFAULT NULL,
`c3` char(10) DEFAULT NULL,
`c4` char(10) DEFAULT NULL,
`c5` char(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `test03` (`c1`, `c2`, `c3`, `c4`, `c5` )
VALUES
( 'a1', 'a2', 'a3', 'a4', 'a5' );
INSERT INTO `test03` (`c1`, `c2`, `c3`, `c4`, `c5` )
VALUES
( 'b1', 'b2', 'b', 'b4', 'b5' );
INSERT INTO `test03` (`c1`, `c2`, `c3`, `c4`, `c5` )
VALUES
( 'c1', 'c2', 'c3', 'c4', 'c5' );
INSERT INTO `test03` (`c1`, `c2`, `c3`, `c4`, `c5` )
VALUES
( 'd1', 'd2', 'd3', 'd4', 'd5' );
INSERT INTO `test03` (`c1`, `c2`, `c3`, `c4`, `c5` )
VALUES
( 'e1', 'e2', 'e3', 'e4', 'e5' );
-- 建立索引
CREATE INDEX idx_test03_c1234 ON test03 ( c1, c2, c3, c4 );
定值、范围还是排序,一般order by是给个范围
group by基本上都需要进行排序,会有临时表产生
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE百分写最右,覆盖索引不写星;
不等空值还有OR,索引失效要少用;
VAR引号不可丢,SQL高级也不难!
类似嵌套循环 Nested Loop
优化原则:小表驱动大表,即小的数据集驱动大的数据集
-- 原理(RBO)
select * from A where id in (select id in B)
-- 等价于循环
for select id from B
for select * from A where A.id = B.id
-- 当B表的数据集必须小于A表的数据集时,用in优于exists
select * from A where id existss (select id in B)
-- 等价于
for select * from A
for select * from B where B.id = A.id
-- 当A的数据集小于B表的数据集时,用exists优先于in
-- 注意:A表与B表的ID字段应建立索引
SELECT … FROM table WHERE EXISTS(subquery)
该语法可以理解为:将主查询的数据,放到子查询中做条件验证,根据验证结果(TRUE或FALSE)来决定主查询的数据结果是否得以保留。
ORDER BY 子句,尽量使用Index方式排序,避免使用FileSort方式排序
-- 建表
CREATE TABLE `tbla` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`age` int(11) DEFAULT NULL,
`birth` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 新增测试数据
INSERT INTO tbla(age,birth) VALUES(22,NOW());
INSERT INTO tbla(age,birth) VALUES(23,NOW());
INSERT INTO tbla(age,birth) VALUES(24,NOW());
-- 创建索引
CREATE INDEX idx_A_ageBirth ON tbla(age,birth);
-- 测试案例1
EXPLAIN SELECT * FROM tbla WHERE age > 20 ORDER BY age;
-- 结果:未使用FileSort排序
-- 测试案例2
EXPLAIN SELECT * FROM tbla WHERE age > 20 ORDER BY age,birth;
-- 结果:未使用FileSort排序
-- 测试案例3
EXPLAIN SELECT * FROM tbla WHERE age > 20 ORDER BY birth;
-- 结果:使用FileSort排序
-- 测试案例4
EXPLAIN SELECT * FROM tbla WHERE age > 20 ORDER BY birth,age;
-- 结果:使用FileSort排序
-- 测试案例5
EXPLAIN SELECT * FROM tbla ORDER BY birth;
-- 结果:使用FileSort排序
-- 测试案例6
EXPLAIN SELECT * FROM tbla WHERE birth > '2023-01-01 00:00:00' ORDER BY birth;
-- 结果:使用FileSort排序
-- 测试案例7
EXPLAIN SELECT * FROM tbla WHERE birth > '2023-01-01 00:00:00' ORDER BY age;
-- 结果:未使用FileSort排序
-- 测试案例8
EXPLAIN SELECT * FROM tbla ORDER BY age ASC,birth DESC;
-- 结果:使用FileSort排序
MySQL支持两种方式的排序,FileSort和Index,Index效率高,它指MySQL扫描索引本身完成排序。FileSort方式效率低。
ORDER BY 满足两种情况,会使用Index方式排序:
尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
如果不在索引列上,FileSort有两种算法,MySQL就要启动双路排序和单路排序
MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据。
读取行指针和order by列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。
从磁盘读取排序字段,在Buffer进行排序,再从磁盘取其他字段
取一批数据,要对磁盘进行两次扫描,众所周知,IO是很耗时的,所以在MySQL4.1之后,出现了第二种改进的算法,就是单路排序。
从磁盘读取查询需要的所有列,按照ORDER BY列在Buffer对它们进行排序,然后扫描排序后的列表进行输出,因为它的效率更快一下,避免了第二次读取数据。并把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。
由于单路是后出的,总体而言好过双路。
单路的问题:
在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出,所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能读取sort_buffer容量的数据,进行排序(创建tmp文件,多路合并),拍完序后再取sort_buffer容量大小,再排……从而多次IO。
本来想省一次IO操作,结果却导致了大量的IO操作,反而得不偿失。
优化策略:
增大sort_buffer_size参数的配置
增大max_length_for_sort_data参数的设置
优化原因:
ORDER BY 时,select * 是一个大忌,只Query需要的字段,这点非常重要。在这里的影响是:
尝试提高 sort_buffer_size:不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的。
尝试提高 max_length_for_sort_data:提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘IO活动和低的处理器使用率。
测试案例:
-- 索引:
KEY 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几乎一致
GROUP BY实质是先排序后进行分组,遵照索引建的最佳左前缀
当无法使用索引列,增大max_length_for_sort_data参数的设置,增大sort_buffer_size参数的设置
WHERE高于HAVING,能写在WHERE限定的条件就不要去HAVING限定了
默认情况下,MySQL数据库没有开启慢查询日志,需要手动来设置这个参数。
当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。
SHOW VARIABLES LIKE '%slow_query_log%';
-- 默认情况下,slow_query_log的值是OFF,表示慢查询日志是禁用的,可以通过设置slow_query_log的值来开启
-- 使用 set global slow_query_log=1 开启慢查询日志只对当前数据库生效,如果MySQL重启后则会失效
set global slow_query_log=1
-- 如果要永久生效,那么就必须修改配置文件my.cnf(其他系统变量也是如此)
-- 修改my.cnf文件,[mysqld]下增加或修改参数:slow_query_log和slow_query_log_file后,然后重启MySQL服务器。
slow_query_log=1
slow_query_log_file=/var/lib/mysql/root-slow.log
-- 关于慢查询的参数slow_query_log_file,它指定慢查询日志文件的存放路径,系统默认会给一个缺省的文件host_name-slow.log(如果没有指定参数slow_query_log_file的话)
这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒,命令:
SHOW VARIABLES LIKE 'long_query_time%';
这个参数可以使用命令修改,也可以在my.cnf参数里修改。
set global long_query_time = 3
-- 设置后再次查询,会发现值没有变化
-- 这时需要重新连接或新开一个会话才能看到修改值
SHOW VARIABLES LIKE 'long_query_time%';
-- 或者使用命令查询全局
SHOW GLOBAL VARIABLES LIKE 'long_query_time%';
-- 测试慢查询语句 4秒的查询语句
SELECT sleep(4);
-- 通过命令查询MySQL中有多少慢查询记录
SHOW GLOBAL STATUS LIKE '%Slow_queries%';
假如运行时间正好等于long_query_time的情况,并不会被记录下来。也就是说,
在MySQL源码里是**判断大于long_query_time,而非大于等于。 **
[mysqld]
slow_query_log=1,
slow_query_log_file=/var/lib/mysql/root-slow.log
long_query_time=3
log_output=FILE
在生产环境中,如果要手工分析日志,查询、分析SQL,显然是个体力活,MySQL提供了日志分析工具mysqldumpslow
mysqldumpslow --help
s:是表示按照何种方式排序
c:访问次数
l:锁定时间
r:返回记录
t:查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
t:返回前面多少条的数据
g:后边搭配一个正则匹配模式,大小写不敏感
# 得到返回记录集最多的10个SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/root-slow.log
# 得到访问次数最多的10个SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/root-slow.log
# 得到按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/root-slow.log
# 另外建议使用这些命令时结合| 和 more 使用,否则有可能出现爆屏情况
mysqldumpslow -s r -t 10 /var/lib/mysql/root-slow.log | more
往表里插入1000W条数据
-- 创建部门表
CREATE TABLE `dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`deptno` mediumint(9) DEFAULT NULL,
`dname` varchar(20) DEFAULT NULL,
`loc` varchar(13) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 创建员工表
CREATE TABLE `emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`empno` mediumint(9) DEFAULT NULL COMMENT '编号',
`ename` varchar(20) DEFAULT NULL COMMENT '员工姓名',
`job` varchar(9) DEFAULT NULL COMMENT '工作',
`mgr` mediumint(9) DEFAULT NULL COMMENT '上级编号',
`hiredate` date DEFAULT NULL COMMENT '入职时间',
`sal` decimal(7,2) DEFAULT NULL COMMENT '薪水',
`comm` decimal(7,2) DEFAULT NULL COMMENT '红利',
`deptno` mediumint(9) DEFAULT NULL COMMENT '部门编号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
创建函数,假如报错:This function has none of DETERMINISTIC……
由于开启过慢查询日志,因为我们开启了bin_log,我们就必须为我们的function指定一个参数。
SHOW VARIABLES LIKE 'log_bin_trust_function_creators';
SET GLOBAL log_bin_trust_function_creators=1;
这样添加参数以后,如果MySQL重启,上述参数又会消失,永久生效的办法,还是修改配置文件。
[mysqld]
log_bin_trust_function_creators=1
保证每条数据都不相同
DELIMITER $$
CREATE FUNCTION rand_string ( n INT ) RETURNS VARCHAR ( 255 ) BEGIN
DECLARE
chars_str VARCHAR ( 100 ) 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 $$
-- 删除函数
DROP FUNCTION rand_string
DELIMITER $$
CREATE FUNCTION rand_num () RETURNS INT ( 5 ) BEGIN
DECLARE
i INT DEFAULT 0;
SET i = FLOOR( 100 + RAND()* 10 );
RETURN i;
END $$
-- 删除函数
DROP FUNCTION rand_num
DELIMITER $$
CREATE PROCEDURE insert_emp (
IN START INT ( 10 ),
IN max_num INT ( 10 )) BEGIN
DECLARE
i INT DEFAULT 0;# 把autocommit 设置为0
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO emp ( empno, ename, job, mgr, hiredate, sal, comm, deptno )
VALUES
(
( START + i ),
rand_string ( 6 ),
'SALESMAN',
0001,
CURDATE(),
2000,
400,
rand_num ());
UNTIL i = max_num
END REPEAT;
COMMIT;
END $$
-- 删除存储过程
DROP PROCEDURE insert_emp;
DELIMITER $$
CREATE PROCEDURE insert_dept (
IN START INT ( 10 ),
IN max_num INT ( 10 )) BEGIN
DECLARE
i INT DEFAULT 0;# 把autocommit 设置为0
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO dept ( deptno, dname, loc )
VALUES
(
( START + i ),
rand_string ( 10 ),
rand_string ( 8 ));
UNTIL i = max_num
END REPEAT;
COMMIT;
END $$
-- 删除存储过程
DROP PROCEDURE insert_dept;
DELIMITER;
CALL insert_dept(100,10);
-- 执行存储过程,往emp表添加50万条数据
CALL insert_emp(100001,500000);
ShowProfile,是MySQL提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL调优的测量
官网地址:https://dev.mysql.com/doc/refman/5.5/en/show-profile.html
默认情况下,参数处于关闭状态,并保存最近15次的运行结果。
SHOW VARIABLES like 'profiling';
-- 或者
SHOW VARIABLES like 'profiling%';
-- 默认是关闭的,使用前需要开启
SET profiling = on;
-- 查看是否开启
SHOW VARIABLES like 'profiling';
-- 测试SQL
select * from emp group by id%10 limit 150000;
select * from emp group by id%20 order by 5;
show profiles;
show profile cpu,block io for query 上一步获取到的Query_ID;
不要在生产环境中启用
# 在MySQL的my.cnf中,设置如下:
# 开启
general_log=1
# 记录日志文件的路径
general_log_file=/path/logfile
# 输出格式
log_output=FILE
-- 命令
SET GLOBAL general_log=1;
SET GLOBAL log_output='TABLE';
-- 此后,所有编写的SQL语句,都会记录到MySQL库里的general_log表,可以用下面的名称查看
select * from mysql.general_log;
锁是计算机协调多个进程或线程并发访问某一资源的机制。
在数据库中,除传统的计算资源(如CPU、RAM、IO等)的争用意外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定力度大,发生锁冲突的概率最高,并发度最低。
-- 使用MyISAM引擎
CREATE TABLE `mylock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
-- 插入数据
INSERT INTO mylock(name) VALUES ('a');
INSERT INTO mylock(name) VALUES ('b');
INSERT INTO mylock(name) VALUES ('c');
INSERT INTO mylock(name) VALUES ('d');
INSERT INTO mylock(name) VALUES ('e');
-- 手动增加表锁
lock table 表名 read(write),表名2 read(write),其他;
-- 查看表上加过的锁
show open tables;
-- 释放所有锁
unlock tables;
-- 添加读锁
lock table mylock read;
-- 读表
select * from mylock;
-- 读表没有问题
-- 尝试更新数据
update mylock set name = 'a2' where id = 1;
ERROR 1099 - Table 'mylock' was locked with a READ lock and can't be updated
-- 当前session插入或者更新锁定的表都会提示错误
-- 其他session插入或者更新锁定表会一直等待获得锁
-- 读其他表
select * from book;
ERROR 1100 - Table 'book' was not locked with LOCK TABLES
-- 当前session不能查询没有锁定的表
-- 其他连接可以查询或者更新未锁定的表
-- 当前连接添加写锁
lock table mylock write;
-- 当前连接对锁定表的查询、更新、插入操作都可以执行
-- 其他连接对锁定表的查询被阻塞,需要等待锁被释放
MyISAM在执行查询语句(SELECT)前,会自动给涉及到的所有表加读锁,在执行增删改操作前,会自动给设计的表加写锁。
MySQL的表级锁有两种模式:
表共享读锁(Table Read Lock)
表独占写锁(Table Write Lock)
锁类型 | 可否兼容 | 读锁 | 写锁 |
---|---|---|---|
读锁 | 是 | 是 | 否 |
写锁 | 是 | 否 | 否 |
结论:
结合上表,所以对MyISAM表进行操作,会有以下情况:
简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁会把读和写都堵塞
show open tables;
-- 可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定
show status like 'table%';
这里有两个状态变量记录MySQL内部表级锁定的情况,这两个变量说明如下:
此外,MyISAM的读写锁调度是写优先,这也是MyISAM不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。
偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
InnoDB和MyISAM的最大不同有两点:一是支持事务(TRANSACTION),二是采用了行级锁
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
当两个或多个事务选择同一行,然后基于选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——最后的更新覆盖了由其他事务所做的更新。
例如,两个程序员修改同一java文件。每个程序员独立的更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。
如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时候,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏“数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象并形象的叫做”脏读“。
一句话,事务A读取了事务B已修改但尚未提交 的数据,还在这个数据基础上做了操作。此时,如果事务B回滚,A读取的数据无效,不符合一致性要求。
一个事务在读取某个数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了。这种现象就叫做“不可重复读”。
一句话,事务A读取了事务B已经提交的修改数据,不符合隔离性。
一个事务按照相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为”幻读“。
一句话,事务A读取刅了事务B提交的新数据,不符合隔离性。
幻读与脏读有点类似:
脏读、不可重复读和幻读,其实都是数据库一致性问题,必须由数据库提供一定的事务隔离机制来解决。
读数据一致性及允许的并发副作用/隔离级别 | 读数据一致性 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
未提交读(Read uncommitted) | 最低级别,只能保证不读取物理上损坏的数据 | 是 | 是 | 是 |
已提交读(Read committed) | 语句级 | 否 | 是 | 是 |
可重复读(Repeatable read) | 事务级 | 否 | 否 | 是 |
可序列化(Serializable) | 最高级别,事务级 | 否 | 否 | 否 |
数据库的事务隔离越严格,并发副作用越小,但付出的代价越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关系数据并发访问的能力。
查看当前数据库的事务隔离级别:
SHOW VARIABLES LIKE 'tx_isolation';
-- 创建表,存储引擎为innodb
CREATE TABLE `innodb_lock` (
`a` int(11) DEFAULT NULL,
`b` varchar(16) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入测试数据
INSERT INTO innodb_lock VALUES(1,'b2');
INSERT INTO innodb_lock VALUES(1,'b1');
INSERT INTO innodb_lock VALUES(3,'3');
INSERT INTO innodb_lock VALUES(4,'4000');
INSERT INTO innodb_lock VALUES(5,'5000');
INSERT INTO innodb_lock VALUES(6,'6000');
INSERT INTO innodb_lock VALUES(7,'7000');
INSERT INTO innodb_lock VALUES(8,'8000');
INSERT INTO innodb_lock VALUES(9,'9000');
-- 新增索引
create index idx_innodb_lock_a_ind on innodb_lock(a);
create index idx_innodb_lock_b_ind on innodb_lock(b);
客户端1 | 客户端2 |
---|---|
关闭自动提交:set autommit=0; | 关闭自动提交:set autommit=0; |
更新但是不提交,没有手写commit:update innodb_lock set b = ‘4001’ where a = 4 | 执行更新语句:更新但是不提交,没有手写commit:update innodb_lock set b = ‘4001’ where a = 1被阻塞,只能等待 |
提交更新:commit; | 解除阻塞,更新下正常进行 |
提交更新:commit; |
索引失效场景:索引类型为VARCHAR的,必须加上单引号
客户端1 | 客户端2 |
---|---|
关闭自动提交:set autommit=0; | 关闭自动提交:set autommit=0; |
正常案例:更新id=4的语句:update innodb_lock set b = ‘4002’ where a = 4 | 正常案例:更新id=9的语句:update innodb_lock set b = ‘9001’ where a = 0 |
提交更新,正常:commit; | 提交更新,正常:commit; |
已添加索引,b为VARCHAR类型 | |
错误案例:更新b='4002’的语句:update innodb_lock set a = 42 where b = 4002 | 更新id=9的语句:update innodb_lock set b = ‘9002’ where a = 0被阻塞,只能等待 |
提交更新:commit; | 解除阻塞,更新正常进行 |
提交更新:commit; |
客户端1 | 客户端2 |
---|---|
关闭自动提交:set autommit=0; | 关闭自动提交:set autommit=0; |
更新语句:update innodb_lock set b=‘0616’ where a > 1 and b < 6 | 新增语句:insert into innodb_lock values(2,‘2000’); 被阻塞,只能等待 |
提交更新:commit; | 解除阻塞,插入正常进行 |
提交更新:commit; |
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内并不存在的记录,叫做”间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的“间隙锁(Next-Key锁)”。
因为Query执行过程中通过范围查找的话,会锁定整个范围内所有的索引键值,即使这个键值并不存在。
间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下可能会对性能造成很大的危害。
通过select xxx for update锁定某一行后,其他的操作会被阻塞,直到锁定行的会话提交commit
InnoDB存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,InnoDB的整体性能和MyISAM相比就会有比较明显的优势了。
但是,InnoDB的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让InnoDB的整体性能表现不仅不能比MyISAM高,甚至可能会更差。
通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况。
SHOW STATUS LIKE 'innodb_row_lock%';
对各个状态量的说明如下:
对于这5个状态变量,比较重要的主要是Innodb_row_lock_time_avg(等待平均时长)、Innodb_row_lock_waits(等待总次数)、Innodb_row_lock_time(等待总时长)这三项。
了解即可
开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
Slave会从Master读取binlog来进行数据同步。
MySQL复制过程分为三步:
延时
要求:
- MySQL版本一直且后台以服务运行
- 主从服务器在同一网段,互相之间可以ping通
主从配置都是在[mysqld]节点下,都是小写
以Windows为例,修改my.ini配置文件
从机以Linux服务器示例,修改my.cnf配置文件
由于配置文件已经修改,需要重启MySQL服务。
主机与从机都需要关闭防火墙。
-- 建立账户并授权
GRANT REPLICATION SLAVE ON *.* TO 'test_sync'@'从机数据库IP' IDENTIFIED BY '123456';
-- 刷新配置
flush privileges;
查询master的状态
SHOW master STATUS;
-- 记录下File和Position的值
执行完此步骤后不要再操作主服务MySQL,防止主服务器状态值变化
CHANGE MASTER TO MASTER_HOST=’主机IP’,
MASTER_USER='test_sync',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='File名字',
MASTER_LOG_POS=Position数字;
从服务器的复制功能
-- 启动
start slave;
-- 停止
stop slave;
查看状态
SHOW SLAVE STATUS\G
-- 查看下面两个参数,都是YES,则说明配置成功
Slave_IO_Running:Yes
Slave_SQL_Running:Yes
主机新建数据库、新建表、新增记录,测试从服务器的复制功能。
g events;
延时
要求:
- MySQL版本一直且后台以服务运行
- 主从服务器在同一网段,互相之间可以ping通
主从配置都是在[mysqld]节点下,都是小写
以Windows为例,修改my.ini配置文件
从机以Linux服务器示例,修改my.cnf配置文件
由于配置文件已经修改,需要重启MySQL服务。
主机与从机都需要关闭防火墙。
-- 建立账户并授权
GRANT REPLICATION SLAVE ON *.* TO 'test_sync'@'从机数据库IP' IDENTIFIED BY '123456';
-- 刷新配置
flush privileges;
查询master的状态
SHOW master STATUS;
-- 记录下File和Position的值
执行完此步骤后不要再操作主服务MySQL,防止主服务器状态值变化
CHANGE MASTER TO MASTER_HOST=’主机IP’,
MASTER_USER='test_sync',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='File名字',
MASTER_LOG_POS=Position数字;
从服务器的复制功能
-- 启动
start slave;
-- 停止
stop slave;
查看状态
SHOW SLAVE STATUS\G
-- 查看下面两个参数,都是YES,则说明配置成功
Slave_IO_Running:Yes
Slave_SQL_Running:Yes
主机新建数据库、新建表、新增记录,测试从服务器的复制功能。