Lehman-Blog - Mysql
type: channel
link: https://blog.lehman.top/tag/Mysql/
items: 新时代的数据库全景：从业务基石到 AI 记忆库
items: MySQL知识扩展：sql_mode
items: MYSQL-DDL/DML/DCL/DQL
items: MYSQL常用优化技巧
items: MYSQL常见问题汇总
items: MYSQL常用功能汇总

新时代的数据库全景：从业务基石到 AI 记忆库
type: item
date: 2026-06-25T14:50:00+08:00
description: 前言上周有个朋友问我：「我们新项目刚立项，MySQL 够不够用？要不要一步到位上向量库？」我反问：「你们是要做电商下单，还是要做企业知识问答？」他愣了两秒：「……都要。」这就是 2026 年做后端最常踩的坑——把「选一个数据库用到退休」当成架构目标。业务一多元、AI 一进场，数据库早就不是「一招鲜吃遍天」的时代了。关系型、缓存、向量、NoSQL 各守一块地盘，拼起来才是现代系统的常态。结论：没有银弹，只有 Polyglot Persistence（多模持久化）——让专业的库干专业的事。[scode type="red"]别指望一个数据库包打天下，那是 PPT 架构，不是生产架构。[/scode]下面按我自己梳理选型时的思路，把 2026 年主流数据库的门派和战场过一遍。全文不堆版本号和 benchmark——那些东西三个月一过期，定位和场景才是不容易过时的。关系型：Still the 业务基石关系型数据库（RDBMS）是最老派、也最不能省的那一层。数据按二维表存，事务严格走 ACID（原子性、一致性、隔离性、持久性）——翻译成人话就是：钱和库存这种事儿，错一条都不行。我把它比作公司里的「账本会计」：花样不多，但每一笔都得对得上。AI 再火，下单扣库存、银行转账这类链路，短期内还是绕不开它。MySQL 和 PostgreSQL 怎么选？我自己的粗线条是：团队熟 MySQL、业务偏 Web 和电商，MySQL 省心；复杂查询、地理信息、需要 JSON 和扩展插件同台，PostgreSQL 更香。Oracle 则是「关键任务 + 预算充足 + 运维团队扛得住」的组合，不是创业公司第一天该想的事。产品核心定位最佳战场MySQL轻量开源，生态成熟，Web 应用事实标准中小型互联网、电商后台、创业公司快速迭代PostgreSQL功能全面的开源数据库，复杂查询与扩展能力强复杂业务逻辑、科研数据处理、GIS / LBSOracle企业级闭源商业库，稳定性与安全性标杆银行核心账务、大型国企 ERP、电信计费等关键任务TiDB分布式 SQL，MySQL 协议兼容，HTAP 架构（OLTP + 实时分析同库）MySQL 规模化、需要实时分析又不想搞 ETL 管道的团队OceanBase国产分布式库，金融级 OLTP，支持 MySQL 与 Oracle 双模式租户高并发交易、Oracle 迁移、信创替代TiDB 和 OceanBase 常被放在一起比，但设计重心不一样：TiDB 更偏 HTAP，用 TiFlash 在同一套数据上做实时分析；OceanBase 更偏 极限 OLTP 和 Oracle 替代，多租户模型对从 Oracle 迁过来的团队更友好。具体选型可以看 PingCAP 的 TiDB vs OceanBase 对比。典型场景我一般会先想到这三类：电商交易：下单、扣库存、支付流水——强一致性链路，RDBMS 主场。企业管理：ERP、CRM，表结构固定、关联复杂，MySQL 或 PostgreSQL 都能打。金融账务：转账、信贷、证券交易——数据丢失或错乱等于事故，Oracle 或国产分布式库更常见。国产分布式这条线，2026 年已经能正经上台面了，但别因为「国产」就跳过 POC。TiDB 和 OceanBase 都兼容 MySQL 协议，应用层改动可以很少，真正要验的是：你的慢 SQL、分布式事务边界、运维工具链能不能接住。缓存与内存：给慢 SQL 打肾上腺素当读请求多到把数据库读趴下，就该请缓存出场了。核心思路很简单：把热数据丢进内存，微秒级响应，给后面的 RDBMS 挡流量。Redis 和 Memcached 常被放在一起比，我的口诀是：要数据结构、要持久化选项、要分布式锁 → Redis；只要最简单的 KV、纯缓存、越轻越好 → Memcached。Ignite 则是另一个物种——它不只是缓存，而是带 SQL 和 ACID 的分布式内存数据库，适合「算得比存得还猛」的场景，但运维复杂度也高一档。产品核心定位最佳战场Redis数据结构丰富（String、Hash、List、Set、ZSet 等），生态绝对主流全能缓存、分布式锁、消息队列、排行榜、购物车Memcached极简键值存储，多线程，极致简单纯页面缓存、查询结果集缓存（不需要持久化时）Apache Ignite分布式内存数据库，支持 SQL 与 ACID需要内存级响应的复杂计算、高频交易系统三个场景，我面试候选人时也爱问：秒杀抢购：流量洪峰先打在 Redis 上，拦截重复请求，别让主库直接被击穿。热搜榜 / 战力排行：ZSet 天然适合，实时更新比扫 SQL 快一个数量级。Session / Token：多设备登录状态共享，Redis 当会话仓库很顺手。顺带提醒一句：缓存是加速…

MySQL知识扩展：sql_mode
type: item
date: 2025-05-28T10:45:00+08:00
description: 前言最近公司开发同学A在群聊中说某某系统的某条SQL有问题，抛出异常，而同样的SQL在开发同学B（SQL由B同学编写）的环境没有问题，带着好奇心我上前协助一看，最终发现异常大致内容是：sql_mode=ONLY_FULL_GROUP_BY，SQL大致类似如下格式：SELECT name, age, SUM(sales) FROM orders GROUP BY name; 此时让我想起了年轻的自己，当初也犯过如此问题，于是我耐心的告知开发同学B：SELECT 中的非聚合字段必须出现在 GROUP BY 子句中所有非聚合字段需通过聚合函数（如MAX、MIN、SUM）处理，或显式声明分组依据解释完毕后，开发同学B提出疑问，为什么开发同学A的环境不行，而我的就可以，于是我在他们电脑分别使用了SELECT VERSION(); 语句进行查看版本，然后告知开发同学A和开发同学B，自MySQL 5.7.5 起：官方将ONLY_FULL_GROUP_BY 加入默认的sql_mode，以增强 SQL 的标准兼容性和数据一致性，当我说完 打算扬长而去时，两位开发同学随即又抛出疑问：sql_mode是什么？我：......简介MySQL为了支持在不同的环境下运行，允许我们给它设置不同的运行模式（sql_mode）sql_mode是MySQL中用于设置sql语法和行为的系统变量控制MySQL的sql解析和执行的方式，使其与sql标准或其他数据库系统的行为一致，通过设置sql_mode，可以改变MySQL处理待定sql操作的方式[scode type="green"]我们在安装mysql时就要确定它的运行模式（sql_mode），不建议在中途更改它的运行模式[/scode][scode type="green"]建议在跨mysql实例备份还原数据时，保持双方mysql实例具有相同的运行模式[/scode]sql_mode模式MySQL 支持的 sql_mode 模式模式名含义STRICT_TRANS_TABLES事务表中对无效数据报错，非事务表警告STRICT_ALL_TABLES所有表对无效数据都报错NO_ZERO_IN_DATE不允许日期中的月或日为 0（如 2023-00-10）NO_ZERO_DATE不允许日期为 '0000-00-00'ERROR_FOR_DIVISION_BY_ZERO除以 0 报错NO_AUTO_CREATE_USER禁止 GRANT 自动创建用户（MySQL 8.0 移除）NO_ENGINE_SUBSTITUTION禁止在指定引擎不可用时自动替代ONLY_FULL_GROUP_BYGROUP BY 中必须列出所有非聚合列ANSI_QUOTES用双引号表示标识符（字段、表名等）PIPES_AS_CONCAT使用 “||” 表示字符串拼接IGNORE_SPACE函数名后允许加空格ANSI启用符合 ANSI SQL 的语法和行为TRADITIONAL启用接近标准 SQL 的严格模式组合NO_BACKSLASH_ESCAPES禁用反斜杠 “\” 作为转义字符HIGH_NOT_PRECEDENCE改变 “NOT” 运算符的优先级NO_UNSIGNED_SUBTRACTION无符号数减法结果转换为有符号类型REAL_AS_FLOAT将 “REAL” 类型作为 “FLOAT” 处理PAD_CHAR_TO_FULL_LENGTH使用 “CHAR” 比较时右侧填充空格至固定长度ALLOW_INVALID_DATES允许插入无效日期（非严格模式时有效）TIME_TRUNCATE_FRACTIONAL时间字段截断小数秒部分而非四舍五入sql_mode对比对比项MySQL 5.7 默认值MySQL 8.0 默认值默认 sql_modeONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTIONONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTIONNO_AUTO_CREATE_USER默认启用已移除（不再支持 GRANT 创建用户）严格性中等更严格，接近 SQL 标准查看和修改sql_modesql_mode，分为全局的、会话的（即：针对当前链接有效），在mysql运行时查看和更改sql_mode的sql如下：-- 查看当前会话的sql_mode selec…

MYSQL-DDL/DML/DCL/DQL
type: item
date: 2020-11-21T15:20:00+08:00
description: [scode type="red"]以下示例基于MYSQL[/scode][collapse status="false" title="常用DDL"]DDLDDL：[Data Mefinition Language] 数据定义语言关键点： CREATE TABLE、ALTER TABLE、DROP TABLE、CREATE/DROP INDEX 等主要是用在定义或改变表（TABLE）的结构，数据类型，表之间的链接和约束等初始化工作上，他们大多在建立表时使用DDL操作是隐性提交的！不能rollback [scode type="blue"]创建数据库[/scode] # 创建 user 数据库 CREATE DATABASE user; [scode type="blue"]删除数据库[/scode] # 删除user数据库 DROP DATABASE user; [scode type="blue"] 创建表[/scode] # 语法： CREATE TABLE [IF NOT EXISTS] 表名( 字段名 字段类型 【字段约束】, 字段名 字段类型 【字段约束】, 字段名 字段类型 【字段约束】 ); # 案例：创建一张表【没有添加约束】 CREATE TABLE IF NOT EXISTS stuinfo ( stuid INT, stuname VARCHAR (20), stugender CHAR(1), email VARCHAR (20), borndate DATETIME ) ; -- 案例： DROP TABLE IF EXISTS stuinfo; CREATE TABLE IF NOT EXISTS stuinfo( stuid INT PRIMARY KEY, --添加了 主键约束 stuname VARCHAR(20) UNIQUE NOT NULL, -- 添加了 唯一约束+非空 stugender CHAR(1) DEFAULT '男', -- 添加了默认约束 email VARCHAR(20) NOT NULL, age INT CHECK( age BETWEEN 0 AND 100), -- 添加了检查约束，mysql不支持 majorid INT, CONSTRAINT fk_stuinfo_major FOREIGN KEY (majorid) REFERENCES major(id) -- 添加了外键约束-- majorid列 与 major表内的id列 ); [scode type="blue"]修改表[/scode] # 语法： ALTER TABLE 表名 ADD|CHANGE|MODIFY|DROP COLUMN 字段名 字段类型 【字段约束】 # 1，修改表名 ALTER TABLE stuinfo RENAME TO students ; # 2.添加字段 ALTER TABLE students ADD COLUMN borndate TIMESTAMP NOT NULL ; # 3.修改字段名 ALTER TABLE students CHANGE COLUMN borndate birthday DATETIME NULL ; # 4.修改字段类型 ALTER TABLE students MODIFY COLUMN birthday TIMESTAMP ; # 5.删除字段 ALTER TABLE students DROP COLUMN birthday ; [scode type="blue"]删除表[/scode] DROP TABLE IF EXISTS students ; [scode type="blue"] 复制表[/scode] # 复制表 CREATE TABLE newTable LIKE students ; # 复制表的结构+数据 CREATE TABLE newTable SELECT * FROM girls.`admin` #案例：复制employees表中的last_name，department_id，salary字段到新表 xx 表，但不复制数据 CREATE TABLE xx SELECT last_name, department_id, salary FROM myemployees.`employees` WHERE 1=2; # 恒不成立 [/collapse][collapse status="false" title="常用DML"]DMLDML：[Data Manipulation Language] 数据操纵语言关键点： INSERT、UPDATE、DELETE主要是对表数据进行操作，如对表数据进行增、删…

MYSQL常用优化技巧
type: item
date: 2020-11-21T14:00:00+08:00
description: 从15个方面，分享sql优化的一些小技巧避免使用select *很多时候，我们写sql语句时，为了方便，喜欢直接使用select *，一次性查出表中所有列的数据，例如:select * from user where id=1; 在实际业务场景中，可能我们真正需要使用的只有其中一两列。查了很多数据，但是不用，白白浪费了数据库资源，比如：内存或者cpu。此外，多查出来的数据，通过网络IO传输的过程中，也会增加数据传输的时间。还有一个最重要的问题是：select *不会走覆盖索引，会出现大量的回表操作，而从导致查询sql的性能很低。我们需要sql语句查询时，只查需要用到的列，多余的列根本无需查出来select name,age from user where id=1; 用union all代替union我们都知道sql语句使用union关键字后，可以获取排重后的数据。而如果使用union all关键字，可以获取所有数据，包含重复的数据。(select * from user where id=1) union (select * from user where id=2); 排重的过程需要遍历、排序和比较，它更耗时，更消耗cpu资源。所以如果能用union all的时候，尽量不用union。(select * from user where id=1) union all (select * from user where id=2); 小表驱动大表小表驱动大表，也就是说用小表的数据集驱动大表的数据集。假如有order和user两张表，其中order表有10000条数据，而user表有100条数据。这时如果想查一下，所有有效的用户下过的订单列表。可以使用in关键字实现：select * from order where user_id in (select id from user where status=1) 也可以使用exists关键字实现：select * from order where exists (select 1 from user where order.user_id = user.id and status=1) 前面提到的这种业务场景，使用in关键字去实现业务需求，更加合适。为什么呢？因为如果sql语句中包含了in关键字，则它会优先执行in里面的子查询语句，然后再执行in外面的语句。如果in里面的数据量很少，作为条件查询速度更快。而如果sql语句中包含了exists关键字，它优先执行exists左边的语句（即主查询语句）。然后把它作为条件，去跟右边的语句匹配。如果匹配上，则可以查询出数据。如果匹配不上，数据就被过滤掉了。这个需求中，order表有10000条数据，而user表有100条数据。order表是大表，user表是小表。如果order表在左边，则用in关键字性能更好。总结：in 适用于左边大表，右边小表exists 适用于左边小表，右边大表不管是用in，还是exists关键字，其核心思想都是用小表驱动大表。批量操作有一批数据经过业务处理之后，需要插入数据for(Order order: list){ orderMapper.insert(order): } 然后在循环中逐条插入数据。insert into order(id,code,user_id) values(123,'001',100); 该操作需要多次请求数据库，才能完成这批数据的插入。应该优化为：orderMapper.insertBatch(list)； 提供一个批量插入数据的方法。insert into order(id,code,user_id) values(123,'001',100),(124,'002',100),(125,'003',101); 这样只需要远程请求一次数据库，sql性能会得到提升，数据量越多，提升越大。但需要注意的是，不建议一次批量操作太多的数据，如果数据太多数据库响应也会很慢。批量操作需要把握一个度，建议每批数据尽量控制在500以内。如果数据多于500，则分多批次处理。多用limit有时候，我们需要查询某些数据中的第一条，比如：查询某个用户下的第一个订单，想看看他第一次的首单时间select id, create_date from order where user_id=123 order by create_date asc; 根据用户id查询订单，按下单时间排序，先查出该用户所有的订单数据，得到一个订单集合。然后在代码中，获取第一个元素的数据，即首单的数据，就能获取首单时间。List<Order> list = orderMapper.getOrderList(); Order order = …

MYSQL常见问题汇总
type: item
date: 2020-11-21T13:56:00+08:00
description: [scode type="red"]基于MYSQL[/scode][collapse status="false" title="常见命令"]# root密码登录 mysql -u root -p # 指定ip地址密码登录 mysql -u root -h 192.168.1.1 -p # 查询用户和主机(以root身份) use mysql; select user,host from user; [/collapse][collapse status="false" title="远程主机无法访问"][scode type="yellow"]一般情况：修改host,然后问题解决[/scode]Host XXX is not allowed to connect to this MySQL server修改host# 以root身份登录 use mysql; # 查看host的状态 select host from user where user='root'; # 将Host设置为通配符% update user set host = '%' where user ='root'; # 刷新权限即可 flush privileges; [scode type="yellow"]特殊情况：修改host仍然未解决[/scode]1.3306端口冲突解决方法：# 打开命令行，查看3306端口 netstat -ano | findstr "3306" # 查看占用端口的程序 tasklist | findstr "9158" # 发现有两个mysql进程都占据了3306端口； # 可使用快捷键win+r,输入`services.msc`,在服务中关闭一个mysql或者直接使用命令杀掉一个进程（如杀掉9158进程） taskkill /pid 9158 # 然后问题解决 [/collapse][collapse status="false" title="忘记密码"][scode type="yellow"]windows环境[/scode]1.在mysql安装目录下,找到my.ini文件，打开文件，在[mysqld]后面任意行，加入skip-grant-tables；2.重启MySQL：使用快捷键win+r打开运行，输入services.msc,回车打开服务，找到mysql服务，然后右击选择重启MySQL；3.打开cmd命令行，输入mysql -u root -p,直接登录mysql;4.修改root密码use mysql; # 旧的修改语句 update user set password=password("你的新密码") where user="root"; # 使用旧的修改语句如果报错：Unknown column ‘password’ in ‘field list’，则使用下面语句： # 注：新版本的mysql对密码有要求，具体见附录 update mysql.user set authentication_string=password('root') where user='root' ; # 刷新权限 flush privileges; 5.再次打开my.ini文件，删除skip-grant-tables,然后重启mysql服务。[scode type="yellow"]linux环境[/scode]1.打开my.cnf文件，vi /etc/my.cnf，[mysqld]后面任意行，加入skip-grant-tables，用来跳过密码验证的过程；2.使用/etc/init.d/mysql restart(或可能需要使用/etc/init.d/mysqld restart)重启mysql服务；3.登录mysql,并修改密码，具体过程与windows环境一致；4.修改完成后再打开my.cnf文件，删除skip-grant-tables,然后重启mysql服务。[/collapse][collapse status="false" title="添加用户并授权"]# 以root用户登录mysql mysql -u root -p use mysql; # 添加只能本地访问的新用户 create user 'user'@'localhost' identified by 'user!123456'; # 添加外网ip可以访问的新用户 create user 'user'@'%' identified by 'user!123456'; # 用户创建完成后需要授权 flush privileges; # 基于业务的授权 grant all privileges on dbname.* to user@'%' identif…

MYSQL常用功能汇总
type: item
date: 2020-11-21T09:57:00+08:00
description: [collapse status="false" title="表生成Excel"]SELECT COLUMN_NAME 列名, COLUMN_TYPE 数据类型, DATA_TYPE 字段类型, CHARACTER_MAXIMUM_LENGTH 长度, IS_NULLABLE 是否为空, COLUMN_DEFAULT 默认值, COLUMN_COMMENT 备注 FROM INFORMATION_SCHEMA. COLUMNS WHERE table_schema = 'sxepp' AND table_name = 'sx_jc_car' sxepp为数据库名称，到时候只需要修改成你要导出表结构的数据库即可sx_jc_car为表名，到时候换成你要导出的表的名称table_name 如果不写的话，默认会查询出所有表中的数据，这样可能就分不清到底哪些字段是哪张表中的了，所以还是建议写上要导出的名名称[/collapse][collapse status="false" title="插入大量数据"]创建随机生成字符串函数delimiter $$ CREATE FUNCTION rand_string(n int) RETURNS varchar(255) begin declare chars_str varchar(100) default "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; declare return_str varchar(255) default ""; declare i int default 0; while i < n do set return_str=concat(return_str,substring(chars_str,floor(1+rand()*62),1)); set i= i+1; end while; return return_str; end $$ delimiter ; 创建循环插入函数delimiter $$ CREATE PROCEDURE `insert_data`(IN n int) BEGIN DECLARE i INT DEFAULT 1; WHILE (i <= n ) DO INSERT into system_inventory (i_on,related_units,opening_date ) VALUEs (rand_string(20),FLOOR(RAND() * 100) ,now() ); set i=i+1; END WHILE; END $$ delimiter ; 执行插入函数call insert_data(10000); [/collapse][collapse status="false" title="递归查询"]查询子节点的函数 查询时 包含自身（示例）create function getChildrenByFolder(folderId INT) returns varchar(4000) BEGIN DECLARE temp VARCHAR(4000); DECLARE tempChild VARCHAR(4000); SET temp = ''; SET tempChild = CAST(folderId AS CHAR); WHILE tempChild IS NOT NULL DO SET temp = CONCAT(temp,',',tempChild); SELECT GROUP_CONCAT(folder_id) INTO tempChild FROM system_base_folder WHERE FIND_IN_SET(parent_id,tempChild) > 0; END WHILE; RETURN temp; END 使用函数 进行递归查询SELECT * FROM system_base_folder WHERE FIND_IN_SET(folder_id,getChildrenByFolder(4)); 查询父节点 查询的时候 包含自身（示例）CREATE FUNCTION `getParentByOrganizationr`(organizationId longtext) RETURNS longtext CHARSET utf8 BEGIN DECLARE temp longtext default ''; DECLARE tempParent longtext default organizationId; WHILE organizationId is not null do SET temp =…