MySQL

目录

概述

架构 

安装 

数据库

数据类型

创建

查询表结构

查看建表语句

删除

修改表结构

SQL

分类

CRUD

条件运算符

函数

统计函数

字符串函数

数学函数

日期函数

加密函数

流程函数

单表查询

条件

分组

排序

分页

多表查询

子查询

连接查询

内连接

左外连接

右外连接

合并查询

约束

主键约束

外键约束

唯一约束

非空约束

索引

主键索引

普通索引

唯一索引

全文索引

事务

特性

操作

隔离级别

用户管理

存储原理

索引最佳实践

查询计划

id

select_type

type

key

key_len

row

filtered

Extra

连接原理

Buffer Pool

Redo Log

Undo Log

MVCC

参考


概述

MySQL是一个数据库服务或数据库管理系统,它对外开发一个tcp端口接收客户端的SQL,然后通过解析SQL语句来明确用户的意图,操作文件系统上的文件。

架构 

 

  • 连接器:管理客户端连接、权限校验;
  • 查询缓存:缓存热点数据,提高查询效率;
  • 分析器:解析SQL语句;
  • 优化器:生成执行计划,算则索引等;
  • 执行器:与FS交互,驱动执行计划的执行,操作文FS上的数据;

安装 

操作系统:centos8

mysql版本:8

安装方式:yum

wget -i -c  https://repo.mysql.com//mysql80-community-release-el7-1.noarch.rpm
yum -y install mysql80-community-release-el7-1.noarch.rpm
yum module disable mysql
yum -y install mysql-community-server --nogpgcheck
systemctl start  mysqld.service
systemctl enable  mysqld.service
systemctl status mysqld.service

grep "password" /var/log/mysqld.log
2022-09-15T21:35:41.199269Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: i<Wv8jZEg7Om

mysql -uroot -p
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Gz2021..';
//放开防火墙
firewall-cmd --permanent --zone=public --add-port=3306/tcp
firewall-cmd --reload
//用新密码登陆
mysql -uroot -p

数据库

MySQL中的一个数据库对应了一组业务数据,体现在文件系统中就是一个文件夹,数据库中的表对应一个文件(文件的具体格式取决于采用的存储引擎类型)。

#创建数据库,并指定编码类型
create database dbName character set utf8;
#显示系统中有哪些数据库
show databases;
#选择一个数据库
use dbName;
#删除数据库
drop database dbName;

#将test数据库进行备份
mysqldump -u root -p -B test > ./db_test.sql
#从备份文件中恢复数据库
source /root/mysql/db_test.sq

查看MySQL所支持的存储引擎类型。 

mysql> show engines \g
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       |
| MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         |
| InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        |
| PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         |
| MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         |
| MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         |
| BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         |
| CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         |
| ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+

InnoDB是默认的存储引擎,存储引擎可以在数据库上设置也可以在表上设置,在表上的设置优先级高于在数据库上的设置。

在上面的多种存储引擎中最常用的是InnoDB、MyISAM和Memory

  • MyISAM不支持事务,也不支持外键,但它的访问速度最快;
  • InnnoDB是默认的存储引擎,支持事务、索引,支持锁,但会占用额外的存储空间和性能来实现复杂的功能;
  • Memory是基于内存的存储引擎,一般只针对临时表和不重要但需要较快查询速度的数据使用;

数据类型

整型

BIT TINYINT SMALLINT INT BIGINT

浮点型

DOUBLE FLOAT DECIMAL

布尔型

BOOLEAN

枚举型

ENUM

文本型

CHAR VARCHAR TINYTEXT MEDIUMTEXT TEXT LONGTEXT

二进制型

BLOB TINYBLOB MEDIUMBLOB LONGBLOB

时间日期型

DATE TIME DATETIME TIMESTAMP YEAR

  • salary decimal(10,3) default 0.0;decimal能够支持长达数值及指定长度的小数精度;
  • sex enum('male','female'); 枚举类型可以固话取值;
  • char是定长,varchar是变长,其中的长度都指的是字符数,但varchar能够支持的最大字符数并不是固定的,这取决于具体的编码方式,比如utf-8一个字符占3个字节,而gbk则占两个字节,因为锁占用的字节的长度是有范围限制的;
  • 时间类型可以与字符串进行直接比较,执行时会自动进行转换;
  • 选择能够满足需求的最小长度的数据类型能够有效节省存储空间,尤其是在大数据量的情况下;
  • 每个类型的说明具体参考 help contents → help Data Type; → help CHAR/VARCHAR/ENUM;

表时一组具有相同属性的数据的集合。

创建

mysql> create table user(
    ->  id int(11) primary key auto_increment,
    ->  name varchar(30),
    ->  password varchar(30),
    ->  birthday date
    -> )character set utf8 engine InnoDB;

查询表结构

mysql> desc user;
+----------+-------------+------+-----+---------+----------------+
| Field    | Type        | Null | Key | Default | Extra          |
+----------+-------------+------+-----+---------+----------------+
| id       | int         | NO   | PRI | NULL    | auto_increment |
| name     | varchar(30) | YES  |     | NULL    |                |
| password | varchar(30) | YES  |     | NULL    |                |
| birthday | date        | YES  |     | NULL    |                |
+----------+-------------+------+-----+---------+----------------+

查看建表语句

mysql> show create table user;
| user  | CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(30) DEFAULT NULL,
  `password` varchar(30) DEFAULT NULL,
  `birthday` date DEFAULT NULL,
  `salary` decimal(10,3) DEFAULT '0.000',
  `sex` enum('male','female') DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci |

删除

mysql> drop table user;

修改表结构

alter table tname add column columnName type defualt value;
alter table tname modify column columnName type default value;
alter table tname change oldCName newCName type default value;
alter table tname drop column columnName;

//修改表名
rename table oldName to newName;
//修改表的编码
alter table tName character set gbk;

SQL

分类

  • DDL:数据定义语句;
  • DML:数据修改语句;
  • DQL:数据查询语句;
  • DCL:数据控制语句;

CRUD

insert into tname (c1,c2,c3) values (v1,v2,v3),(v4,v5,v6);
//列的数量和类型要匹配
insert into select * from tname where ...;
update tname set cname1 = value1 [,cname2=value2] where ....
delete from tname where ...
select 
    [distinct] c1 as a1,c2 as a1,c3 as a3 
from tname 
where condition
  group by cn 
  having condition
  order by cx asc/desc
  limit start,rows;

条件运算符

条件运算符就是用在以true/false为返回值的条件,主要分为比较运算符和逻辑运算符两种:

  • 比较运算符
    • > 、 >= 、 <  、 <= 、 <>  、 != 、 =
    • between .. and ..
    • in 、not in
    • like、not like
    • is null 、is not null
  • 条件运算符
    • and、or、not

函数

统计函数

统计函数 count/sum/min/max/avg 通常与group by语句配合使用。

/*
count,统计数量,count(1)、count(*)、count(column),其中count(column)不会统计指定列上的值为null的行数。
*/
mysql> select count(*) from employees;
+----------+
| count(*) |
+----------+
|   300024 |
+----------+

//min/max求最大值或最小值

mysql> select max(salary), min(salary), emp_no from salaries group by emp_no limit 5;
+-------------+-------------+--------+
| max(salary) | min(salary) | emp_no |
+-------------+-------------+--------+
|       88958 |       60117 |  10001 |
|       72527 |       65828 |  10002 |
|       43699 |       40006 |  10003 |
|       74057 |       40054 |  10004 |
|       94692 |       78228 |  10005 |
+-------------+-------------+--------+

//avg求平均值
mysql> select avg(salary) from salaries;
+-------------+
| avg(salary) |
+-------------+
|  63810.7448 |
+-------------+

mysql> select sum(salary) from salaries;
+--------------+
| sum(salary)  |
+--------------+
| 181480757419 |
+--------------+
1 row in set (0.33 sec)

字符串函数

charset、concat、ucase、lcase、left、length、replace、strcmp、substring、ltrim、rtrim、trim

#返回某个字段的字符集
mysql> select charset(first_name) from employees limit 1;
+---------------------+
| charset(first_name) |
+---------------------+
| utf8mb4             |
+---------------------+


#字符串连接
mysql> select concat(first_name, ", hello!") from employees limit 1;
+--------------------------------+
| concat(first_name, ", hello!") |
+--------------------------------+
| Georgi, hello!                 |
+--------------------------------+

#将字符串转大写
mysql> select ucase(first_name) from employees limit 1;
+-------------------+
| ucase(first_name) |
+-------------------+
| GEORGI            |
+-------------------+


#比较两个字符串,> → 1  < → -1  = 0
mysql> select first_name, strcmp(first_name, 'Geora') from employees limit 1;
+------------+-----------------------------+
| first_name | strcmp(first_name, 'Geora') |
+------------+-----------------------------+
| Georgi     |                           1 |
+------------+-----------------------------+
1 row in set (0.00 sec)

mysql> select first_name, strcmp(first_name, 'Geori') from employees limit 1;
+------------+-----------------------------+
| first_name | strcmp(first_name, 'Geori') |
+------------+-----------------------------+
| Georgi     |                          -1 |
+------------+-----------------------------+
1 row in set (0.00 sec)

mysql> select first_name, strcmp(first_name, 'Georgi') from employees limit 1;
+------------+------------------------------+
| first_name | strcmp(first_name, 'Georgi') |
+------------+------------------------------+
| Georgi     |                            0 |
+------------+------------------------------+

#截取字符串,从左侧截取指定位数
mysql> select first_name,left(first_name,4) from employees limit 1;
+------------+--------------------+
| first_name | left(first_name,4) |
+------------+--------------------+
| Georgi     | Geor               |
+------------+--------------------+

#将字符串转小写
mysql> select lcase(first_name) from employees limit 1;
+-------------------+
| lcase(first_name) |
+-------------------+
| georgi            |
+-------------------+

#返回字符串长度
mysql> select first_name,length(first_name) from employees limit 1;
+------------+--------------------+
| first_name | length(first_name) |
+------------+--------------------+
| Georgi     |                  6 |
+------------+--------------------+

#字符串替换
mysql> select first_name, replace(first_name, 'i','_aaaa') from employees limit 1;
+------------+----------------------------------+
| first_name | replace(first_name, 'i','_aaaa') |
+------------+----------------------------------+
| Georgi     | Georg_aaaa                       |
+------------+----------------------------------+


#字符串截取,index从1开始
mysql> select first_name, substring(first_name, 2, 2) from employees limit 1;
+------------+-----------------------------+
| first_name | substring(first_name, 2, 2) |
+------------+-----------------------------+
| Georgi     | eo                          |
+------------+-----------------------------+

#去除空格
mysql> select ltrim('  abc'), rtrim('abc  '), trim(' abc ') from employees limit 1;
+----------------+----------------+---------------+
| ltrim('  abc') | rtrim('abc  ') | trim(' abc ') |
+----------------+----------------+---------------+
| abc            | abc            | abc           |
+----------------+----------------+---------------+

数学函数

abs、bin、ceiling、floor、format、least、rand

#取绝对值
mysql> select abs(-10) from dual;
+----------+
| abs(-10) |
+----------+
|       10 |
+----------+

#十进制转二进制
mysql> select bin(4) from dual;
+--------+
| bin(4) |
+--------+
| 100    |
+--------+


#向上获取一个>=给定值的最小整数
mysql> select ceiling(-10.9) from dual;
+----------------+
| ceiling(-10.9) |
+----------------+
|            -10 |
+----------------+

#求最小值
mysql> select least(10,9,33,8) from dual;
+------------------+
| least(10,9,33,8) |
+------------------+
|                8 |
+------------------+

#向上获取一个<=给定值的最小整数
mysql> select floor(-10.9) from dual;
+--------------+
| floor(-10.9) |
+--------------+
|          -11 |
+--------------+


#截取、添加小数位
mysql> select format(10, 2) from dual;
+---------------+
| format(10, 2) |
+---------------+
| 10.00         |
+---------------+
1 row in set (0.00 sec)

mysql> select format(10.3645, 2) from dual;
+--------------------+
| format(10.3645, 2) |
+--------------------+
| 10.36              |
+--------------------+


#获取一个随机数,范围是 0<=x<=1
mysql> select rand(10);
+--------------------+
| rand(10)           |
+--------------------+
| 0.6570515219653505 |
+--------------------+

日期函数

current_date、current_time、current_timestamp、date、date_add、date_sub

datediff、timediff、now、year、month、from_unixtime、unix_timestamp

mysql> select current_date(), current_time(), current_timestamp() from dual;
+----------------+----------------+---------------------+
| current_date() | current_time() | current_timestamp() |
+----------------+----------------+---------------------+
| 2022-09-17     | 13:06:41       | 2022-09-17 13:06:41 |
+----------------+----------------+---------------------+


#返回日期的部分
mysql> select date(current_timestamp()) from dual;
+---------------------------+
| date(current_timestamp()) |
+---------------------------+
| 2022-09-17                |
+---------------------------+


#在某日时间上添加指定的时间
mysql> SELECT DATE_ADD('2018-05-01',INTERVAL 1 DAY);
        -> '2018-05-02'
mysql> SELECT DATE_SUB('2018-05-01',INTERVAL 1 YEAR);
        -> '2017-05-01'
mysql> SELECT DATE_ADD('2020-12-31 23:59:59',
    ->                 INTERVAL 1 SECOND);
        -> '2021-01-01 00:00:00'
mysql> SELECT DATE_ADD('2018-12-31 23:59:59',
    ->                 INTERVAL 1 DAY);
        -> '2019-01-01 23:59:59'
mysql> SELECT DATE_ADD('2100-12-31 23:59:59',
    ->                 INTERVAL '1:1' MINUTE_SECOND);
        -> '2101-01-01 00:01:00'
mysql> SELECT DATE_SUB('2025-01-01 00:00:00',
    ->                 INTERVAL '1 1:1:1' DAY_SECOND);
        -> '2024-12-30 22:58:59'
mysql> SELECT DATE_ADD('1900-01-01 00:00:00',
    ->                 INTERVAL '-1 10' DAY_HOUR);
        -> '1899-12-30 14:00:00'
mysql> SELECT DATE_SUB('1998-01-02', INTERVAL 31 DAY);
        -> '1997-12-02'
mysql> SELECT DATE_ADD('1992-12-31 23:59:59.000002',
    ->            INTERVAL '1.999999' SECOND_MICROSECOND);
        -> '1993-01-01 00:00:01.000001'


#date_sub 的用法与date_add相同


#返回两个日期相差的天数
mysql> SELECT DATEDIFF('2007-12-31 23:59:59','2007-12-30');
+----------------------------------------------+
| DATEDIFF('2007-12-31 23:59:59','2007-12-30') |
+----------------------------------------------+
|                                            1 |
+----------------------------------------------+

#timediff 返回两个时间相差的时间,精确到秒
mysql> SELECT TIMEDIFF('2008-12-31 23:59:59.000001', '2008-12-30 01:01:01.000002');
+----------------------------------------------------------------------+
| TIMEDIFF('2008-12-31 23:59:59.000001', '2008-12-30 01:01:01.000002') |
+----------------------------------------------------------------------+
| 46:58:57.999999                                                      |
+----------------------------------------------------------------------+


#获取当前时间
mysql> select now();
+---------------------+
| now()               |
+---------------------+
| 2022-09-17 13:15:16 |
+---------------------+


#时间戳转日期
mysql> SELECT FROM_UNIXTIME(1447430881);
        -> '2015-11-13 10:08:01'
mysql> SELECT FROM_UNIXTIME(1447430881) + 0;
        -> 20151113100801
mysql> SELECT FROM_UNIXTIME(1447430881,
    ->                      '%Y %D %M %h:%i:%s %x');
        -> '2015 13th November 10:08:01 2015'



#日期转时间戳
mysql> SELECT UNIX_TIMESTAMP();
        -> 1447431666
mysql> SELECT UNIX_TIMESTAMP('2015-11-13 10:20:19');
        -> 1447431619
mysql> SELECT UNIX_TIMESTAMP('2015-11-13 10:20:19.012');
        -> 1447431619.012


#获取年 和 月份
mysql> select year(current_timestamp()), month(current_timestamp()), date(current_timestamp());
+---------------------------+----------------------------+---------------------------+
| year(current_timestamp()) | month(current_timestamp()) | date(current_timestamp()) |
+---------------------------+----------------------------+---------------------------+
|                      2022 |                          9 | 2022-09-17                |
+---------------------------+----------------------------+---------------------------+

加密函数

md5

#md5 加密
mysql> select md5(first_name) from employees limit 1;
+----------------------------------+
| md5(first_name)                  |
+----------------------------------+
| d1cf9daad124357a935536bda5f4ab47 |
+----------------------------------+

流程函数

if、ifnull、case..when..then..else..end;

mysql> select if(true,'beijing','guangzhou');
+--------------------------------+
| if(true,'beijing','guangzhou') |
+--------------------------------+
| beijing                        |
+--------------------------------+
1 row in set (0.00 sec)

mysql> select if(false,'beijing','guangzhou');
+---------------------------------+
| if(false,'beijing','guangzhou') |
+---------------------------------+
| guangzhou                       |
+---------------------------------+
1 row in set (0.00 sec)

mysql> select ifnull(null,'guangzhou');
+--------------------------+
| ifnull(null,'guangzhou') |
+--------------------------+
| guangzhou                |
+--------------------------+
1 row in set (0.00 sec)

mysql> select ifnull('aaa','guangzhou');
+---------------------------+
| ifnull('aaa','guangzhou') |
+---------------------------+
| aaa                       |
+---------------------------+
1 row in set (0.00 sec)



mysql> select case
    ->    when true then 'beiing'
    ->    when false then 'guangzhou'
    ->    else 'shenzhen' end;
+-----------------------------------------------------------------------------------------+
| case
   when true then 'beiing'
   when false then 'guangzhou'
   else 'shenzhen' end |
+-----------------------------------------------------------------------------------------+
| beiing                                                                                  |
+-----------------------------------------------------------------------------------------+

单表查询

条件

数值判断:
>、>=、<、<=、<>、!=、=
between..and..
in
like

逻辑判断:
and、or、not

分组

group by cnamec having  condition
#having 是对分组后的结果进行过滤的条件,也可以用子查询+where的方式实现
mysql> select count(1) cnt, emp_no from dept_emp group by emp_no having cnt > 1 limit 1;
+-----+--------+
| cnt | emp_no |
+-----+--------+
|   2 |  10010 |
+-----+--------+

排序

order by cname1 [,cname2] asc / desc
#按薪入职日期倒序排列,如果入职日期一样,按照薪资倒序排列
mysql> select * from salaries order by from_date desc,salary desc limit 10;
+--------+--------+------------+------------+
| emp_no | salary | from_date  | to_date    |
+--------+--------+------------+------------+
|  49068 | 121837 | 2002-08-01 | 9999-01-01 |
|  72127 | 120317 | 2002-08-01 | 9999-01-01 |
|  80354 | 118791 | 2002-08-01 | 9999-01-01 |
| 213241 | 116577 | 2002-08-01 | 9999-01-01 |
| 475293 | 116498 | 2002-08-01 | 9999-01-01 |
|  93618 | 116474 | 2002-08-01 | 9999-01-01 |
|  73632 | 116377 | 2002-08-01 | 9999-01-01 |
| 401873 | 116043 | 2002-08-01 | 9999-01-01 |
|  94172 | 115434 | 2002-08-01 | 9999-01-01 |
| 234261 | 115319 | 2002-08-01 | 9999-01-01 |
+--------+--------+------------+------------+

分页

start = (page - 1) * rows
limit start, rows

这种分页方式是内存分页,不是物理分页,如果数据量非常大的话,查询到最后的页数时会非常慢。

多表查询

子查询

子查询就是出现在select语句中的select语句,子查询可以作为临时表,也可以作为条件项。

#条件项-单行子查询
mysql> select * from employees where emp_no = (select emp_no from dept_manager where dept_no = 'd001' order by from_date limit 1);
+--------+------------+------------+------------+--------+------------+
| emp_no | birth_date | first_name | last_name  | gender | hire_date  |
+--------+------------+------------+------------+--------+------------+
| 110022 | 1956-09-12 | Margareta  | Markovitch | M      | 1985-01-01 |
+--------+------------+------------+------------+--------+------------+

#条件项-多行子查询
mysql> select * from employees where emp_no in (select emp_no from dept_manager where dept_no = 'd001' order by from_date);
+--------+------------+------------+------------+--------+------------+
| emp_no | birth_date | first_name | last_name  | gender | hire_date  |
+--------+------------+------------+------------+--------+------------+
| 110022 | 1956-09-12 | Margareta  | Markovitch | M      | 1985-01-01 |
| 110039 | 1963-06-21 | Vishwani   | Minakawa   | M      | 1986-04-12 |
+--------+------------+------------+------------+--------+------------+

#临时表
mysql> select temp.salary, e.* from (select * from salaries order by salary desc limit 10) temp, employees e where temp.emp_no = e.emp_no;
+--------+--------+------------+------------+-----------+--------+------------+
| salary | emp_no | birth_date | first_name | last_name | gender | hire_date  |
+--------+--------+------------+------------+-----------+--------+------------+
| 158220 |  43624 | 1953-11-14 | Tokuyasu   | Pesch     | M      | 1985-03-26 |
| 157821 |  43624 | 1953-11-14 | Tokuyasu   | Pesch     | M      | 1985-03-26 |
| 156286 | 254466 | 1963-05-27 | Honesty    | Mukaidono | M      | 1986-08-08 |
| 155709 |  47978 | 1956-03-24 | Xiahua     | Whitcomb  | M      | 1985-07-18 |
| 155513 | 253939 | 1957-12-03 | Sanjai     | Luders    | M      | 1987-04-15 |
| 155377 | 109334 | 1955-08-02 | Tsutomu    | Alameldin | M      | 1985-02-15 |
| 155190 | 109334 | 1955-08-02 | Tsutomu    | Alameldin | M      | 1985-02-15 |
| 154888 | 109334 | 1955-08-02 | Tsutomu    | Alameldin | M      | 1985-02-15 |
| 154885 | 109334 | 1955-08-02 | Tsutomu    | Alameldin | M      | 1985-02-15 |
| 154459 |  80823 | 1963-01-21 | Willard    | Baca      | M      | 1985-02-26 |
+--------+--------+------------+------------+-----------+--------+------------+

连接查询

内连接

内连接一般都会指定连接条件,否则会返回没有意义的笛卡尔积结果,内连接又可以分为自连接、等值连接和不等值连接,自连接就是表自己和自己连接,等值连接和不等值连接指的是两个或多个表的连接条件。

mysql> select temp.salary, e.* from employees e inner join (select * from salaries order by salary desc limit 10) temp on e.emp_no = temp.emp_no;
+--------+--------+------------+------------+-----------+--------+------------+
| salary | emp_no | birth_date | first_name | last_name | gender | hire_date  |
+--------+--------+------------+------------+-----------+--------+------------+
| 158220 |  43624 | 1953-11-14 | Tokuyasu   | Pesch     | M      | 1985-03-26 |
| 157821 |  43624 | 1953-11-14 | Tokuyasu   | Pesch     | M      | 1985-03-26 |
| 156286 | 254466 | 1963-05-27 | Honesty    | Mukaidono | M      | 1986-08-08 |
| 155709 |  47978 | 1956-03-24 | Xiahua     | Whitcomb  | M      | 1985-07-18 |
| 155513 | 253939 | 1957-12-03 | Sanjai     | Luders    | M      | 1987-04-15 |
| 155377 | 109334 | 1955-08-02 | Tsutomu    | Alameldin | M      | 1985-02-15 |
| 155190 | 109334 | 1955-08-02 | Tsutomu    | Alameldin | M      | 1985-02-15 |
| 154888 | 109334 | 1955-08-02 | Tsutomu    | Alameldin | M      | 1985-02-15 |
| 154885 | 109334 | 1955-08-02 | Tsutomu    | Alameldin | M      | 1985-02-15 |
| 154459 |  80823 | 1963-01-21 | Willard    | Baca      | M      | 1985-02-26 |
+--------+--------+------------+------------+-----------+--------+------------+

左外连接

在内连接中,不符合连接条件的记录会被过滤的,但在左外连接中,左表中不符合记录的数据会被保留。

右外连接

与左外连接相比,右外连接时右表的不符合连接条件的记录会被保留,但是左与右是相对的。

合并查询

union和union all能够将两个查询结果进行合并,区别在于union会对重复的结果进行合并,而union all不会进行合并;

mysql> (select * from salaries order by salary desc limit 10) union all (select * from salaries order by salary desc limit 10);
+--------+--------+------------+------------+
| emp_no | salary | from_date  | to_date    |
+--------+--------+------------+------------+
|  43624 | 158220 | 2002-03-22 | 9999-01-01 |
|  43624 | 157821 | 2001-03-22 | 2002-03-22 |
| 254466 | 156286 | 2001-08-04 | 9999-01-01 |
|  47978 | 155709 | 2002-07-14 | 9999-01-01 |
| 253939 | 155513 | 2002-04-11 | 9999-01-01 |
| 109334 | 155377 | 2000-02-12 | 2001-02-11 |
| 109334 | 155190 | 2002-02-11 | 9999-01-01 |
| 109334 | 154888 | 2001-02-11 | 2002-02-11 |
| 109334 | 154885 | 1999-02-12 | 2000-02-12 |
|  80823 | 154459 | 2002-02-22 | 9999-01-01 |
|  43624 | 158220 | 2002-03-22 | 9999-01-01 |
|  43624 | 157821 | 2001-03-22 | 2002-03-22 |
| 254466 | 156286 | 2001-08-04 | 9999-01-01 |
|  47978 | 155709 | 2002-07-14 | 9999-01-01 |
| 253939 | 155513 | 2002-04-11 | 9999-01-01 |
| 109334 | 155377 | 2000-02-12 | 2001-02-11 |
| 109334 | 155190 | 2002-02-11 | 9999-01-01 |
| 109334 | 154888 | 2001-02-11 | 2002-02-11 |
| 109334 | 154885 | 1999-02-12 | 2000-02-12 |
|  80823 | 154459 | 2002-02-22 | 9999-01-01 |



mysql> (select * from salaries order by salary desc limit 10) union (select * from salaries order by salary desc limit 10);
+--------+--------+------------+------------+
| emp_no | salary | from_date  | to_date    |
+--------+--------+------------+------------+
|  43624 | 158220 | 2002-03-22 | 9999-01-01 |
|  43624 | 157821 | 2001-03-22 | 2002-03-22 |
| 254466 | 156286 | 2001-08-04 | 9999-01-01 |
|  47978 | 155709 | 2002-07-14 | 9999-01-01 |
| 253939 | 155513 | 2002-04-11 | 9999-01-01 |
| 109334 | 155377 | 2000-02-12 | 2001-02-11 |
| 109334 | 155190 | 2002-02-11 | 9999-01-01 |
| 109334 | 154888 | 2001-02-11 | 2002-02-11 |
| 109334 | 154885 | 1999-02-12 | 2000-02-12 |
|  80823 | 154459 | 2002-02-22 | 9999-01-01 |
+--------+--------+------------+------------+

约束

主键约束

主键约束可以使单列也可以是多列,在建表的时候一般都会指定主键约束,如果不指定,mysql内部会额外生成一个全局唯一的列作为主键,会额外占用一部分存储空间。

主键约束也具有唯一性。

create table user (
   id int primary key,
   name varchar(200)
) engine=InnoDB default charset=utf8;

create table user(
   id int,
   name varchar(200),
   primary key(id, name)
)engine=InnoDB default charset=utf8;

外键约束

外键的作用是在两个表结构之间建立强关联,但由于外键会严格限制数据的一致性,给编码工作带来一定程度上的不必要的麻烦,所以一般很少使用。

create table user_dept(
    id int primary key,
    user_id int,
    dept_id int,
    foreign key(user_id) references user(id)
);

唯一约束

create table user(
   id int,
   #名字要唯一
   name varchar(200) unique,
   primary key(id, name)
)engine=InnoDB default charset=utf8;

非空约束

create table user(
   id int,
   #名字不能为空
   name varchar(200) not null,
   primary key(id, name)
)engine=InnoDB default charset=utf8;

索引

索引的原理是将索引列利用B+树的结构单独存储,一次来提高基于索引的查询效率。

create [unique] index idx_name on tname (columen[(length)] [ASC|DESC]) 

alter table tname add index idx_name (columen[(length)] [ASC|DESC]);

alter table tname add primary key(column,[column]);

alter table tname drop primary key;

drop index idx_name on tname ;

alter table tname drop index idx_name;

show indexes from tname;

主键索引

在主键列上默认就会创建主键索引。

#删除主键
alter table tname drop primary key;

普通索引

mysql> create index idx_name on employees(last_name);
Query OK, 0 rows affected (0.50 sec)
Records: 0  Duplicates: 0  Warnings: 0



mysql> create index idx_name2 on employees(last_name(5) DESC);
Query OK, 0 rows affected (0.45 sec)
Records: 0  Duplicates: 0  Warnings: 0

唯一索引

create unique index idx_name2 on employees(last_name(5) DESC);

全文索引

事务

特性

ACID → 原子性、一致性、隔离性、持久性

操作

#开启事务
start transaction
#创建保存点
savepoint x
#回滚
rollback
#提交
commit

隔离级别

  • RU(Read Uncommited) 读未提交  → 脏读
  • RC(Read Commited)读已提交   → 不可重复度
  • RR(Repeated Read)可重复读 → 幻读
  • Serializable 串行化 → 无问题,但慢

用户管理

#查询当前存在的用户
mysql> select host,user,authentication_string from mysql.user;
+-----------+------------------+------------------------------------------------------------------------+
| host      | user             | authentication_string                                                  |
+-----------+------------------+------------------------------------------------------------------------+
| localhost | mysql.infoschema | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |
| localhost | mysql.session    | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |
| localhost | mysql.sys        | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |
| localhost | root             | $A$005$0prYc~b!Qd
                                                  c<nYFz4uhWuc9.lMn8Ma4IL7i81SIbC/4tpWmE/Tp3V7qc54 |
+-----------+------------------+------------------------------------------------------------------------+


#添加一个用户,指定密码
mysql> create user 'echo'@'localhost' identified by 'gz2021..';

#删除用户
mysql> drop user 'echo'@'localhost';
#修改当前登录用户的密码
set password = password('密码')

#修改指定用户的密码
set password for 'echo'@'localhost' = password('密码');

存储原理

在mysql中的每一个数据库对应一个文件夹,当使用InnoDB存储引擎时会存在两种文件,.frm和.ibd,.frm文件存储表结构,.ibd文件存储表数据;在MyIsam存储引擎中存在三种文件,.frm、.MYD和.MYI,.frm文件存储表结构,.MYD文件存储表数据,.MYI存储索引。

mysql存储数据的最小单位是页,默认大小是16kb,一般与磁盘页和内存也大小一致,或是整数倍的关系,每个页中记录了数据项,就是表中的一行记录,一次读取和写入都是以页未单位来进行的。

为了减少磁盘IO次数,加快检索效率,mysql采用B+树的方式组织数据页,每一个B+树的节点都对应一个磁盘页,所以在B+树上没检索一次节点就对应一次磁盘IO,B+树能够做到在4层的情况下存储亿级的数据,所以很大程度上降低了磁盘IO带来的开销。

mysql以主键为基础来组织数据,如果建表时没有指定主键列,会默认生成一个rowid作为主键。

 在InnoDB存储引擎中,通过聚簇索引来存储数据,也就是以表的主键为基础构建一个B+树,并且在叶子节点中存储行数据,而对于普通索引来说,叶子节点不存储行数据,而是存储当前行对应的主键值,所以通过普通索引查询数据时会多一步通过主键到聚簇索引中查询当前行数据的过程,即回表。

联合索引也是经常使用的一种索引,在联合索引中会存储多个索引列,这时在构建所索引树的时候就会按照指定的索引列的值的先后顺序进行排序来组织索引页。 

普通索引和联合索引中由于存在一个或多个索引列,所以在查询数据的时候,如果只查询索列的数据就能够避免到主键索引中回表的过程,这也称为索引覆盖,能够有效加快查询速度,所以在创建索引的时候要尽量考虑到具体的查询业务。

MyISAM这种存储引擎对数据的组织方式与InnoDB有所不同,由于MyIsam不需要支持事务等复杂功能,它的主键索引树中并没有存储具体的行数据,而是将行数据和索引树分开存储,然后只在页节点上存储行数据的物理地址。

 主键索引页成为聚簇索引,普通索引/联合索引又称为二级索引或非聚簇索引。

索引虽然能够加快查询速度,但是凡事都有代价,首先是空间上的代价,索引本身也是一个B+树,会占用额外的空间,另外是时间上的代价,比如新增或修改数据时,需要同时调整索引树,以及在执行查询mysql制定执行计划时,需要对每一个索引进行性能分析等。

索引最佳实践

  1. 只为用于搜索、排序或分组的列创建索引,不创建多余的索引;
  2. 考虑索引列中不重复值的个数,取值过于稀疏的列不适合创建索引;
  3. 索引列的类型尽量小,可以减少对磁盘空间的占用,加快IO速度;
  4. 如果某个字符串列取值过长,并且前缀的区分度比较大,应该尽量缩短索引值的长度 index idx_name(column(length));
  5. 如果可以,尽量使用索引覆盖,从而避免回表;
  6. 不要在索引列上进行计算和函数调用,会导致索引失效;
  7. 不要创建冗余、重复的索引;
  8. 遵循最左前缀匹配规则;
  9. 对于字符串,要加上单引号,否则会触发类型转换,造成索引失效;
  10. 索引条件中,范围条件右面的索引会失效;
  11. 尽量不使用不等值判断,!,=,<>;
  12. 不使用is null,is not null;
  13. like查询时,如果以通配符开头会造成索引失效;
  14. 少用or 和 in,有可能会造成索引失效;

查询计划

mysql> mysql> explain select * from film where id = 2;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | film  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+

id

id列的编号是select的序列号,有几个select就有几个id,并且id的顺序是按照select出现的顺序增长的,id列的值越大优先级越高,id相同则从上往下执行,id为null的最后执行。

select_type

  • simple:简单查询 select * from film where id = 2;
  • primary:复杂查询中的最外层查询;
  • subquery:包含在select中的子查询(不是在from子句中);
  • derived:包含在from子句中的子查询,用于临时存放数据的派生表;
  • union:在union中的第二个和随后的select;

type

  1. system/const:表示mysql能够对查询的某部分进行优化并将其转换为一个常量,用主键索引或唯一索引与常熟进行比较时,最多有一个匹配行;
  2. eq_ref:primary key 或 unique key索引的所有部分被连接使用,最多只会返回一条符合条件的记录。这是在const之外最好的链接类型;
  3. ref:比eq_ref要差,不使用唯一索引,而是使用普通索引或唯一索引的部分前缀,会有多个结果被匹配;
  4. range:范围扫描通常出现在in(),between,>,=等操作中;
  5. index:当可以使用索引覆盖,但需要扫描全表索引时,比ALL快一些;
  6. all:全表扫描,性能最差;

key

这一列显示mysql实际采用的索引。

key_len

这以列表示使用到的索引的长度,对于联合索引或模糊查询,有可能只使用到索引的一部分。

row

表示mysql估计的查询结果的行树。

filtered

通过过滤条件过滤出来的数据量占总数据量的百分比。

Extra

  • no table used
  • impossible where
  • no matching min/max
  • using index
  • using index condition
  • using where
  • usiing join buffer
  • zero limit
  • using filesort
  • using temporary
mysql> mysql> explain select * from film where id = 2;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | film  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+

mysql> explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table      | partitions | type   | possible_keys | key     | key_len | ref   | rows | filtered | Extra       |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------------+
|  1 | PRIMARY     | <derived3> | NULL       | system | NULL          | NULL    | NULL    | NULL  |    1 |   100.00 | NULL        |
|  3 | DERIVED     | film       | NULL       | const  | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL        |
|  2 | SUBQUERY    | actor      | NULL       | const  | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | Using index |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------------+

mysql> explain select 1 union all select 1;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
|  1 | PRIMARY     | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | No tables used |
|  2 | UNION       | NULL  | NULL       | NULL | NULL          | NULL | NULL    | NULL | NULL |     NULL | No tables used |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+

//system and constant
mysql> explain select * from (select * from film where id = 1) temp;
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table      | partitions | type   | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+
|  1 | PRIMARY     | <derived2> | NULL       | system | NULL          | NULL    | NULL    | NULL  |    1 |   100.00 | NULL  |
|  2 | DERIVED     | film       | NULL       | const  | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------+------+----------+-------+

//eq_ref
mysql> explain select * from film_actor left join film on film_actor.film_id = film.id;
+----+-------------+------------+------------+--------+---------------+---------+---------+-------------------------+------+----------+-------+
| id | select_type | table      | partitions | type   | possible_keys | key     | key_len | ref                     | rows | filtered | Extra |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------------------------+------+----------+-------+
|  1 | SIMPLE      | film_actor | NULL       | ALL    | NULL          | NULL    | NULL    | NULL                    |    3 |   100.00 | NULL  |
|  1 | SIMPLE      | film       | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | test.film_actor.film_id |    1 |   100.00 | NULL  |
+----+-------------+------------+------------+--------+---------------+---------+---------+-------------------------+------+----------+-------+

//ref
mysql> explain select * from film where name = 'film1';
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key      | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | film  | NULL       | ref  | idx_name      | idx_name | 43      | const |    1 |   100.00 | Using index |
+----+-------------+-------+------------+------+---------------+----------+---------+-------+------+----------+-------------+

//rang
mysql> explain select * from actor where id > 1;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | actor | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    2 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+

//index
mysql> explain select * from film ;
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | film  | NULL       | index | NULL          | idx_name | 43      | NULL |    3 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+----------+---------+------+------+----------+-------------+

//ALL
mysql> explain select * from actor;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | actor | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+

连接原理

驱动表与被驱动表。

嵌套循环连接 → 使用索引加快连接速度,即在被驱动表的连接条件字段上和过滤条件字段上增加索引 → 基于块的嵌套循环连接(在内存中用JoinBuffer缓存一部分被驱动表的数据,加快连接过程);

Buffer Pool

Buffer Pool就是一个换从页的缓存,用来弥补cpu和磁盘IO之间的速度差。Buffer Pool分为控制块和缓存页,一个控制块对应一个缓存页以方便对缓存页进行管理。

未被分配的缓存页对应的控制块会被组织成一个free链表,当有页数据需要缓存时,就从free链表中分配一个控制块及其对应的缓存页,并将其从free链表中移除。

对于被修改的页,mysql并不会立即把修改刷新到磁盘上,而是通过一个flus链表来记录这些缓冲页,然后通过一个后台线程进行批量刷新。

 mysql利用哈希表来存放已经在Buffer Pool中的缓冲页,key=表空间号 + 页号,value=控制块地址。

Buffer Pool的作用是缓存页,但它的大小并不是无限大的,这就需要一种按照访问频率进行淘汰的也换成淘汰策略,总体上遵循LRU算法,但实际情况并没有那么简单,因为有以下两种情况的存在:

  1. mysql的预加载机制;
  2. 全表扫描;

所以如果简单的把最新被访问的数据放到LRU链表的表头,就会造成那些真正会被频繁访问的页被挤出Buffer Pool,为了解决这个问题,msyql将整个Buffer Pool分成了两段,一个Old、一段是Young,默认情况下Old占总的Buffer Pool大小的35%,当某个页被加载时默认先放到Old这部分区域中,如果再次被访问就会从Old区域移动到Young区域,但由于一个Page中会包含多条数据,为了解决访问同一个页下的数据时误以为Page也被频繁访问的情况(比如全表扫描时的按顺序扫描),mysql规定如果一个Page也在1秒内被连续访问,那么就不将其移动到Young区域中。此外,对于Young区域来说,由于这部分都是热点数据,如果按照LRU算法每次都把被访问的Page放入到表头,那么会严重损耗性能,所以mysql规定,只有在Young区域的后1/4部分的Page被访问时才将其移动到表头,这样就避免了对Page缓存页的频繁移动。 

Buffer Pool是一块连续的内存空间,并且当操作其内部的各种链表时都需要加锁处理,如果Buffer Pool很大并且系统并发度很高时,就会严重影响系统性能,所以mysql内部会初始化多个Buffer Pool的实例,每个实例之间互相不影响,可以大大提高并发处理能力。Buffer Pool的大小可以通过系统参数进行修改。 

Redo Log

Redo日志的作用是在不实时刷新Buffer Pool中的脏页到磁盘上的情况下保证数据的持久性,即不丢数据,Redo日志中包含表空间号、页号、页内偏移量和被修改的数据,在通过Redo Log恢复数据的时候就能够通过这几项定位到被修改的数据了。

Buffer Pool是为了缓存页,提高数据处理速度,同样的,Redo日志也提供了一个log buffer来缓存redo日志的数据,虽然redo日志是顺序写入,但写入速度与内存相比还是会差一些。

Redo日志数据写入log buffer后,后台会有独立的线程去将log buffer中的数据写入磁盘,同时mysql还规定了写强制刷新log buffer的检查点: 

  1. log buffer内存不足时;
  2. 事务提交时;
  3. 将某个脏页刷新到磁盘前,会保证先将脏页对应的redo日志刷新到磁盘;
  4. 后台有一个线程,大约以每秒钟一次的频率将log buffer中的redo日志刷新到磁盘;
  5. 正常关闭服务时;
  6. 做checkpoint时;

Undo Log

undo log是实现事务回滚的基础,再对表进行DML操作室,每一个事务都会在undo log中生成若干条undo 日志,在undo 日志中记录了事务id和回滚指针,以及具体的能够进行回滚操作的数据(每种DML操作的undo 日志中的数据的格式都不相同),当对事务进行回滚时,就可以以tx_id为标识,从undo 日志中读取需要回滚的数据然后执行回滚操作。

另外undo log也是实现MVCC的基础。

MVCC

undo log通过roll_pointer构造了一个版本链。

对于Read Uncommited这种隔离级别,直接读取最新的数据就可以,对于Serializable这种隔离级别,是采用锁的方式实现,所以也用不到MVCC机制。而对于Read Commited和Repeatable Read这两种隔离级别就需要借助MVCC机制来实现。

MVCC机制的核心是Read View,在Read View中包含以下信息:

  1. m_ids:在生成Read View时,当前系统中活跃的事务的事务id列表;
  2. min_trx_id:在生成Read View时,当前系统中活跃的事务中最小的事务id;
  3. max_trx_id:在生成Read View时,系统应该分配给下一个事务的事务id;
  4. creator_trx_id:生成Read View的事务的事务id;

然后遵循以下逻辑来确定数据的可见性:

  1. 如果被访问版本的trx_id属性值与Read View中的creator_trx_id值相同,说明当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问;
  2. 如果被访问版本的trx_id属性小于Read View中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问;
  3. 如果被访问版本的trx_id属性值大于或等于Read View中的max_trx_id值,表明生成该版本的事务在当前事务生成Read View后才开启,所以该版本不可以被当前事务访问;
  4. 如果被访问版本的trx_id属性值在Read View的min_trx_id和max_trx_id之间,则需要判断trx_id属性值是否在m_ids列表中,如果在,说明创建Read View时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建Read View时该版本的事务已经提交了,该版本可以被访问;

主从同步原理

  1. 客户端通过主节点写入数据,写入成功后mysql会保证将变化写入bin log(两阶段提交);
  2. 主节点上的binlog dump线程把bin log的变化通过网络发送给从节点;
  3. 从节点的IO线程接收到从节点的数据并将其写入的relay log(中继日志);
  4. 从节点的SQL线程将relay log中的数据进行回放,最终让主动节点的数据保持一致;

主节点默认采用异步复制的方式同步数据,即主节点不会管从节点是否成功接收了binlog,这样当主节点宕机,从节点升级成主节点后,会出现数据丢失的情况,所以一般会采用确认同步的方式,主节点确认同步bin log的方式有两种:

  1. 全同步复制:等所有的从节点都同步完成后再确认写数据成功;
  2. 半同步复制:等到某一个从节点确认同步成功后再确认写数据成功;

分库分表

进行分库分表的原因无非有两个,一个是数据库访问压力过大,一个是表中的数据量过大,对业务产生了影响,对于访问压力过大的问题,可以先通过添加索引、进行读写分离来解决,毕竟这些方案要比分库分表要简单的多,如果实在没办法解决问题,再考虑分库分表。

首先要考虑的方案是分表,分表一般有两种方案,一种是基于范围的分表,比如按照日期,但是这种方案的一个弊端是容易造成数据分布不均匀。另外一种方案是基于hash来分,通常就是hash + mod。

但是要分多少张表合适呢?首先要确保在短期内甚至在几年之内不会再出现需要再拆分的情况,另外,还要考虑后续扩容的便利性,参考hashmap,可以将分表数量定为2的整数次幂,这样可以尽可能的减小扩容时需要迁移的数据量。

另外,还需要考虑分布式id、分布式事务、跨表count、group by、join等操作,分表的另一个挑战来自于数据迁移,如何做到在数据迁移的过程中不让业务停摆或把影响控制在业务可接受的范围内是一个很棘手的问题,一般如果实实在在的进行数据迁移,影响到业务是在所难免的,所以更好的办法是,新数据走分表,老数据走原表,这样只需要增加一层路由就可以了,等到后续对老表的操作越来越少时,再逐渐的把老数据迁移过去。

在分表的方案下,实际上所有的流量还是在同一台数据库实例上,而且有可能还和其他的业务共享同一个实例, 这样一方面数据库的压力没有减小,另一方面如果其他业务的流量增大时,会影响分表业务,所以当分表不能满足业务需求时,就需要考虑分库,为了不影响业务,也可以采用新旧数据分离的方式来做。

参考

【韩顺平讲MySQL】零基础一周学会MySQL -sql mysql教程 mysql视频 mysql入门_哔哩哔哩_bilibili

《MySQL从入门到精通》

《MySQL是怎么运行的》 

https://github.com/datacharmer/test_db

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

echo20222022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值