Skip to content

再学MySQL

推荐图书《MySQL是怎样运行的》 语言较轻松,不失专业性,比较容易读进去

MySQL 连接管理

当有客户端连接到服务器时,服务器进程会创建一个线程来处理与这个客户端的交互。而当客户端断开连接时,服务器不会立即销毁处理客户端交互的线程,而是会将它缓存起来,在新的客户端与服务器建立连接时,再讲这个线程分配给新的客户端。

MySQL字符编码

character_set_client:客户端请求数据的字符集

character_set_connection:客户机/服务器连接的字符集

character_set_results:结果集,返回给客户端的字符集

character_set_server:数据库服务器的默认字符集

本地客户端常用编码格式:

IE6使用utf8,命令行是gbk,一般程序则是gb2312

要保证connection的字符集大于client字符集才能保证转换不丢失信息。

常见字符集 latin1 < gb2312 < gbk < utf8

InnoDB 记录存储结构

InnoDB 以页作为磁盘与内存之间交互的基本单位,InnoDB 中页的大小一般为16KB。

InnoDB 引擎中页的大小由系统变量innodb_page_size表明,该变量只能在第一次初始化MySQL数据目录时指定,服务器运行过程中不能被更改。

InnoDB 表的主键生成策略:

  1. 优先使用用户自定义的主键作为主键。
  2. 若用户没有定义主键,则选取一个不允许存储NULL值的UNIQUE键作为主键。
  3. 如果表中连不允许存储NULL值的UNIQUE都没有定义,则InnoDB会为表添加一个名为row_id的隐藏列作为主键。

COMPACT

image-20221112220149808

变长字段长度列表由各个变长字段的真实数据所占的字节数按照列的顺序逆序存放组成。

REDUNDANT

没有NULL值列表,将列对应的偏移量值的第一个比特位作为是否为NULL的依据(NULL比特位)

多了n_field(列的数量)和1byte_offs_flag(标记字段长度偏移列表中每个列对应的偏移量是使用1字节表示还是2字节表示)两个属性

没有record_type(表示当前记录类型,普通记录、目录表项记录等)属性

CHAR(M)类型的列在COMPACT行格式中,使用不同的字符集(定长编码字符集和变长编码的字符集),其存储方案也不同,而在REDUNDANT行格式中,该列真实数据所占空间的大小就是所用字符集表示一个字符最多需要的字节数与M的乘积。

在COMPACT和REDUNDANT行格式中,对于占用存储空间很多的列(溢出列,即off-page列),在记录真实数据的位置只会存储该列的一部分数据(前 768字节),再将剩余的数据分散存储在几个其他的页(溢出页)中,并在记录真实数据的位置上用20 字节存储指向这些页的地址(以及分散在各个页中数据所占的字节数)

DYNAMIC

大体与COMPACT相同,区别是溢出列真实数据存放处值记录溢出页位置与溢出页数据字节数,将所有真实数据全部存放至溢出页中。

COMPRESSED

与DYNAMIC大致相同,不同的一点是COMPRESSED会使用压缩算法对页面进行压缩。

InnoDB 数据页结构

MySQL 中规定一个页中至少要存放两行记录

image-20221113222720341

Infimum记录是一个页面中最小的记录;Supremum记录是一个页面中最大的记录。(由5字节大小的记录头信息和8字节大小的一个固定单词组成,人为规定)

image-20221113222745297

InfimumSupremum记录的heap_no值分别为0和1。

堆中记录的heap_no值一旦分配便不可改变,即使记录被删除,该删除记录的heap_no值也依旧保持不变

next_record值记录从当前记录的真实数据到下一条记录的真实数据的距离(下一条数据指按照主键值从小到大排序的下一条记录)

image-20221113222759236+

next_record指向真实数据开始的位置,该位置向左是记录头信息,向右是真实数据,这便与之前行结构部分变长字段列表、NULL值列表逆序存放相对应。

删除一条记录时

  1. 该条记录的deleted_flag值设置为1
  2. 该条记录的上一条数据的next_record值修改为该条记录的next_record
  3. 该条记录的next_record值为0
  4. Supremun记录的n_owned值减小1

页目录

  1. 将所有记录(包括SupremumInfimum)划分为几个组。
  2. 每个组最后一条记录的n_owned属性表示该组内有几条记录。
  3. 将每组最后一条数据在页面中的地址偏移量单独提取出来按顺序放在靠近页尾部的地方(Page Dictionary页目录 )。页目录中的地址偏移量称为Slot)每个槽占2字节.页目录就是由多个槽组成。

每个分组的记录条数有以下规定:

  1. 对于Infimum记录所在的分组只能有一条记录
  2. Supremum记录所在的分组拥有的记录条数只能在1-8之间
  3. 其他分组中记录的条数范围在4-8之间

给记录进行分组的步骤:

  1. 初始情况页面中只有InfimumSupremum两条记录,它们分属于两个分组。页目录中只有两个记录Infimum和Supremum记录偏移量的槽
  2. 每插入一条记录,都会从页目录中找到对应记录主键值比待插入记录的主键值大并且差值最小的槽,并将该槽的n_owned值加1
  3. 当一个组中的记录数等于8时,再插入一条记录便会将这两个组拆分为分别包含四条记录和五条记录的两个组。

页的效验和

File Header中的FIL_PAGE_LSN,表示页面被最后修改时对应的日志序列号的值

在将页从内存刷新到磁盘时,为保证页的完整性,页首和页尾都会存储页中数据的校验和,以及页面最后修改时对应的LSN值(页尾只会存储LSN值的后 4字节)。若页首和页尾的校验和以及LSN值校验不成功,就说明刷新期间出现了问题。

索引

  • 主键索引(PRIMARY KEY)
    • 唯一标识,主键不可重复,只能有一个主键
  • 唯一索引(UNIQUE KEY)
    • 索引列
  • 常规索引(KEY/INDEX)
  • 全文索引(FullText)
    • 可以快速定位数据

image-20230113215324604

sql
ALTER TABLE `account`
  ADD FULLTEXT INDEX `name`;

image-20230114100029461

不使用全文索引

sql
 EXPLAIN
SELECT *
FROM account
WHERE name = 'B';

image-2023014102202433

使用全文索引

sql
 EXPLAIN
SELECT *
FROM account
WHERE MATCH(name) AGAINST('B');

image-20230114105225466

编写sql函数插入百万数据

sql
DELIMITER $$ -- 写函数之前必须要写的标志
CREATE FUNCTION mock_data()
  RETURNS INT
BEGIN
  DECLARE num INT DEFAULT 1000000;
  DECLARE i INT DEFAULT 0;
  WHILE i < num
    DO
      INSERT INTO account(`name`, `money`)
      VALUES (CONCAT('用户', i), i);
      SET i = i + 1;
    END WHILE;
  RETURN i;
END;

运行函数

SQL
SELECT mock_data();

image-20230114111453055

插入百万数据后测试

image-20230114114740833

创建索引

sql
 CREATE INDEX name_idx ON account (`name`);

再查

imaage-20230114114937303

索引原则

  • 索引不是越多越好
  • 不要对经常变动的数据加索引
  • 小数据量表不需要加索引
  • 一般加在常用来查询到字段上
  • 回表:通过携带主键信息到聚簇索引中重新定位完整的用户记录的过程。
  • 以非主键列的大小为排序规则而建立的B+树需要执行回表操作才可以定位到完整的用户记录,这种B+树也称为二级索引或辅助索引。
  • 以两个或两个以上个列的大小为排序规则建立的B+树称为复合索引,本质也是二级索引。
  • 一个B+树索引的根节点自创建之日起便不会再移动(页号不会改变)
  • 要保证B+树同一层内节点的目录项记录除页号这个字段以外是唯一的。
  • 二级索引的内节点的目录项记录的内容分为三部分(索引列的值、主键值、页号)。
  • 先按照二级索引列的值进行排序,在二级索引列值相同的情况下,再按照主键值进行排序。故为 c 列建立的索引相当于为(c,主键)列建立了一个联合索引。
  • 唯一二级索引:某个列被声明为UNIQUE属性时,便会建立一个唯一二级索引。

MyISAM 和 InnoDB 索引的区别

  1. InnoDB 中 索引和数据的存储格式相同,有record_type标识来区分用户记录和目录记录,而 MyISAM 中索引与数据存储格式不同,索引是索引,数据是数据。
  2. MyISAM 中 所有索引都是二级索引,它的主键索引叶子节点存的是主键值与行号(定长记录),而变长记录存储的是主键值与地址偏移量。

事务

ACID原则:原子性、一致性、隔离性、持久性

原子性(Atomicity):要么都成功,要么都失败。

一致性(Consistency):事务前后的数据完整性要保持一致。

隔离性(Isolation):每个事务之间相互隔离。

持久性(Durability): 事务一旦被提交则不可逆,会被持久化到数据库中。

隔离所导致的问题:

可重复读

sql
begin;
select *
from dept;

image-20221019200515493

另一个窗口

sql
begin;
insert into dept(name)
values ("产品部");
commit;

image-20221019200707335

再查

image-20221019200646578

commit之后再差

image-20221019200741255

幻读

image-20221019200932357

image-20221019200925161

一个事务内读取到了其他事务插入的数据,导致前后数据不一致。

脏读

指一个事务读取到了另一个事务未提交的数据

不可重复读

一个事务内读取某一行数据,多次读取数据不同(不一定错误,以场景而定)

sql
-- mysql 默认开启事务自动提交
SET autocommit = 0 -- 关闭
SET autocommit = 1
-- 开启(默认)

-- 手动处理事务
SET autocommit = 0
-- 关闭自动提交
-- 事务开启
START TRANSACTION -- 标记事务开始

INSERT ...

-- 提交事务
  COMMIT
-- 回滚事务
ROLLBACK
-- 事务结束

SET autocommit = 1
-- 开启自动提交


-- 扩展
SAVEPOINT SAVE_POINT_NAME -- 设置一个事务保存点
ROLLBACK TO SAVEPOINT SAVE_POINT_NAME -- 回滚到保存点
RELEASE SAVEPOINT SAVE_POINT_NAME -- 撤销保存点

转账案例

sql
-- 转账案例
CREATE DATABASE shop CHARACTER
  SET utf8 COLLATE utf8_general_ci;
USE shop;
CREATE TABLE `account`
(
  `id`    INT(3)        NOT NULL AUTO_INCREMENT,
  `name`  VARCHAR(30)   NOT NULL,
  `money` DECIMAL(9, 2) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE = INNODB
  DEFAULT CHARSET = utf8;
INSERT INTO account (`name`, `money`)
VALUES ('A', 2000.00),
       ('B',
        5000.00);
-- 模拟转账事务

SET autocommit = 0;-- 关闭自动提交

START TRANSACTION;-- 开启一个事务
UPDATE account
SET money = money - 500
WHERE NAME = 'A';
UPDATE account
SET money = money + 500
WHERE NAME = 'B';

SELECT *
FROM account;
ROLLBACK;
COMMIT;

报错记录

在MySQL中创建函数时出现这种错误

在mysql数据库中执行以下语句 (临时生效,重启后失效)

sql
set global log_bin_trust_function_creators = TRUE;

在配置文件my.cnf[mysqld]配置log_bin_trust_function_creators=1

image-20220924120544149

恶补SQL语句

SQL中的条件语句

使用CASE写条件语句

sql
CASE
	WHEN ... THEN ...
	WHEN ... THEN ...
	ELSE ...
END

或者类似三元运算符的IF函数

sql
IF
  (a,b,c)
# 相当于 a?b:c

LeetCode 1873. 计算特殊奖金

表: Employees

+-------------+---------+
| 列名        | 类型     |
+-------------+---------+
| employee_id | int     |
| name        | varchar |
| salary      | int     |
+-------------+---------+
employee_id 是这个表的主键。
此表的每一行给出了雇员id ,名字和薪水。

写出一个SQL 查询语句,计算每个雇员的奖金。如果一个雇员的id是奇数并且他的名字不是以'M'开头,那么他的奖金是他工资的100%,否则奖金为0。

Return the result table ordered by employee_id.

返回的结果集请按照employee_id排序。

例:

输入:
Employees 表:
+-------------+---------+--------+
| employee_id | name    | salary |
+-------------+---------+--------+
| 2           | Meir    | 3000   |
| 3           | Michael | 3800   |
| 7           | Addilyn | 7400   |
| 8           | Juan    | 6100   |
| 9           | Kannon  | 7700   |
+-------------+---------+--------+
输出:
+-------------+-------+
| employee_id | bonus |
+-------------+-------+
| 2           | 0     |
| 3           | 0     |
| 7           | 7400  |
| 8           | 0     |
| 9           | 7700  |
+-------------+-------+
解释:
因为雇员id是偶数,所以雇员id 是2和8的两个雇员得到的奖金是0。
雇员id为3的因为他的名字以'M'开头,所以,奖金是0。
其他的雇员得到了百分之百的奖金。

AC代码:

sql
SELECT employee_id,
       (
         CASE
           WHEN name LIKE 'M%' OR MOD(employee_id, 2) = 0 THEN 0
           ELSE salary
           END
         ) AS bonus
FROM Employees
ORDER BY employee_id;

LeetCode 627. 变更性别

Salary 表:

+-------------+----------+
| Column Name | Type     |
+-------------+----------+
| id          | int      |
| name        | varchar  |
| sex         | ENUM     |
| salary      | int      |
+-------------+----------+
id 是这个表的主键。
sex 这一列的值是 ENUM 类型,只能从 ('m', 'f') 中取。
本表包含公司雇员的信息。

请你编写一个 SQL 查询来交换所有的 'f''m' (即,将所有 'f' 变为 'm' ,反之亦然),仅使用 单个 update 语句 ,且不产生中间临时表。

注意,你必须仅使用一条 update 语句,且 不能 使用 select 语句。

查询结果如下例所示。

例:

输入:
Salary 表:
+----+------+-----+--------+
| id | name | sex | salary |
+----+------+-----+--------+
| 1  | A    | m   | 2500   |
| 2  | B    | f   | 1500   |
| 3  | C    | m   | 5500   |
| 4  | D    | f   | 500    |
+----+------+-----+--------+
输出:
+----+------+-----+--------+
| id | name | sex | salary |
+----+------+-----+--------+
| 1  | A    | f   | 2500   |
| 2  | B    | m   | 1500   |
| 3  | C    | f   | 5500   |
| 4  | D    | m   | 500    |
+----+------+-----+--------+
解释:
(1, A) 和 (3, C) 从 'm' 变为 'f' 。
(2, B) 和 (4, D) 从 'f' 变为 'm' 。

AC代码:

sql
UPDATE Salary
SET sex = IF(sex = 'm', 'f', 'm');

SQL中的字符串函数

NameDescription
UPPER(s)将字符串s全部大写
CONCAT(s1,s2...)连接多个字符串
LOWER(s)将字符串s全部小写
SUBSTRING(s,n,len)截取字符串s从n位开始截取长度为len的字符串(从1开始!!!
CHAR_LENGTH(s)返回s中的字符数
TRIM(s)删除前导和尾随空格
MID(s,n)返回s从指定位置n开始的子字符串
LENGTH()返回字符串s的长度(以字节为单位)
BIT_LENGTH()返回s的长度(以位为单位)

LeetCode 1667.修复表中的名字

SQL Schema


表: Users

+----------------+---------+
| Column Name    | Type    |
+----------------+---------+
| user_id        | int     |
| name           | varchar |
+----------------+---------+
user_id 是该表的主键。
该表包含用户的 ID 和名字。名字仅由小写和大写字符组成。

编写一个 SQL 查询来修复名字,使得只有第一个字符是大写的,其余都是小写的。

返回按 user_id 排序的结果表。

查询结果格式示例如下。

例:

输入:
Users table:
+---------+-------+
| user_id | name  |
+---------+-------+
| 1       | aLice |
| 2       | bOB   |
+---------+-------+
输出:
+---------+-------+
| user_id | name  |
+---------+-------+
| 1       | Alice |
| 2       | Bob   |
+---------+-------+

AC代码:

sql
SELECT user_id,
       CONCAT(
         SUBSTRING(UPPER(name), 1, 1),
         MID(LOWER(name), 2)
       ) as name
FROM Users
ORDER BY user_id;

SQL中的GROUP_CONCAT函数

将组中字符串连接

使用示例

sql
GROUP_CONCAT
  (DISTINCT product ORDER BY sell_date SEPARATOR ',')

LeetCode 1484. 按日期分组销售产品

Activities

+-------------+---------+
| 列名         | 类型    |
+-------------+---------+
| sell_date   | date    |
| product     | varchar |
+-------------+---------+
此表没有主键,它可能包含重复项。
此表的每一行都包含产品名称和在市场上销售的日期。

编写一个 SQL 查询来查找每个日期、销售的不同产品的数量及其名称。 每个日期的销售产品名称应按词典序排列。 返回按 sell_date 排序的结果表。 查询结果格式如下例所示。

例 :

输入:
Activities 表:
+------------+-------------+
| sell_date  | product     |
+------------+-------------+
| 2020-05-30 | Headphone   |
| 2020-06-01 | Pencil      |
| 2020-06-02 | Mask        |
| 2020-05-30 | Basketball  |
| 2020-06-01 | Bible       |
| 2020-06-02 | Mask        |
| 2020-05-30 | T-Shirt     |
+------------+-------------+
输出:
+------------+----------+------------------------------+
| sell_date  | num_sold | products                     |
+------------+----------+------------------------------+
| 2020-05-30 | 3        | Basketball,Headphone,T-shirt |
| 2020-06-01 | 2        | Bible,Pencil                 |
| 2020-06-02 | 1        | Mask                         |
+------------+----------+------------------------------+
解释:
对于2020-05-30,出售的物品是 (Headphone, Basketball, T-shirt),按词典序排列,并用逗号 ',' 分隔。
对于2020-06-01,出售的物品是 (Pencil, Bible),按词典序排列,并用逗号分隔。
对于2020-06-02,出售的物品是 (Mask),只需返回该物品名。

AC代码:

sql
SELECT sell_date,
       COUNT(DISTINCT product)        as num_sold,
       GROUP_CONCAT(DISTINCT product) as products
FROM Activities
GROUP BY sell_date
ORDER BY sell_date;

SQL 中的IFNULL函数

LeetCode 176. 第二高的薪水

Employee 表:

+-------------+------+
| Column Name | Type |
+-------------+------+
| id          | int  |
| salary      | int  |
+-------------+------+
id 是这个表的主键。
表的每一行包含员工的工资信息。

编写一个 SQL 查询,获取并返回 Employee 表中第二高的薪水 。如果不存在第二高的薪水,查询应该返回 null

查询结果如下例所示。

例 1:

输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1  | 100    |
| 2  | 200    |
| 3  | 300    |
+----+--------+
输出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| 200                 |
+---------------------+

例 2:

输入:
Employee 表:
+----+--------+
| id | salary |
+----+--------+
| 1  | 100    |
+----+--------+
输出:
+---------------------+
| SecondHighestSalary |
+---------------------+
| null                |
+---------------------+

AC代码:

sql
SELECT (SELECT DISTINCT salary
        FROM Employee
        ORDER BY salary DESC
        LIMIT 1 OFFSET 1) AS SecondHighestSalary

使用IFNULL优化代码:

sql
SELECT IFNULL(
         (SELECT DISTINCT salary
          FROM Employee
          ORDER BY salary DESC
          LIMIT 1 OFFSET 1),
         NULL
       ) AS SecondHighestSalary

SQL中的日期函数

函数名称描述
ADDDATE()相加日期
ADDTIME()相加时间
CONVERT_TZ()从一个时区转换到另一个时区
CURDATE()返回当前日期
CURRENT_DATE(), CURRENT_DATECURDATE() 函数的同义词
CURRENT_TIME(), CURRENT_TIMECURTIME() 函数的同义词
CURRENT_TIMESTAMP(), CURRENT_TIMESTAMPNOW() 函数的同义词
CURTIME()返回当前时间
DATE_ADD()两个日期相加
DATE_FORMAT()按格式指定日期
DATE_SUB()两个日期相减
DATE()提取日期或日期时间表达式的日期部分
DATEDIFF()两个日期相减
DAY()DAYOFMONTH() 函数的同义词
DAYNAME()返回星期的名字
DAYOFMONTH()返回该月的第几天 (1-31)
DAYOFWEEK()返回参数的星期索引
DAYOFYEAR()返回一年中的天 (1-366)
EXTRACT提取日期部分
FROM_DAYS()日期的数字转换为一个日期
FROM_UNIXTIME()格式化日期为UNIX时间戳
HOUR()提取小时部分
LAST_DAY返回该参数对应月份的最后一天
LOCALTIME(), LOCALTIMENOW() 函数的同义词
LOCALTIMESTAMP, LOCALTIMESTAMP()NOW() 函数的同义词
MAKEDATE()从一年的年份和日期来创建日期
MAKETIMEMAKETIME()
MICROSECOND()从参数中返回微秒
MINUTE()从参数返回分钟
MONTH()通过日期参数返回月份
MONTHNAME()返回月份的名称
NOW()返回当前日期和时间
PERIOD_ADD()添加一个周期到一个年月
PERIOD_DIFF()返回两个时期之间的月数
QUARTER()从一个日期参数返回季度
SEC_TO_TIME()转换秒为“HH:MM:SS'的格式
SECOND()返回秒 (0-59)
STR_TO_DATE()转换一个字符串为日期
SUBDATE()当调用三个参数时,它就是 DATE_SUB() 的代名词
SUBTIME()相减时间
SYSDATE()返回函数执行时的时间
TIME_FORMAT()格式化为时间
TIME_TO_SEC()将参数转换成秒并返回
TIME()提取表达式传递的时间部分
TIMEDIFF()相减时间
TIMESTAMP()带一个参数,这个函数返回日期或日期时间表达式。有两个参数,参数的总和
TIMESTAMPADD()添加一个时间间隔到datetime表达式
TIMESTAMPDIFF()从日期时间表达式减去的间隔
TO_DAYS()返回日期参数转换为天
UNIX_TIMESTAMP()返回一个UNIX时间戳
UTC_DATE()返回当前UTC日期
UTC_TIME()返回当前UTC时间
UTC_TIMESTAMP()返回当前UTC日期和时间
WEEK()返回周数
WEEKDAY()返回星期的索引
WEEKOFYEAR()返回日期的日历周 (1-53)
YEAR()返回年份
YEARWEEK()返回年份和周

该表格摘于 https://www.yiibai.com/mysql/mysql_date_time_functions.html

利用 K8S 搭建 MySQL 集群

yaml
---  
apiVersion: v1  
kind: Namespace  
metadata:  
  name: mysql  
---  
apiVersion: v1  
kind: Secret  
metadata:  
  name: mysql-auth  
  namespace: mysql  
type: Opaque  
stringData:  
  password: "Mysql@123"  
---  
apiVersion: v1  
kind: ConfigMap  
metadata:  
  name: mysql-config  
  namespace: mysql  
data:  
  master.cnf: |  
    [mysqld]  
    server-id=1  
    log-bin=mysql-bin  
    binlog_format=row  
    gtid_mode=ON  
    enforce_gtid_consistency=ON  
    default-authentication-plugin=mysql_native_password  
    skip-host-cache  
    skip-name-resolve  
  slave.cnf: |  
    [mysqld]  
    log-bin=mysql-bin  
    gtid_mode=ON  
    enforce_gtid_consistency=ON  
    read_only=1  
    default-authentication-plugin=mysql_native_password  
    skip-host-cache  
    skip-name-resolve  
---  
apiVersion: v1  
kind: Service  
metadata:  
  name: mysql-headless  
  namespace: mysql  
spec:  
  clusterIP: None  
  selector:  
    app: mysql  
  ports:  
    - port: 3306  
---  
apiVersion: v1  
kind: Service  
metadata:  
  name: mysql-external  
  namespace: mysql  
spec:  
  type: LoadBalancer  
  loadBalancerIP: 192.168.30.68  
  selector:  
    statefulset.kubernetes.io/pod-name: mysql-0  
  ports:  
    - port: 3306  
      targetPort: 3306  
      name: mysql  
---  
apiVersion: apps/v1  
kind: StatefulSet  
metadata:  
  name: mysql  
  namespace: mysql  
spec:  
  serviceName: "mysql-headless"  
  replicas: 3  
  selector:  
    matchLabels:  
      app: mysql  
  template:  
    metadata:  
      labels:  
        app: mysql  
    spec:  
      imagePullSecrets:  
      - name: harbor-pull-secret  
      initContainers:  
      - name: init-mysql  
        image: 192.168.30.12/proxy/library/mysql:8.0  
        env:  
        - name: POD_NAME  
          valueFrom:  
            fieldRef:  
              fieldPath: metadata.name  
        command:  
        - bash  
        - "-c"  
        - |  
          set -ex  
          # 使用注入的环境变量 POD_NAME 代替 hostname 命令  
          [[ $POD_NAME =~ -([0-9]+)$ ]] || exit 1  
          ordinal=${BASH_REMATCH[1]}  
          if [[ $ordinal -eq 0 ]]; then  
            cp /mnt/config-map/master.cnf /mnt/conf.d/server.cnf  
          else  
            cp /mnt/config-map/slave.cnf /mnt/conf.d/server.cnf  
            echo -e "\nserver-id=$((100 + $ordinal))" >> /mnt/conf.d/server.cnf  
          fi  
        volumeMounts:  
        - name: conf  
          mountPath: /mnt/conf.d  
        - name: config-map  
          mountPath: /mnt/config-map  
      containers:  
      - name: mysql  
        image: 192.168.30.12/proxy/library/mysql:8.0  
        env:  
        - name: MYSQL_ROOT_PASSWORD  
          valueFrom:  
            secretKeyRef:  
              name: mysql-auth  
              key: password  
        ports:  
        - containerPort: 3306  
          name: mysql  
        volumeMounts:  
        - name: mysql-data  
          mountPath: /var/lib/mysql  
        - name: conf  
          mountPath: /etc/mysql/conf.d  
      volumes:  
      - name: conf  
        emptyDir: {}  
      - name: config-map  
        configMap:  
          name: mysql-config  
  volumeClaimTemplates:  
  - metadata:  
      name: mysql-data  
    spec:  
      accessModes: [ "ReadWriteOnce" ]  
      storageClassName: "ceph-rbd"  
      resources:  
        requests:  
          storage: 50Gi
  1. 配置从节点 1 (mysql-1)
shell
kubectl exec -it mysql-1 -n mysql -- mysql -u root -p'Mysql@123' -e "CHANGE MASTER TO MASTER_HOST='mysql-0.mysql-headless.mysql.svc.cluster.local', MASTER_USER='root', MASTER_PASSWORD='Mysql@123', MASTER_AUTO_POSITION=1; START SLAVE;"
  1. 配置从节点 2 (mysql-2)
shell
kubectl exec -it mysql-2 -n mysql -- mysql -u root -p'Mysql@123' -e "CHANGE MASTER TO MASTER_HOST='mysql-0.mysql-headless.mysql.svc.cluster.local', MASTER_USER='root', MASTER_PASSWORD='Mysql@123', MASTER_AUTO_POSITION=1; START SLAVE;"

检查同步状态

shell
kubectl exec -it mysql-1 -n mysql -- mysql -u root -p'Mysql@123' -e "SHOW SLAVE STATUS\G" | grep -E "Slave_.*_Running"
# 预期输出:Slave_IO_Running: Yes, Slave_SQL_Running: Yes