MySQL高级第四篇(共四篇)之MySQL常用工具、MySQL日志、MySQL主从复制、MySQL读写分离、MySQL性能优化(二)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 复制是指将主数据库的DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。

3. Mysql主从复制


3.1 复制概述

复制是指将主数据库的DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。


MySQL支持一台主库同时向多台从库进行复制, 从库同时也可以作为其他从服务器的主库,实现链状复制。


3.2 复制原理

MySQL 的主从复制原理如下。

20210204215326597.png

从上层来看,复制分成三步:


1.Master 主库在事务提交时,会把数据变更作为时间 Events 记录在二进制日志文件 Binlog 中。

2.主库推送二进制日志文件 Binlog 中的日志事件到从库的中继日志 Relay Log 。

3.slave重做中继日志中的事件,将改变反映它自己的数据。


3.3 复制优势

MySQL 复制的要点主要包含以下三个方面:

1.主库出现问题,可以快速切换到从库提供服务。

2.可以在从库上执行查询操作,从主库中更新,实现读写分离,降低主库的访问压力。

3.可以在从库中执行备份,以避免备份期间影响主库的服务。


3.4 搭建步骤


3.4.1 master(主库)

1.在master 的配置文件(/usr/my.cnf)中,配置如下内容:

#mysql 服务ID,保证整个集群环境中唯一
server-id=1
#mysql binlog 日志的存储路径和文件名
log-bin=/var/lib/mysql/mysqlbin
#错误日志,默认已经开启
#log-err
#mysql的安装目录
#basedir
#mysql的临时目录
#tmpdir
#mysql的数据存放目录
#datadir
#是否只读,1 代表只读, 0 代表读写
read-only=0
#忽略的数据, 指不需要同步的数据库
binlog-ignore-db=mysql
#指定同步的数据库
#binlog-do-db=db01


2.执行完毕之后,需要重启Mysql:

systemctl restart mysqld


3.创建同步数据的账户,并且进行授权操作:

grant replication slave on *.* to 'slave128'@'192.168.75.128' identified by 'slave128';
flush privileges;

如果提示Your password does not satisfy the current policy requirements,则根据需要自己修改下配置,参考链接


4.查看master状态(因为之前配置过日志,可以先清除一下reset master):

show master status;

20210205101322864.png


字段含义:

File : 从哪个日志文件开始推送日志文件
Position : 从哪个位置开始推送日志
Binlog_Ignore_DB : 指定不需要同步的数据库


3.4.2 slave(从库)

首先把虚拟机克隆一台,然后配置下网络就能使用了。

1.在 slave 端配置文件中,配置如下内容:

#mysql服务端ID,唯一
server-id=2
#指定binlog日志
log-bin=/var/lib/mysql/mysqlbin


2.执行完毕之后,需要重启Mysql:

systemctl restart mysqld


3.执行如下指令 (指定当前从库对应的主库的IP地址,用户名,密码,从哪个日志文件开始的那个位置开始同步推送日志。):

change master to master_host= '192.168.75.127', master_user='slave128',master_password='slave128', master_log_file='mysqlbin.000001', master_log_pos=154;


4.开启同步操作

start slave;
show slave status\G;


如果出现如下错误:

20210205102514224.png


则还需要改一个地方:

20210205102547433.png


可以看到因为是克隆,两台机子uuid还是一样,更改下从机(随便改):

20210205102747546.png

然后重启服务。


然后重新配置(又报错了,根据提示需要先停止同步):

20210205103228102.png


可以看到配置成功了:

20210205103325896.png


3.4.3 验证同步操作

1.在主库中创建数据库,创建表,并插入数据 :

create database db01;
use db01;
create table user(
id int(11) not null auto_increment,
name varchar(50) not null,
sex varchar(1),
primary key (id)
)engine=innodb default charset=utf8;
insert into user(id,name,sex) values(null,'Tom','1');
insert into user(id,name,sex) values(null,'Trigger','0');
insert into user(id,name,sex) values(null,'Dawn','1');


2.在从库中查询数据,进行验证 :

在从库中,可以查看到刚才创建的数据库和查询user表中的数据:

20210205103946255.png


4. MySQL读写分离


4.1 概述

在Mysql主从复制的基础上,可以使用读写分离来降低单台Mysql节点的压力,从而来提高访问效率,读写分离的架构如下:


20210205154032470.png


对于读写分离的实现,可以通过Spring AOP 来进行动态的切换数据源。


4.2 创建项目

创建一个spring boot项目,使用mybatis操作数据库,数据库和表使用的是demo_03的tb_user表,那个表使用的是MyISAM引擎,因为后面有需求, 现在改为Innodb的。


表结构和数据:

DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `tb_user` VALUES (1, '令狐冲');
INSERT INTO `tb_user` VALUES (2, '田伯光');
SET FOREIGN_KEY_CHECKS = 1;


4.2.1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>mysqlrw</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mysqlrw</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <!--加入aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>


4.2.2 application.properties

#master
spring.datasource.master.driverClassName=com.mysql.cj.jdbc.Driver
#这里要用jdbcUrl,url是不行的,看默认数据源HikariConfig类
spring.datasource.master.jdbcUrl=jdbc:mysql://192.168.75.127:3306/demo_03?characterEncoding=utf8
spring.datasource.master.username=root
spring.datasource.master.password=123456
#slave
spring.datasource.slave.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.slave.jdbcUrl=jdbc:mysql://192.168.75.128:3306/demo_03?characterEncoding=utf8
spring.datasource.slave.username=root
spring.datasource.slave.password=123456


4.2.3 主要配置

三层架构的代码就不贴了,直接上配置:


当前线程需要使用的数据源:

package com.example.mysqlrw.config;
/**
 * @author baikunlong
 * @date 2021/2/5 11:32
 */
public class HandleDataSource {
    public static final ThreadLocal<String> holder=new ThreadLocal<>();
    public static void putDataSource(String dataSource){
        holder.set(dataSource);
    }
    public static String getDataSource(){
        return holder.get();
    }
}


自定义路由数据源

package com.example.mysqlrw.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * @author baikunlong
 * @date 2021/2/5 11:34
 */
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        System.out.println("determineCurrentLookupKey()--->: "+HandleDataSource.getDataSource());
        return HandleDataSource.getDataSource();
    }
}


注册数据源相关bean:

package com.example.mysqlrw.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
/**
 * @author baikunlong
 * @date 2021/2/5 11:36
 */
@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource(){
        return DataSourceBuilder.create().build();
    }
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource(){
        return DataSourceBuilder.create().build();
    }
    /**
     * 设置数据源路由,通过该类中的determineCurrentLookupKey方法决定使用哪个数据源
     * @return
     */
    @Bean
    public AbstractRoutingDataSource routingDataSource(){
        MyAbstractRoutingDataSource source = new MyAbstractRoutingDataSource();
        HashMap<Object, Object> map = new HashMap<>();
        map.put("master",masterDataSource());
        map.put("slave",slaveDataSource());
        // 默认使用主数据源
        source.setDefaultTargetDataSource(masterDataSource());
        source.setTargetDataSources(map);
        return source;
    }
    @Bean(name = "SqlSessionFactory")
    @Primary
    public SqlSessionFactory MasterSqlSessionFactory( DataSource routingDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(routingDataSource);//DataSource使用路由数据源
//        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//        try {
//            bean.setMapperLocations(resolver.getResources("classpath*:mappers/*.xml"));
//            bean.setConfigLocation(resolver.getResource("classpath:mybatis-config.xml"));
            return bean.getObject();
//        } catch (Exception e) {
//            e.printStackTrace();
//            throw new RuntimeException(e);
//        }
    }
    @Bean(name = "TransactionManager")
    @Primary
    public DataSourceTransactionManager testTransactionManager(DataSource routingDataSource) {
        return new DataSourceTransactionManager(routingDataSource);
    }
    @Bean(name = "SqlSessionTemplate")
    @Primary
    public SqlSessionTemplate MasterSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}


最后使用切面实现在每次请求时动态选择数据源:

package com.example.mysqlrw.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
 * @author baikunlong
 * @date 2021/2/5 11:43
 */
@Aspect
@Component
@Order(-9999)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
    @Pointcut("execution(* com.example.mysqlrw.controller.*.*(..))")
    public void pointcut(){}
    @Before("pointcut()")
    @Order(-9999)
    public void before(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        System.out.println("执行方法:"+name);
        //读用从机,比如get、find、select等开头的方法都可认定为读
        if(name.startsWith("get")){
            HandleDataSource.putDataSource("slave");
        }else {
            //写用主机
            HandleDataSource.putDataSource("master");
        }
    }
}

通过 @Order(-9999) 注解来控制事务管理器, 与该通知类的加载顺序 , 需要让通知类先加载 , 来判定使用哪个数据源 。


4.3 验证

在主库和从库中,执行如下SQL语句,来查看是否读的时候, 从从库中读取 ; 写入操作的时候,是否写入到主库(因为上边开启了主从复制,所以从库的写操作数也会更新)。

20210205160354281.png


5. MySQL性能优化


5.1 分析性能问题

系统中用户访问日志的数据量,随着时间的推移,这张表的数据量会越来越大,因此我们需要根据业务需求,来对日志查询模块的性能进行优化。


分页查询优化

由于在进行日志查询时,是进行分页查询,那也就意味着,在查看时,至少需要查询两次:

A. 查询符合条件的总记录数。–> count 操作

B. 查询符合条件的列表数据。–> 分页查询 limit 操作


通常来说,count() 都需要扫描大量的行(意味着需要访问大量的数据)才能获得精确的结果,因此是很难对该SQL进行优化操作的。如果需要对count进行优化,可以采用另外一种思路,可以增加汇总表,或者redis缓存来专门记录该表对应的记录数,这样的话,就可以很轻松的实现汇总数据的查询,而且效率很高,但是这种统计并不能保证百分之百的准确 。对于数据库的操作,“快速、精确、实现简单”,三者永远只能满足其二,必须舍掉其中一个。


条件查询优化

针对于条件查询,需要对查询条件,及排序字段建立索引。


读写分离

通过主从复制集群,来完成读写分离,使写操作走主节点, 而读操作,走从节点。


MySQL服务器优化


应用优化


5.1 性能优化 - 分页


5.1.1 优化count

创建一张表用来记录日志表的总数据量:

create table log_counter(
logcount bigint not null
)engine = innodb default CHARSET = utf8;


在每次插入数据之后,更新该表 :

update log_counter set logcount = logcount + 1


在进行分页查询时, 获取总记录数,从该表中查询既可。

select logcount from log_counter limit 1


5.1.2 优化limit

在进行分页时,一般通过创建覆盖索引,能够比较好的提高性能。一个非常常见,而又非常头疼的分页场景就是"limit 1000000,10" ,此时MySQL需要搜索出前1000010 条记录后,仅仅需要返回第 1000001 到 1000010 条记录,前1000000 记录会被抛弃,查询代价非常大。


当点击比较靠后的页码时,就会出现这个问题,查询效率非常慢。

优化前SQL:

select * from operation_log limit 3000000 , 10;


优化后SQL:

select * from operation_log t , (select id from operation_log order by id limit 3000000,10) b where t.id = b.id ;


5.2 性能优化 - 索引

20210205161748533.png

当根据操作人进行查询时, 查询的效率很低,耗时比较长。原因就是因为在创建数据库表结构时,并没有针对于操作人字段建立索引。

CREATE INDEX idx_user_method_return_cost ON
operation_log(operate_user,operate_method,return_class,cost_time);


同上 , 为了查询效率高,我们也需要对 操作方法、返回值类型、操作耗时 等字段进行创建索引,以提高查询效率。

CREATE INDEX idx_optlog_method_return_cost ON
operation_log(operate_method,return_class,cost_time);
CREATE INDEX idx_optlog_return_cost ON operation_log(return_class,cost_time);
CREATE INDEX idx_optlog_cost ON operation_log(cost_time);

总结就是一句话,根据最左原则,建立需要用到的所有的复合索引速度就能提升。


5.3 性能优化 - 排序

在查询数据时,如果业务需求中需要我们对结果内容进行了排序处理 ,这个时候,我们还需要对排序的字段建立适当的索引,来提高排序的效率 。


5.4 性能优化 - 读写分离

上面例子已经讲述了。


5.5性能优化 - 应用优化


5.5.1缓存

可以在业务系统中使用redis来做缓存,缓存一些基础性的数据,来降低关系型数据库的压力,提高访问效率。


5.5.2 全文检索

如果业务系统中的数据量比较大(达到千万级别),这个时候,如果再对数据库进行查询,特别是进行分页查询,速度将变得很慢(因为在分页时首先需要count求合计数),为了提高访问效率,这个时候,可以考虑加入Solr 或

者 ElasticSearch全文检索服务,来提高访问效率。


5.5.3 非关系数据库

也可以考虑将非核心(重要)数据,存在 MongoDB 中,这样可以提高插入以及查询的效率。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
20天前
|
存储 安全 关系型数据库
Mysql 的binlog日志的优缺点
MySQL的binlog(二进制日志)是一个记录数据库更改的日志文件,它包含了所有对数据库执行的更改操作,如INSERT、UPDATE和DELETE等。binlog的主要目的是复制和恢复。以下是binlog日志的优缺点: ### 优点: 1. **数据恢复**:当数据库出现意外故障或数据丢失时,可以利用binlog进行点恢复(point-in-time recovery),将数据恢复到某一特定时间点。 2. **主从复制**:binlog是实现MySQL主从复制功能的核心组件。主服务器将binlog中的事件发送到从服务器,从服务器再重放这些事件,从而实现数据的同步。 3. **审计**:b
|
30天前
|
SQL 关系型数据库 MySQL
MySQL数据库,可以使用二进制日志(binary log)进行时间点恢复
对于MySQL数据库,可以使用二进制日志(binary log)进行时间点恢复。二进制日志是MySQL中记录所有数据库更改操作的日志文件。要进行时间点恢复,您需要执行以下步骤: 1. 确保MySQL配置文件中启用了二进制日志功能。在配置文件(通常是my.cnf或my.ini)中找到以下行,并确保没有被注释掉: Copy code log_bin = /path/to/binary/log/file 2. 在需要进行恢复的时间点之前创建一个数据库备份。这将作为恢复的基准。 3. 找到您要恢复到的时间点的二进制日志文件和位置。可以通过执行以下命令来查看当前的二进制日志文件和位
100 1
|
4天前
|
负载均衡 关系型数据库 MySQL
MySQL-Proxy实现MySQL读写分离提高并发负载
MySQL-Proxy实现MySQL读写分离提高并发负载
|
1月前
|
关系型数据库 MySQL 数据库
使用 Docker 搭建一个“一主一从”的 MySQL 读写分离集群(超详细步骤
使用 Docker 搭建一个“一主一从”的 MySQL 读写分离集群(超详细步骤
64 0
|
1月前
|
存储 SQL 关系型数据库
[MySQL]事务原理之redo log,undo log
[MySQL]事务原理之redo log,undo log
|
1月前
|
SQL 缓存 关系型数据库
MySQL的万字总结(缓存,索引,Explain,事务,redo日志等)
MySQL的万字总结(缓存,索引,Explain,事务,redo日志等)
66 0
|
2月前
|
存储 监控 关系型数据库
ELK架构监控MySQL慢日志
ELK架构监控MySQL慢日志
|
2月前
|
SQL 运维 关系型数据库
MySQL中常见的几种日志类型
MySQL中常见的几种日志类型
|
4天前
|
SQL 存储 关系型数据库
MySQL Cluster集群安装及使用
MySQL Cluster集群安装及使用
|
8天前
|
关系型数据库 MySQL 数据库
《MySQL 简易速速上手小册》第1章:MySQL 基础和安装(2024 最新版)
《MySQL 简易速速上手小册》第1章:MySQL 基础和安装(2024 最新版)
35 4