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

1. MySql中常用工具

1.1 mysql

该mysql不是指mysql服务,而是指mysql的客户端工具。
语法 :

mysql [options] [database]

1.1.1 连接选项

参数 :
-u, --user=name 指定用户名
-p, --password[=name] 指定密码
-h, --host=name 指定服务器IP或域名
-P, --port=# 指定连接端口
示例 :
mysql -h 127.0.0.1 -P 3306 -u root -p
mysql -h127.0.0.1 -P3306 -uroot -p2143

1.1.2 执行选项

-e, --execute=name 执行SQL语句并退出

此选项可以在Mysql客户端执行SQL语句,而不用连接到MySQL数据库再执行,对于一些批处理脚本,这种方式尤其方便。

示例:
mysql -uroot -p2143 db01 -e “select * from tb_book”;
在这里插入图片描述

1.2 mysqladmin

mysqladmin 是一个执行管理操作的客户端程序。可以用它来检查服务器的配置和当前状态、创建并删除数据库等。
可以通过 : mysqladmin --help 指令查看帮助文档
在这里插入图片描述

示例:

mysqladmin -uroot -p123456 create 'test01';
mysqladmin -uroot -p123456 drop 'test01';
mysqladmin -uroot -p123456 version;

1.3 mysqlbinlog

由于服务器生成的二进制日志文件以二进制格式保存,所以如果想要检查这些文本的文本格式,就会使用到mysqlbinlog 日志管理工具。
语法 :

mysqlbinlog [options] log-files1 log-files2 ...
选项:
-d, --database=name : 指定数据库名称,只列出指定的数据库相关操作。
-o, --offset=# : 忽略掉日志中的前n行命令。
-r,--result-file=name : 将输出的文本格式日志输出到指定文件。
-s, --short-form : 显示简单格式, 省略掉一些信息。
--start-datatime=date1 --stop-datetime=date2 : 指定日期间隔内的所有日志。
--start-position=pos1 --stop-position=pos2 : 指定位置间隔内的所有日志。

1.4 mysqldump

mysqldump 客户端工具用来备份数据库或在不同数据库之间进行数据迁移。备份内容包含创建表,及插入表的SQL语句。
语法 :

mysqldump [options] db_name [tables]
mysqldump [options] --database/-B db1 [db2 db3...]
mysqldump [options] --all-databases/-A

1.4.1 连接选项

参数 :
-u, --user=name 指定用户名
-p, --password[=name] 指定密码
-h, --host=name 指定服务器IP或域名
-P, --port=# 指定连接端口

1.4.2 输出内容选项

参数:
--add-drop-database 在每个数据库创建语句前加上 Drop database 语句
--add-drop-table 在每个表创建语句前加上 Drop table 语句 , 默认开启 ; 不开启 (--skip-add-drop-table)
-n, --no-create-db 不包含数据库的创建语句
-t, --no-create-info 不包含数据表的创建语句
-d --no-data 不包含数据
-T, --tab=name 自动生成两个文件:一个.sql文件,创建表结构的语句;一个.txt文件,数据文件,相当于select into outfile

示例 :

mysqldump -uroot -p2143 db01 tb_book --add-drop-database --add-drop-table > a
mysqldump -uroot -p2143 -T /tmp test city

在这里插入图片描述

1.5 mysqlimport/source

mysqlimport 是客户端数据导入工具,用来导入mysqldump 加 -T 参数后导出的文本文件。
语法:

mysqlimport [options] db_name textfile1 [textfile2...]

示例:

mysqlimport -uroot -p123456 db_test /tmp/city.txt

如果需要导入sql文件,可以在mysql命令行中使用source 指令 :

source /root/tb_book.sql

因为上边的例子用的sql方式,所以这里继续:
在这里插入图片描述

1.6 mysqlshow

mysqlshow 客户端对象查找工具,用来很快地查找存在哪些数据库、数据库中的表、表中的列或者索引。

语法:

mysqlshow [options] [db_name [table_name [col_name]]]

参数:

--count 显示数据库及表的统计信息(数据库,表 均可以不指定)
-i 显示指定数据库或者指定表的状态信息

示例:

#查询每个数据库的表的数量及表中记录的数量
mysqlshow -uroot -p123456 --count
#查询demo_01库中每个表中的字段数,及行数
mysqlshow -uroot -p123456 demo_01 --count
#查询demo_01库中city表的详细情况
mysqlshow -uroot -p123456 demo_01 city --count

在这里插入图片描述

2. Mysql 日志

在任何一种数据库中,都会有各种各样的日志,记录着数据库工作的方方面面,以帮助数据库管理员追踪数据库曾经发生过的各种事件。MySQL 也不例外,在 MySQL 中,有 4 种不同的日志,分别是错误日志、二进制日志(BINLOG 日志)、查询日志和慢查询日志,这些日志记录着数据库在不同方面的踪迹。

2.1 错误日志

错误日志是 MySQL 中最重要的日志之一,它记录了当 mysqld 启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。当数据库出现任何故障导致无法正常使用时,可以首先查看此日志。

该日志是默认开启的 ,(5.6版本) 默认存放目录为 mysql 的数据目录(var/lib/mysql), 默认的日志文件名为hostname.err(hostname是主机名)。(5.7版本在/var/log/mysqld.log)

查看日志位置指令 :

show variables like 'log_error%';

查看日志内容 :

tail -f /var/log/mysqld.log

2.2 二进制日志

2.2.1概述

二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句,但是不包括数据查询语句。此日志对于灾难时的数据恢复起着极其重要的作用,MySQL的主从复制, 就是通过该binlog实现的。

二进制日志,默认情况下是没有开启的,需要到MySQL的配置文件中开启,并配置MySQL日志的格式。

配置文件位置 : /etc/my.cnf(用命令find / -name 'my.cnf’找)

日志存放位置 : 配置时,给定了文件名但是没有指定路径,日志默认写入Mysql的数据目录。

#配置开启binlog日志, 日志的文件前缀为 mysqlbin -----> 生成的文件名如 : mysqlbin.000001,mysqlbin.000002
log_bin=mysqlbin
#配置二进制日志的格式
binlog_format=STATEMENT
#开启日志后一定要配置server_id,否则重启会报错
server-id=1

最后记得重启服务:systemctl restart mysqld

2.2.2 日志格式

STATEMENT
该日志格式在日志文件中记录的都是SQL语句(statement),每一条对数据进行修改的SQL都会记录在日志文件中,通过Mysql提供的mysqlbinlog工具,可以清晰的查看到每条语句的文本。主从复制的时候,从库(slave)会将日志解析为原文本,并在从库重新执行一次。

ROW
该日志格式在日志文件中记录的是每一行的数据变更,而不是记录SQL语句。比如,执行SQL语句 : update tb_book set status=‘1’ , 如果是STATEMENT 日志格式,在日志中会记录一行SQL文件; 如果是ROW,由于是对全表进行更新,也就是每一行记录都会发生变更,ROW 格式的日志中会记录每一行的数据变更。

MIXED
这是目前MySQL默认的日志格式,即混合了STATEMENT 和 ROW两种格式。默认情况下采用STATEMENT,但是在一些特殊情况下采用ROW来进行记录。MIXED 格式能尽量利用两种模式的优点,而避开他们的缺点。

2.2.3 日志读取

由于日志以二进制方式存储,不能直接读取,需要用mysqlbinlog工具来查看,语法如下 :

mysqlbinlog log-file;

查看STATEMENT格式日志
执行插入语句 :

insert into tb_book values(null,'C语言','2088-05-01','0');

查看日志文件:
在这里插入图片描述
mysqlbin.index : 该文件是日志索引文件 ,记录日志的文件名;
mysqlbin.000001 :日志文件
查看日志内容 :

mysqlbinlog mysqlbin.000001;

在这里插入图片描述
查看ROW格式日志
配置 :
在这里插入图片描述
然后重启服务。

插入数据:

insert into tb_book values(null,'SpringCloud实战','2088-05-05','0');

可以看到日志文件多了一个:
在这里插入图片描述
如果日志格式是 ROW , 直接查看数据 , 是查看不懂的 ; 可以在mysqlbinlog 后面加上参数 -vv

mysqlbinlog -vv mysqlbin.000002

在这里插入图片描述

2.2.4 日志删除

对于比较繁忙的系统,由于每天生成日志量大 ,这些日志如果长时间不清楚,将会占用大量的磁盘空间。下面我们将会讲解几种删除日志的常见方法 :

方式一
通过 Reset Master 指令删除全部 binlog 日志,删除之后,日志编号,将从 xxxx.000001重新开始 。

方式二
执行指令 purge master logs to 'mysqlbin.******',该命令将删除 ****** 编号之前的所有日志。

方式三
执行指令 purge master logs before 'yyyy-mm-dd hh:mm:ss' ,该命令将删除日志为 “yyyy-mm-dd hh:mm:ss” 之前产生的所有日志 。

方式四
设置参数expire_logs_days=# ,此参数的含义是设置日志的过期天数, 过了指定的天数后日志将会被自动删除,这样将有利于减少DBA 管理日志的工作量。
配置如下 :
在这里插入图片描述

2.3 查询日志

查询日志中记录了客户端的所有操作语句,而二进制日志不包含查询数据的SQL语句。

默认情况下, 查询日志是未开启的。如果需要开启查询日志,可以设置以下配置 :

#该选项用来开启查询日志 , 可选值 : 0 或者 1 ; 0 代表关闭, 1 代表开启
general_log=1
#设置日志的文件名 , 如果没有指定, 默认的文件名为 host_name.log
general_log_file=file_name

在 mysql 的配置文件my.cnf 中配置如下内容 :
在这里插入图片描述
配置完毕之后,在数据库执行以下操作 :

select * from tb_book;
select * from tb_book where id = 1;
update tb_book set name = 'lucene入门指南' where id = 5;
select * from tb_book where id < 8;

执行完毕之后, 再次来查询设置的mysql_query.log日志文件 :
在这里插入图片描述

2.4 慢查询日志

慢查询日志记录了所有执行时间超过参数 long_query_time 设置值并且扫描记录数不小于min_examined_row_limit 的所有的SQL语句的日志。

long_query_time 默认为 10 秒,最小为 0, 精度可以到微秒。

2.4.1 文件位置和格式

慢查询日志默认是关闭的 。可以通过两个参数来控制慢查询日志 :

# 该参数用来控制慢查询日志是否开启, 可取值: 1 和 0 , 1 代表开启, 0 代表关闭
slow_query_log=1
# 该参数用来指定慢查询日志的文件名
slow_query_log_file=slow_query.log
# 该选项用来配置查询的时间限制, 超过这个时间将认为值慢查询, 将需要进行日志记录, 默认10s
long_query_time=1

2.4.2 日志的读取

和错误日志、查询日志一样,慢查询日志记录的格式也是纯文本,可以被直接读取。

查询long_query_time 的值。
在这里插入图片描述
执行一个慢查询试试(用那张三百万数据的表耗时了五秒多):
在这里插入图片描述
如果慢查询日志内容很多, 直接查看文件,比较麻烦, 这个时候可以借助于mysql自带的 mysqldumpslow 工具, 来对慢查询日志进行分类汇总。
在这里插入图片描述

3. Mysql主从复制

3.1 复制概述

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

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

3.2 复制原理

MySQL 的主从复制原理如下。
在这里插入图片描述
从上层来看,复制分成三步:

  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
  1. 执行完毕之后,需要重启Mysql:
systemctl restart mysqld
  1. 创建同步数据的账户,并且进行授权操作:
grant replication slave on *.* to 'slave128'@'192.168.75.128' identified by 'slave128';

flush privileges;

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

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

在这里插入图片描述
字段含义:

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

3.4.2 slave(从库)

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

  1. 在 slave 端配置文件中,配置如下内容:
#mysql服务端ID,唯一
server-id=2
#指定binlog日志
log-bin=/var/lib/mysql/mysqlbin
  1. 执行完毕之后,需要重启Mysql:
systemctl restart mysqld
  1. 执行如下指令 (指定当前从库对应的主库的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;
  1. 开启同步操作
start slave;
show slave status\G;

如果出现如下错误:
在这里插入图片描述
则还需要改一个地方:
在这里插入图片描述
可以看到因为是克隆,两台机子uuid还是一样,更改下从机(随便改):
在这里插入图片描述
然后重启服务。

然后重新配置(又报错了,根据提示需要先停止同步):
在这里插入图片描述
可以看到配置成功了:
在这里插入图片描述

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');
  1. 在从库中查询数据,进行验证 :
    在从库中,可以查看到刚才创建的数据库和查询user表中的数据:
    在这里插入图片描述

4. MySQL读写分离

4.1 概述

在Mysql主从复制的基础上,可以使用读写分离来降低单台Mysql节点的压力,从而来提高访问效率,读写分离的架构如下:
在这里插入图片描述
对于读写分离的实现,可以通过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语句,来查看是否读的时候, 从从库中读取 ; 写入操作的时候,是否写入到主库(因为上边开启了主从复制,所以从库的写操作数也会更新)。
在这里插入图片描述

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 性能优化 - 索引

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

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 中,这样可以提高插入以及查询的效率。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值