To Future$ - Dev-Tech
type: channel
link: https://blog.lvming.xin/index.php/category/dev-tech/
items: java 补全之「网络编程」
items: java 补全之「IO」
items: java 补全之「java 核心类」
items: java 补全之「面向对象编程基础」
items: Java Web 基础：登录认证-会话技术、过滤器与拦截器、事务管理
items: MySQL 二轮学习笔记·运维篇·(四) 读写分离
items: MySQL 二轮学习笔记·运维篇·(三) 分库分表
items: MySQL 二轮学习笔记·运维篇·(二) 主从复制
items: MySQL 二轮学习笔记·运维篇·(一) 日志
items: MySQL 二轮学习笔记·进阶篇·(八) InnoDB 引擎

java 补全之「网络编程」
type: item
date: 2026-01-08T15:01:00+08:00
description: 网络编程1.网络编程基础在学习Java网络编程之前，我们先来了解什么是计算机网络。计算机网络是指两台或更多的计算机组成的网络，在同一个网络中，任意两台计算机都可以直接通信，因为所有计算机都需要遵循同一种网络协议。那什么是互联网呢？互联网是网络的网络（Internet），即把很多计算机网络连接起来，形成一个全球统一的互联网。对某个特定的计算机网络来说，它可能使用网络协议ABC，而另一个计算机网络可能使用网络协议XYZ。如果计算机网络各自的通讯协议不统一，就没法把不同的网络连接起来形成互联网。因此，为了把计算机网络接入互联网，就必须使用TCP/IP协议。TCP/IP协议泛指互联网协议，其中最重要的两个协议是TCP协议和IP协议。只有使用TCP/IP协议的计算机才能够联入互联网，使用其他网络协议（例如NetBIOS、AppleTalk协议等）是无法联入互联网的。IP地址在互联网中，一个IP地址用于唯一标识一个网络接口（Network Interface）。一台联入互联网的计算机肯定有一个IP地址，但也可能有多个IP地址。IP地址分为IPv4和IPv6两种。IPv4采用32位地址，类似101.202.99.12，而IPv6采用128位地址，类似2001:0DA8:100A:0000:0000:1020:F2F3:1428。IPv4地址总共有232个（大约42亿），而IPv6地址则总共有2128个（大约340万亿亿亿亿），IPv4的地址目前已耗尽，而IPv6的地址是根本用不完的。IP地址又分为公网IP地址和内网IP地址。公网IP地址可以直接被访问，内网IP地址只能在内网访问。内网IP地址类似于：192.168.x.x10.x.x.x有一个特殊的IP地址，称之为本机地址，它总是127.0.0.1。IPv4地址实际上是一个32位整数。例如：1707762444 = 0x65ca630c = 65 ca 63 0c = 101.202.99.12如果一台计算机只有一个网卡，并且接入了网络，那么，它有一个本机地址127.0.0.1，还有一个IP地址，例如101.202.99.12，可以通过这个IP地址接入网络。如果一台计算机有两块网卡，那么除了本机地址，它可以有两个IP地址，可以分别接入两个网络。通常连接两个网络的设备是路由器或者交换机，它至少有两个IP地址，分别接入不同的网络，让网络之间连接起来。如果两台计算机位于同一个网络，那么他们之间可以直接通信，因为他们的IP地址前段是相同的，也就是网络号是相同的。网络号是IP地址通过子网掩码过滤后得到的。例如：某台计算机的IP是101.202.99.2，子网掩码是255.255.255.0，那么计算该计算机的网络号是：IP = 101.202.99.2 Mask = 255.255.255.0 Network = IP & Mask = 101.202.99.0每台计算机都需要正确配置IP地址和子网掩码，根据这两个就可以计算网络号，如果两台计算机计算出的网络号相同，说明两台计算机在同一个网络，可以直接通信。如果两台计算机计算出的网络号不同，那么两台计算机不在同一个网络，不能直接通信，它们之间必须通过路由器或者交换机这样的网络设备间接通信，我们把这种设备称为网关。网关的作用就是连接多个网络，负责把来自一个网络的数据包发到另一个网络，这个过程叫路由。所以，一台计算机的一个网卡会有3个关键配置：IP地址，例如：10.0.2.15子网掩码，例如：255.255.255.0网关的IP地址，例如：10.0.2.2域名因为直接记忆IP地址非常困难，所以我们通常使用域名访问某个特定的服务。域名解析服务器DNS负责把域名翻译成对应的IP，客户端再根据IP地址访问服务器。用nslookup可以查看域名对应的IP地址：$ nslookup liaoxuefeng.com Server: xxx.xxx.xxx.xxx Address: xxx.xxx.xxx.xxx#53 Non-authoritative answer: Name: liaoxuefeng.com Address: xxx.xxx.xxx.xxx有一个特殊的本机域名localhost，它对应的IP地址总是本机地址127.0.0.1。网络模型由于计算机网络从底层的传输到高层的软件设计十分复杂，要合理地设计计算机网络模型，必须采用分层模型，每一层负责处理自己的操作。OSI（Open System Interconnect）网络模型是ISO组织定义的一个计算机互联的标准模型，注意它只是一个定义，目的是为了简化网络各层的操作，提供标准接口便于实现和维护。这个模型从上到下依次是：应用层，提供应用程序之间的通信；表示层：处理数据格式，加解密等等；会话层：负责建立和维…

java 补全之「IO」
type: item
date: 2026-01-06T21:20:00+08:00
description: IO1.File对象在计算机系统中，文件是非常重要的存储方式。Java的标准库java.io提供了File对象来操作文件和目录。要构造一个File对象，需要传入文件路径：import java.io.*; public class Main { public static void main(String[] args) { File f = new File("C:\\Windows\\notepad.exe"); System.out.println(f); } }构造File对象时，既可以传入绝对路径，也可以传入相对路径。绝对路径是以根目录开头的完整路径，例如：File f = new File("C:\\Windows\\notepad.exe");注意Windows平台使用\作为路径分隔符，在Java字符串中需要用\\表示一个\。Linux平台使用/作为路径分隔符：File f = new File("/usr/bin/javac");传入相对路径时，相对路径前面加上当前目录就是绝对路径：// 假设当前目录是C:\Docs File f1 = new File("sub\\javac"); // 绝对路径是C:\Docs\sub\javac File f3 = new File(".\\sub\\javac"); // 绝对路径是C:\Docs\sub\javac File f3 = new File("..\\sub\\javac"); // 绝对路径是C:\sub\javac可以用.表示当前目录，..表示上级目录。File对象有3种形式表示的路径，一种是getPath()，返回构造方法传入的路径，一种是getAbsolutePath()，返回绝对路径，一种是getCanonicalPath，它和绝对路径类似，但是返回的是规范路径。什么是规范路径？我们看以下代码：import java.io.*; public class Main { public static void main(String[] args) throws IOException { File f = new File(".."); System.out.println(f.getPath()); System.out.println(f.getAbsolutePath()); System.out.println(f.getCanonicalPath()); } }绝对路径可以表示成C:\Windows\System32\..\notepad.exe，而规范路径就是把.和..转换成标准的绝对路径后的路径：C:\Windows\notepad.exe。因为Windows和Linux的路径分隔符不同，File对象有一个静态变量用于表示当前平台的系统分隔符：System.out.println(File.separator); // 根据当前平台打印"\"或"/"文件和目录File对象既可以表示文件，也可以表示目录。特别要注意的是，构造一个File对象，即使传入的文件或目录不存在，代码也不会出错，因为构造一个File对象，并不会导致任何磁盘操作。只有当我们调用File对象的某些方法的时候，才真正进行磁盘操作。例如，调用isFile()，判断该File对象是否是一个已存在的文件，调用isDirectory()，判断该File对象是否是一个已存在的目录：import java.io.*; public class Main { public static void main(String[] args) throws IOException { File f1 = new File("C:\\Windows"); File f2 = new File("C:\\Windows\\notepad.exe"); File f3 = new File("C:\\Windows\\nothing"); System.out.println(f1.isFile()); System.out.println(f1.isDirectory()); System.out.println(f2.isFile()); System.out.println(f2.isDirectory()); System.out.println(f3.isFile()); System.out.println(f3.isDirectory()); } }用File对象获取到一个文件时，还可以进一步判断文件的权限和大小：boolean canRead()：是否可读；boolean canWrite()：是否可写；boolean canExecute()：是否可执行；long length(…

java 补全之「java 核心类」
type: item
date: 2026-01-06T15:01:00+08:00
description: 面向对象编程-java核心类1.字符串字符串比较当我们想要比较两个字符串是否相同时，要特别注意，我们实际上是想比较字符串的内容是否相同。必须使用equals()方法而不能用==。我们看下面的例子：// String public class Main { public static void main(String[] args) { String s1 = "hello"; String s2 = "hello"; System.out.println(s1 == s2); System.out.println(s1.equals(s2)); } }从表面上看，两个字符串用==和equals()比较都为true，但实际上那只是Java编译器在编译期，会自动把所有相同的字符串当作一个对象放入常量池，自然s1和s2的引用就是相同的。所以，这种==比较返回true纯属巧合。换一种写法，==比较就会失败：// String public class Main { public static void main(String[] args) { String s1 = "hello"; String s2 = "HELLO".toLowerCase(); System.out.println(s1 == s2); System.out.println(s1.equals(s2)); } }结论：两个字符串比较，必须总是使用equals()方法。要忽略大小写比较，使用equalsIgnoreCase()方法。字符串编辑：去除首尾空白字符、提取子串、替换子串、分割字符串、拼接字符串使用trim()方法可以移除字符串首尾空白字符。空白字符包括空格，\t，\r，\n：" \tHello\r\n ".trim(); // "Hello"注意：trim()并没有改变字符串的内容，而是返回了一个新字符串。另一个strip()方法也可以移除字符串首尾空白字符。它和trim()不同的是，类似中文的空格字符\u3000也会被移除："\u3000Hello\u3000".strip(); // "Hello" " Hello ".stripLeading(); // "Hello " " Hello ".stripTrailing(); // " Hello"String还提供了isEmpty()和isBlank()来判断字符串是否为空和空白字符串："".isEmpty(); // true，因为字符串长度为0 " ".isEmpty(); // false，因为字符串长度不为0 " \n".isBlank(); // true，因为只包含空白字符 " Hello ".isBlank(); // false，因为包含非空白字符String类还提供了多种方法来搜索子串、提取子串。常用的方法有：// 是否包含子串: "Hello".contains("ll"); // true注意到contains()方法的参数是CharSequence而不是String，因为CharSequence是String实现的一个接口。搜索子串的更多的例子："Hello".indexOf("l"); // 2 "Hello".lastIndexOf("l"); // 3 "Hello".startsWith("He"); // true "Hello".endsWith("lo"); // true提取子串的例子："Hello".substring(2); // "llo" "Hello".substring(2, 4); "ll"要在字符串中替换子串，有两种方法。一种是根据字符或字符串替换：String s = "hello"; s.replace('l', 'w'); // "hewwo"，所有字符'l'被替换为'w' s.replace("ll", "~~"); // "he~~o"，所有子串"ll"被替换为"~~"另一种是通过正则表达式替换：String s = "A,,B;C ,D"; s.replaceAll("[\\,\\;\\s]+", ","); // "A,B,C,D"上面的代码通过正则表达式，把匹配的子串统一替换为","。关于正则表达式的用法我们会在后面详细讲解。要分割字符串，使用split()方法，并且传入的也是正则表达式：String s = "A,B,C,D"; String[] ss = s.split("\\,"); // {"A", "B", "C", "D"}拼接字符串使用静态方法join()，它用指定的字符串连接字符串数组：String[] arr = {"A", "B", "C"}; S格式化字符串字符串提供了formatted()方法和fo…

java 补全之「面向对象编程基础」
type: item
date: 2026-01-06T00:25:00+08:00
description: 面向对象编程-面向对象编程基础1.继承--super()的注意事项我们来看一个例子：// super public class Main { public static void main(String[] args) { Student s = new Student("Xiao Ming", 12, 89); } } class Person { protected String name; protected int age; public Person(String name, int age) { this.name = name; this.age = age; } } class Student extends Person { protected int score; public Student(String name, int age, int score) { this.score = score; } }运行上面的代码，会得到一个编译错误，大意是在Student的构造方法中，无法调用Person的构造方法。这是因为在Java中，任何class的构造方法，第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法，编译器会帮我们自动加一句super();所以，Student类的构造方法实际上是这样：class Student extends Person { protected int score; public Student(String name, int age, int score) { super(); // 自动调用父类的构造方法 this.score = score; } }但是，Person类并没有无参数的构造方法，因此，编译失败。解决方法是调用Person类存在的某个构造方法。例如：class Student extends Person { protected int score; public Student(String name, int age, int score) { super(name, age); // 调用父类的构造方法Person(String, int) this.score = score; } }这样就可以正常编译了！在子类的覆写方法中，如果要调用父类的被覆写的方法，可以通过super来调用。例如：class Person { protected String name; public String hello() { return "Hello, " + name; } } class Student extends Person { @Override public String hello() { // 调用父类的hello()方法: return super.hello() + "!"; } }2.继承-阻止继承final修饰符有多种作用：final修饰的方法可以阻止被覆写；final修饰的class可以阻止被继承；final修饰的field必须在创建对象时初始化，随后不可修改。从Java 15开始，允许使用sealed修饰class，并通过permits明确写出能够从该class继承的子类名称。例如，定义一个Shape类：public sealed class Shape permits Rect, Circle, Triangle { ... }上述Shape类就是一个sealed类，它只允许指定的3个类继承它。如果写：public final class Rect extends Shape {...}是没问题的，因为Rect出现在Shape的permits列表中。但是，如果定义一个Ellipse就会报错：public final class Ellipse extends Shape {...} // Compile error: class is not allowed to extend sealed class: Shape原因是Ellipse并未出现在Shape的permits列表中。这种sealed类主要用于一些框架，防止继承被滥用。sealed类在Java 15中目前是预览状态，要启用它，必须使用参数--enable-preview和--source 15。3.继承--向下转型向下转型很可能会失败。失败的时候，Java虚拟机会报ClassCastException。为了避免向下转型出错，Java提供了instanceof操作符，可以先判断一个实例究竟是不是某种类型：Person p = new Person(); System.out.println(p instanceof Person); // true Syst…

Java Web 基础：登录认证-会话技术、过滤器与拦截器、事务管理
type: item
date: 2025-10-09T17:07:15+08:00
description: 登录认证-会话技术会话：用户打开浏览器，访问web服务器的资源，会话建立，直到有一方断开连接，会话结束。在一次会话中可以包含多次请求和响应。会话跟踪：一种维护浏览器状态的方法，服务器需要识别多次请求是否来自于同意浏览器，以便在同一次会话的多次请求间共享数据。会话跟踪方案：客户端会话跟踪技术：Cookie服务端会话跟踪技术：Session令牌技术Cookie浏览器响应头 Set-Cookie:name=value客户端请求头 Cookie:name=value优点：Http协议支持缺点：移动端App无法使用Cookie；不安全，客户可以自己禁用Cookie；不能跨域（跨域：客户端和服务端协议/ip/域名/端口不同都会跨域）；【无状态】Session底层基于Cookie优点：状态存储在服务端端的内存中，安全；【状态性，Session方案的核心优势在于“状态性”。服务器端有一个“指挥中心”，它知道哪些会话是活跃的、哪些是有效的。它可以随时吊销任何会话的访问权限。】缺点：服务器集群环境下无法直接使用Session；Cookie的缺点状态性就是，服务器有存储用户信息，用户只要一个token，无状态就是，用户自己存着信息，无论是否加密令牌技术优点：支持PC端和移动端；解决集群环境下的认证问题；减轻服务器端存储压力1.解决扩展性，实现集群和分布式【多服务端】使用令牌技术的服务器端不存储任何数据。传统Session存储在单个服务器的内存中，Session的旧解决方案：通过Session复制（性能开销大）或持久化到中央数据库（如Redis）（引入单点依赖和网络延迟）来缓解，但非根本解决。令牌的解决方案：采用无状态设计。服务器不存储会话数据，用户状态自包含在令牌中。任何服务器实例只需验证令牌有效性即可处理请求，天然支持分布式集群与水平扩展。2.解决架构耦合，实现多平台通用【多客户端】Session通常与特定的服务器端技术栈（如Java Servlet、PHP、.NET）紧密耦合。它依赖于Cookie来传递Session ID，这限制了客户端的类型。而令牌（尤其是JWT）是一个开放标准，任何语言、任何平台都能生成和验证。它不依赖Cookie，可以通过HTTP Header（如 Authorization: Bearer <token>）轻松传递，这使得它适用于：原生手机App；API网关；不同域名的前端应用（解决跨域）；第三方登录（OAuth 2.0的核心）：你可以用一个令牌授权一个第三方应用访问你的部分资源，而无需提供用户名和密码3.空间换时间，解决查询性能问题【快】传统的session，即使使用Redis，每个请求都需要根据Session ID执行一次网络查询来获取用户数据。在高并发场景下，这会给中央存储带来巨大的压力和单点故障风险。而令牌技术：用户信息（如user_id, username）直接编码在令牌里，验证成功后即可直接使用，无需查询数据库。这用更大的网络传输量（令牌体积比Session ID大）换取了更低的服务器端延迟和负载。JWT不是一个简单的“加密Cookie”，而是一种更安全、更标准、更通用的无状态凭证机制。特性加密的Cookie（自研方案）JWT令牌（标准方案）1. 安全核心保密性。依赖加密算法隐藏内容。如果密钥泄露，一切皆空。完整性与验证。依赖数字签名证明真伪。Payload可公开读取，但无法篡改。签名密钥泄露是灾难性的，但内容本身不依赖加密。2. 标准化自定义的、私有的。格式和加解密方法由开发者自己定义。开放的、标准的。遵循RFC 7519标准，有固定的结构（Header.Payload.Signature），各语言都有现成库，互通性好。3. 验证方式服务器必须解密整个Cookie才能验证其有效性。服务器只需验证签名（一个数学计算），而不需要先解密整个令牌。验证通过后，可直接解码读取Payload。4. 内容可信度服务器解密后，直接信任其中的内容。服务器验证签名后，才信任其中的内容。这明确区分了“验证”和“信任”两个步骤。5. 适用边界通常与Web域名绑定，通过Cookie机制管理。协议无关、域无关。可用于任何类型的客户端（App、桌面应用）、任何API，不受同源策略限制。过滤器与拦截器1. 过滤器示例：记录请求耗时和字符编码// 使用 @WebFilter 注解或通过 FilterRegistrationBean 配置 @Component public class LoggingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain ch…

MySQL 二轮学习笔记·运维篇·(四) 读写分离
type: item
date: 2025-09-28T21:47:19+08:00
description: 读写分离1.简介定义：读写分离的本质将数据库的两种核心操作 “拆分” 到不同节点：写操作（INSERT/UPDATE/DELETE）：统一交给 “主库（Master）” 处理，保证数据写入的一致性；读操作（SELECT）：交给 “从库（Slave）” 处理，从库通过 “数据同步” 复制主库的内容，确保读数据的准确性。简单理解：主库负责 “改数据”，从库负责 “查数据”，就像餐厅里 “厨师（主库）只负责做菜，服务员（从库）负责传菜”，分工明确效率更高。为什么必须做读写分离？单数据库的瓶颈很明显：单库的 CPU、内存、IO 资源有限，大量读请求会占满 IO，导致写请求排队（比如用户下单 “写订单” 时，因大量 “查商品” 请求阻塞，出现 “下单失败”）；一旦单库宕机，整个业务直接瘫痪，可用性极低。而读写分离能解决这些问题：提升性能：读操作分散到多个从库，主库只处理写操作，资源不抢占，响应速度更快；提高可用性：即使一个从库宕机，其他从库仍能提供读服务，不会影响业务；扩展灵活：后续可通过增加从库数量，进一步分担读压力（比如从 1 个从库扩展到 3 个）。2.基础架构：一主一从的搭建与实现“一主一从” 是读写分离的最小可用架构——1 个主库 + 1 个从库，适合中小型 “读多写少” 业务（如初创公司的 APP、小型电商网站），配置简单、成本低，是入门读写分离的首选。在搭建主从同步前，必须先完成 “环境一致性” 配置，否则后续同步会直接失败。以 MySQL 为例，我们需要 2 台服务器（或 2 个 MySQL 实例），假设配置如下：节点角色IP 地址核心配置要求主库（Master）192.168.1.10开启 binlog 日志、指定唯一 server-id从库（Slave）192.168.1.11指定与主库不同的唯一 server-id① 修改主库配置文件（my.cnf）打开 /etc/my.cnf，添加以下内容（开启 binlog 是主从同步的核心 —— 主库通过 binlog 记录写操作，从库读取 binlog 实现同步）：[mysqld] server-id=10 # 唯一标识，不能与从库重复 log-bin=mysql-bin # 开启binlog，日志文件前缀为mysql-bin binlog-do-db=test # 只同步test数据库（可选，不写则同步所有库）修改后重启 MySQL：systemctl restart mysqld。② 主库创建同步账号从库需要通过一个 “专用账号” 连接主库并读取 binlog，因此在主库执行 SQL：-- 创建账号slave_user，允许从库IP（192.168.1.11）连接 CREATE USER 'slave_user'@'192.168.1.11' IDENTIFIED BY '123456'; -- 授予同步权限（REPLICATION SLAVE是核心权限） GRANT REPLICATION SLAVE ON *.* TO 'slave_user'@'192.168.1.11'; -- 刷新权限 FLUSH PRIVILEGES;③ 查看主库 binlog 信息记录主库当前的 binlog 文件名和位置（后续从库同步需要用到）：SHOW MASTER STATUS;执行后会得到类似结果：FilePositionBinlog_Do_DBBinlog_Ignore_DBmysql-bin.000001156test ④ 修改从库配置文件（my.cnf）打开 /etc/my.cnf，添加：[mysqld] server-id=11 # 必须与主库不同 relay-log=mysql-relay-bin # 开启中继日志（从库读取主库binlog后，先存到中继日志，再执行）重启从库 MySQL：systemctl restart mysqld。环境准备完成后，我们需要实现 “主从数据同步”，再配置 “读写路由”—— 让写操作走主库，读操作走从库。第一步：实现主从同步在从库执行以下 SQL，连接主库并开启同步：-- 停止现有同步（首次配置可忽略） STOP SLAVE; -- 连接主库：替换IP、账号、密码、binlog文件名和位置 CHANGE MASTER TO MASTER_HOST='192.168.1.10', # 主库IP MASTER_USER='slave_user', # 同步账号 MASTER_PASSWORD='123456', # 密码 MASTER_LOG_FILE='mysql-bin.000001', # 主库的binlog文件名（上文记录的File） MASTER_LOG_POS=156; # 主库的binlog位置（上文记录的Position） …

MySQL 二轮学习笔记·运维篇·(三) 分库分表
type: item
date: 2025-09-28T21:22:00+08:00
description: 分库分表当数据库满足以下任一条件时，就必须考虑分库分表：数据量过大：单表数据量超 1000 万（MySQL 单表最优性能区间是 100 万 - 500 万），查询时 “全表扫描” 耗时超 1 秒，甚至出现 “锁表” 导致写入阻塞；访问压力过高：单库 QPS 超 5000（MySQL 单库默认能承载的 QPS 约 2000-3000），高峰时出现 “连接耗尽”，应用无法正常访问；业务耦合严重：单库中包含 “用户、订单、商品” 等所有业务表，一次表结构变更（如订单表加字段）会影响所有业务，运维风险高。1.分库分表的核心拆分方式（1）垂直拆分：按 “业务维度” 拆拆分逻辑：把单库中不同业务的表，拆到不同的数据库中。示例：电商单库拆成 3 个独立库：用户库（user_db）：存储用户表（user）、用户地址表（user_address）；订单库（order_db）：存储订单表（order）、订单详情表（order_item）；商品库（product_db）：存储商品表（product）、商品分类表（product_category）。解决问题：降低业务耦合：用户业务变更（如加 “会员等级” 字段），不会影响订单库；分散存储压力：订单库的写入压力（每秒 1000 单），不会占用用户库的磁盘和 IO。注意：垂直拆分后，跨库查询会变复杂（如 “查用户的所有订单” 需关联 user_db 和 order_db），需依赖中间件（如 MyCat）实现 “虚拟关联”。（2）水平拆分：按 “数据维度” 拆拆分逻辑：把单张 “大表”，按某一字段（如用户 ID、时间）拆成多张 “小表”，这些小表结构完全相同，数据分散存储。示例：订单表（order）按 “用户 ID 取模” 拆成 10 张表：order_0：存储用户 ID%10=0 的数据；order_1：存储用户 ID%10=1 的数据；...order_9：存储用户 ID%10=9 的数据。解决问题：提升单表效率：原 1 亿数据的订单表，拆后每张表仅 1000 万数据，查询耗时从 1 秒降到 100ms；支持弹性扩容：后续数据量增长，可新增 order_10、order_11，无需重构表结构。注意：水平拆分后，“全局查询”（如 “查昨天所有订单总数”）需聚合所有分表数据，需中间件支持 “分布式统计”。(3)实际场景：垂直 + 水平结合大多数业务会用 “混合拆分”：先垂直拆库，再对核心表水平拆表。示例：电商系统的最终拆分方案：垂直拆库：拆成 user_db、order_db、product_db；水平拆表：对 order_db 中的 order 表，按 “用户 ID 取模” 拆成 order_0~order_9，分散到 2 台数据库服务器（每台存 5 张表）。2.分库分表中间件-MyCat分库分表后，应用不能直接连接多个后端库（否则需改代码适配路由逻辑）——MyCat就是解决这个问题的 “分布式数据库中间件”：它对外伪装成 “单台 MySQL”，接收应用请求后，自动路由到后端真实分库分表，对应用完全透明。概念定义示例（对应电商拆分场景）逻辑库（Schema）MyCat 对外暴露的 “虚拟库”，应用连接的是逻辑库，而非真实后端库逻辑库名：ecommerce（对应后端 3 个真实库）逻辑表（Table）MyCat 对外暴露的 “虚拟表”，对应后端多个 “分表”（水平拆分）或 “单表”（垂直拆分）逻辑表order：对应后端 order_db 的 order_0~order_9数据节点（DataNode）后端 “真实库 + 表” 的映射，是 MyCat 路由的 “最终目标”dn1=localhost:3306/order_db（order_db 库的节点）节点主机（DataHost）后端数据库服务器的配置，包含 IP、端口、用户名、密码，可对应多个 DataNodedataHost="db_host1"：IP=192.168.1.100，端口 33063.8种核心分片规则详解(1)范围分片核心逻辑：按分片字段的 “数值范围” 划分分片，例如按user_id划分：user_id 1~10000 → 路由到order_0；user_id 10001~20000 → 路由到order_1；user_id 20001~30000 → 路由到order_2。配置关键：在rule.xml中通过PartitionByRange算法定义范围映射，示例：<tableRule name="range-user-id"> <rule> <columns>user_id</columns> <algorithm>range-alg</algorithm> </rule> </tableRule> <function name="ran…

MySQL 二轮学习笔记·运维篇·(二) 主从复制
type: item
date: 2025-09-28T16:35:17+08:00
description: 主从复制当单台 MySQL 服务器无法满足业务需求（如读请求量过大、担心单点故障导致数据丢失）时，主从复制 是最基础的解决方案。其核心逻辑是：主库（Master）写入数据，从库（Slave）实时同步主库数据，实现 “数据备份”“读写分离”（主库写、从库读）“故障转移”（主库挂了，从库可切换为主库）。1.核心概念角色定义：主库（Master）：唯一的写入节点，所有数据变更（DML/DDL）均在主库执行，同时将变更记录到 二进制日志。从库（Slave）：1 个或多个读取节点，通过 “IO 线程” 从主库拉取二进制日志，通过 “SQL 线程” 执行日志中的变更，实现与主库数据一致。核心目标：数据备份：从库相当于主库的实时备份，主库故障时可从从库恢复数据。读写分离：主库负责写操作（INSERT/UPDATE/DELETE），从库负责读操作（SELECT），分摊主库压力，提升并发能力。故障转移：主库宕机后，可将其中一个从库升级为主库，保证业务连续性。2.原理主从复制的本质是 “从库模拟主库的操作”，整个过程通过 3 个线程（主库 1 个、从库 2 个）协同完成，步骤如下：主库记录二进制日志（binlog）当主库执行数据变更操作（如 INSERT）时，会先将操作记录到 二进制日志（binlog）中，同时给每条记录分配一个 “偏移量（Position）”，标记日志的位置。从库 IO 线程拉取二进制日志从库启动后，会启动一个 IO 线程，该线程会连接主库的 “二进制日志 dump 线程”（主库自动创建），并请求主库发送 “从指定偏移量开始的二进制日志”。主库的 dump 线程收到请求后，会读取二进制日志内容，发送给从库的 IO 线程；从库 IO 线程将收到的日志内容写入本地的 中继日志（Relay Log）（中继日志是临时日志，避免直接操作主库日志导致冲突）。从库 SQL 线程执行中继日志从库同时启动一个 SQL 线程，该线程会实时读取中继日志中的内容，解析出具体的 SQL 语句，并在从库上执行这些语句，最终实现从库数据与主库一致。关键结论：主从复制存在 微小延迟（IO 线程拉取日志、SQL 线程执行日志的时间差），通常在毫秒到秒级，大部分业务可接受；若需 “无延迟同步”，需使用 MySQL 8.0 后的 “GTID 复制” 或 “半同步复制”（进阶内容）。3.常见问题(1)主从延迟过大主库执行数据变更后，从库需要较长时间（如几秒、几分钟）才能查询到变更结果。常见原因从库压力过大（大量读请求占用 CPU/IO，导致 SQL 线程执行缓慢）。主库写入量过高（短时间内大量 DML 操作，二进制日志生成速度超过从库同步速度）。大事务（主库执行耗时较长的大事务，如批量插入 10 万条数据，从库需等待整个事务执行完成才能同步）。解决方案优化从库：给从库配置更高性能的硬件（CPU、SSD），或增加从库数量（通过 “一主多从” 分摊读压力）。拆分大事务：将主库的大事务拆分为多个小事务，减少单次同步耗时。启用并行复制（MySQL 5.7+）：通过 slave_parallel_workers = 4（配置 4 个并行 SQL 线程），让从库可并行执行多个事务，提升同步效率（需配合 binlog_format=ROW）。(2)主从数据不一致主库有某条数据，从库没有；或主从数据字段值不同，导致 Slave_SQL_Running: No。常见原因从库开启了写操作（未设置 read_only=ON，误在从库插入 / 修改数据）。主库执行了从库不支持的 SQL（如主库使用自定义函数，从库未创建该函数）。主从复制中断后未正确恢复（如主库日志文件被删除，从库无法找到同步起点）。解决方案紧急恢复，通过主库全量备份初始化从库（推荐使用 mysqldump 工具）主库执行备份：mysqldump -u root -p --all-databases --lock-all-tables > master_backup.sql（--lock-all-tables 确保备份期间数据不变化）。复制备份文件到从库：scp master_backup.sql root@192.168.1.101:/tmp/。从库恢复备份：mysql -u root -p < /tmp/master_backup.sql，之后重新执行 CHANGE MASTER TO 关联主库最新的日志位置。长期预防从库严格设置 read_only=ON，并限制超级权限用户的使用（避免通过 root 账号在从库写数据）。定期使用工具检查数据一致性（如 pt-table-checksum，自动对比主从表数据，发现不一致时生成修复语句）。(3)主库二进制日志丢失主库二进制日志文件被误删除（或因磁盘故障损坏），导致从库 IO …

MySQL 二轮学习笔记·运维篇·(一) 日志
type: item
date: 2025-09-28T16:16:39+08:00
description: 日志所有日志的配置均通过 my.cnf（Linux） 或 my.ini（Windows） 配置文件生效，修改后需 重启 MySQL 服务 才能生效（部分日志支持动态开启）。通用配置步骤找到 MySQL 配置文件：通过 mysql --help | grep my.cnf 查看默认路径（如 /etc/my.cnf）。在 [mysqld] 模块下添加日志相关配置项。重启 MySQL 服务：systemctl restart mysqld（Linux）或 “服务” 中重启（Windows）。验证日志是否生效：查看日志文件是否生成，或通过 show variables like '%日志关键字%' 确认配置。注意事项磁盘空间监控：所有日志（尤其是二进制日志、查询日志）会持续占用磁盘，需配置自动过期（如 expire_logs_days），并定期检查磁盘使用率（避免日志占满导致数据库崩溃）。权限控制：日志文件需设置 MySQL 所属权限（如 chown mysql:mysql /var/log/mysql/），避免其他用户篡改或泄露数据。按需开启：除错误日志默认开启外，二进制日志（主从 / 恢复必开）、慢查询日志（性能优化必开）建议长期开启；查询日志仅临时开启，用完即关。1.错误日志（Error Log）作用：记录 MySQL 启动 / 关闭过程中的错误、运行中 critical 级别的故障（如内存不足、索引损坏、连接失败等），是 排查 “数据库起不来”“服务崩溃” 的首要日志。默认状态：默认开启，无需额外配置（仅需确认路径即可）。核心配置（my.cnf 中添加）：[mysqld] log-error=/var/log/mysql/mysqld-error.log # 日志路径（建议放在非系统盘，避免占满根目录） log-warnings=2 # 可选：记录警告信息（1=默认，2=记录所有警告，0=关闭）查看方式：直接查看文件：tail -f /var/log/mysql/mysqld-error.log（实时跟踪最新错误）。关键场景示例：若 MySQL 启动失败，执行 systemctl status mysqld 会提示 “查看错误日志”，此时打开上述文件，搜索 ERROR 关键字即可定位原因（如 “端口被占用”“配置文件语法错误”“数据目录权限不足”）。2.二进制日志（Binary Log）作用：记录所有数据变更操作（如 INSERT/UPDATE/DELETE、CREATE TABLE 等 DML/DDL 语句），但不记录查询操作（如 SELECT）。核心用途：数据恢复：通过二进制日志恢复指定时间点的数据（配合全量备份）。主从复制：主库通过二进制日志将数据变更同步给从库（后续主从复制章节的核心依赖）。默认状态：默认关闭，需手动开启。核心配置（my.cnf 中添加）：[mysqld] server-id=1 # 必须配置：唯一标识（主从复制中主库和从库的 server-id 不能重复） log-bin=/var/log/mysql/mysql-bin # 开启二进制日志，日志文件前缀为 mysql-bin（会自动生成 mysql-bin.000001、mysql-bin.000002 等） binlog_format=ROW # 二进制日志格式（3种可选，推荐 ROW）： # - ROW：记录行数据变更（最安全，主从复制无歧义，推荐） # - STATEMENT：记录执行的 SQL 语句（可能有函数/存储过程导致主从不一致） # - MIXED：混合模式（自动切换，兼容场景） expire_logs_days=7 # 可选：日志自动过期时间（7天，避免日志占满磁盘）关键操作命令：命令作用show binary logs;查看所有二进制日志文件列表show master status;查看当前正在写入的二进制日志文件及偏移量（主从复制必用）flush logs;手动切换二进制日志（生成新文件，如从 000001 切换到 000002）purge binary logs to 'mysql-bin.000003';删除指定文件之前的所有二进制日志（避免手动删文件导致索引错乱）3.查询日志（General Query Log）作用：记录 所有发送到 MySQL 服务器的 SQL 语句（包括 SELECT 查询、连接请求等），相当于 “数据库的全量操作日记”。默认状态：默认关闭（因会记录大量无关查询，占用磁盘且影响性能，仅在排查特定问题时临时开启）。核心配置（my.cnf 中添加）：[mysqld] general_log=ON # 开启查询日志 general_log_file=/var/log/mysql/mysql-general.…

MySQL 二轮学习笔记·进阶篇·(八) InnoDB 引擎
type: item
date: 2025-09-28T16:02:12+08:00
description: InnoDB 引擎1.逻辑存储结构层级大小作用表空间（Tablespace）可变（GB 级）最高层逻辑容器，对应 .ibd 文件（独立表空间）或 ibdata1（系统表空间）段（Segment）动态增长每棵 B+ 树对应一个段。管理该树所有页的分配区（Extent）1MB = 64 页空间分配的基本单位。段从表空间中申请区页（Page）16KB（默认）I/O 的最小单位。B+ 树的节点（根、内节点、叶节点）都存储在页中行（Row）可变实际数据记录，存储在叶子页（聚簇索引）或索引页（二级索引）(1)表空间（Tablespace）：数据的 “大仓库”表空间是 InnoDB 存储的最高层级，负责整合所有数据文件，分为两种类型：系统表空间（ibdata1）：默认存储方式，所有数据库的表数据、索引、undo log、数据字典（表结构信息）都存放在这里。缺点是文件会无限膨胀，后期难以管理。独立表空间（.ibd 文件）：通过innodb_file_per_table=ON开启，每个表对应一个独立的.ibd文件，仅存储当前表的数据和索引。优势是删除表时可直接释放空间，便于维护（生产环境必开）。(2)段（Segment）：表空间的 “功能分区”段是表空间的子单位，按 “功能用途” 划分，核心分为三类：数据段（Clustered Index Segment）：即聚簇索引对应的段，InnoDB 中 “表即索引”，聚簇索引的叶子节点就是实际数据行，因此数据段直接存储表数据。索引段（Secondary Index Segment）：非聚簇索引（普通索引）对应的段，叶子节点存储的是 “聚簇索引键值”（而非实际数据），仅用于定位数据行。回滚段（Rollback Segment）：存储 undo log 的段，每个回滚段包含 1024 个 undo 页，用于事务回滚和 MVCC（后续重点讲）。段类型对应数据结构是否 B+ 树？说明数据段（Clustered Index Segment）聚簇索引✅ 是表数据就存在这棵 B+ 树的叶子节点中索引段（Secondary Index Segment）二级索引✅ 是每个二级索引是一棵独立的 B+ 树回滚段（Rollback Segment）Undo Log❌ 否用于存储 undo 记录，结构是 链表/日志型，非树形临时段（Temporary Segment）排序、临时表等❌ 否用于排序、GROUP BY 等操作的临时空间系统段（System Segment）数据字典、事务系统等❌ 否存储元数据，如 TRX_SYS、SYS_TABLES 等“段”是一个通用的空间管理抽象，B+ 树只是其中一种使用场景。(3)区（Extent）：段的 “最小分配单元”区是段的组成单位，固定大小为 1MB（无论页大小是多少），每个区包含 64 个连续的 “页”（因为默认页大小 16KB，64×16KB=1MB）。设计目的：避免 “频繁分配小空间”—— 如果直接按页分配，当表数据量增大时，会产生大量离散的页，导致磁盘 IO 碎片化；按区分配可保证 64 个页连续，减少随机 IO。(4)页（Page）：InnoDB 的 “最小 IO 单元”页是 InnoDB 读写数据的基本单位（类比操作系统的 “块”），默认大小 16KB（可通过innodb_page_size调整为 4K/8K/32K 等），核心页类型包括：数据页（B-tree Node）：存储表数据和索引的 B + 树节点（聚簇索引和非聚簇索引的叶子 / 非叶子节点）。undo 页（Undo Log Page）：存储 undo log 记录。重做日志页（Redo Log Page）：存储 redo log 记录。系统页（System Page）：存储数据字典等系统信息。(5)行（Row）：数据的 “最小存储单元”行是最终存储业务数据的单位，InnoDB 支持两种行格式（通过innodb_default_row_format设置）：Dynamic（默认）：适合大字段（如 VARCHAR、TEXT），大字段会被拆分到 “溢出页” 存储，行中仅保留 20 字节的指针。Compact：大字段的前 768 字节存在行中，剩余部分存溢出页，兼容性更好但空间利用率略低。此外，每行数据都会隐藏 3 个核心字段（MVCC 依赖）：DB_TRX_ID（6 字节）：最后修改该记录的事务 ID。DB_ROLL_PTR（7 字节）：指向该记录的上一个版本（undo log 中的位置）。DB_ROW_ID（6 字节）：默认不显示，当表没有主键且没有唯一索引时，InnoDB 会自动生成该字段作为聚簇索引键。2.架构-内存与磁盘InnoDB 的架构核心是 “内存缓冲 + 磁盘持久化”，通过内存减少磁盘 IO，通过磁盘保证数…