Skip to content

SQL 语言

字数: 0 字 时长: 0 分钟

SQL (Structured Query Language)是管理关系型数据库的标准语言。MySQL 支持标准的 SQL 语法,同时也有自己的扩展。

DDL

DDL(Data Definition Language),数据库定义语言,用于定义和管理数据库对象(如数据库、表、索引)。

数据库操作

sql
CREATE DATABASE [IF NOT EXISTS] db_name 
[DEFAULT CHARACTER SET charset_name]  -- 字符集(推荐 utf8mb4)
[COLLATE collation_name]              -- 排序规则(如 utf8mb4_unicode_ci)
[ENCRYPTION {'Y' | 'N'}];            -- MySQL 8.0+ 支持透明加密
sql
ALTER DATABASE db_name 
[CHARACTER SET charset_name]
[COLLATE collation_name];
sql
DROP DATABASE [IF EXISTS] db_name;  -- 连级删除所有表(高危操作!)

表级操作

sql
CREATE [TEMPORARY] TABLE [IF NOT EXISTS] table_name (
    column1 datatype [column_constraints],
    column2 datatype [column_constraints],
    ...
    [table_constraints]
) [ENGINE=storage_engine]        -- 存储引擎(InnoDB/MyISAM)
  [DEFAULT CHARSET=utf8mb4]      -- 默认字符集
  [COMMENT '表注释']
  [PARTITION BY ...];            -- 分区表(高级特性)
       
# 示例
CREATE TABLE users (
       id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
       username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
       email VARCHAR(100) NOT NULL UNIQUE CHECK (email LIKE '%@%'),
       balance DECIMAL(12,2) DEFAULT 0.00 COMMENT '账户余额',
       reg_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
       INDEX idx_email (email)  -- 非唯一索引
) ENGINE=InnoDB 
  CHARSET=utf8mb4 
  COMMENT '用户主表';
sql
ALTER TABLE table_name
    ADD COLUMN col_name datatype [constraints] [FIRST|AFTER col]  -- 新增列
    DROP COLUMN col_name                                          -- 删除列
    MODIFY COLUMN col_name new_datatype [constraints]             -- 修改列类型
    CHANGE COLUMN old_name new_name new_datatype [constraints]    -- 重命名列
    RENAME TO new_table_name                                      -- 表重命名
    ADD INDEX index_name (col1, col2)                             -- 添加索引
    ADD CONSTRAINT fk_name FOREIGN KEY (col) REFERENCES tbl(col)  -- 添加外键
    DROP PRIMARY KEY                                              -- 删除主键
    DROP FOREIGN KEY fk_symbol;                                  -- 删除外键
sql
DROP [TEMPORARY] TABLE [IF EXISTS] table_name [, table_name2...]

pt-online-schema-change 解决大表 DDL 操作锁表问题

如果线上表的数据量很大,进行 alter table 操作时耗时很长,可能导致锁表,锁表的原因简单来说有两点:

  • 使用 alter 操作修改表结构时,需要创建新表结构并复制原表数据,对大表而言,复制这个过程很耗时,而且会使用表级锁阻塞所有写操作,某些操作甚至会阻塞读取(如修改主键)
  • 即使是添加索引这类不需要重建表的操作,仍然会获取元数据锁这种表级锁来阻塞所有对该表的操作(包括 select

对于这种情况,可以使用 pt-online-schema-change 工具来解决,它的原理是复制过程不会使用表锁来阻塞操作,而是给原表加触发器来捕获 insert/update/delete 操作, 保证新表与旧表的数据同步。

索引操作

sql
# 创建主键约束/唯一约束等会自动隐式创建相关索引
create table book(
     book_id int,
     book_name varchar(100),
     info varchar(100),
     author varchar(20),
     # 显式为 book_name 字段创建普通索引
     index index_name(book_name),
     # 显式为 info 字段创建唯一索引
     unique index uk_idx(info),
     # 创建联合索引
     index mul_index_author_name(author,book_name)
);
sql
# 普通索引
alter table book add index idx_info(info);
# 唯一索引
alter table book add unique idx_id(book_id);
# 联合索引
alter table book add index mul_idx_author_info(author,info);
sql
# 在大量增删改操作之前可以先删除索引,可以增加执行效率,然后再重新创建索引
alter table book drop index idx_info;
# 或
drop index uk_idx on book;

DML

DML (Data Manipulation Language),数据库操作语言,用于增删改查数据(select 也被认为是 DQL)。

插入数据

sql
-- 基础插入(列顺序严格匹配)
INSERT INTO table_name VALUES (value1, value2, ...);

-- 指定列插入(推荐)
INSERT INTO employees (name, salary, hire_date)
VALUES ('李华', 28000.00, '2023-01-15');

-- 多行批量插入
INSERT INTO employees (name, dept_id)
VALUES
    ('张三', 101),
    ('李四', 102),
    ('王五', 101);

数据冲突时如何插入?

当我们插入一条新数据时,如果遇到主键冲突那么使用普通 insert 会插入失败的, 这时候可以使用 ON DUPLICATE KEY UPDATEREPLACE 来解决,不过它们两者有本质区别。

  1. REPLACE
sql
replace into users (id,username, email) 
values (1,'admin2', 'admin2@example.com');

REPLACE 本质是:当 insert 数据遇到冲突时,则会先 delete 旧数据再 insert 新数据(两个操作,不过由 MySQL 保证原子性)

因此自增ID会重新生成,可能导致 ID 改变,整行替换,需要包含所有字段,否则报错

  1. ON DUPLICATE KEY UPDATE
sql
insert into users (id,username,email)
-- 如果没冲突则 username 改为 admin5,email 改为 admin5@example.com
values (1,'admin5', 'admin5@example.com')
-- 如果冲突则更新 username 为 admin7,其他字段不变    
on duplicate key update username = 'admin7';

本质是:尝试 insert 新数据,当主键或唯一索引冲突时,则执行 update 操作,仅修改指定列的值,自增ID保持不变,性能比 REPLACE 更高,死锁概率更低。

更新数据

sql
-- 务必使用 WHERE 条件限定范围
UPDATE employees 
SET salary = salary * 1.1 
WHERE dept_id = 101;

-- 多列更新
UPDATE employees 
SET salary = salary * 1.1, 
    hire_date = '2023-01-15' 
WHERE dept_id = 101;

删除数据

sql
-- 按条件删除
DELETE FROM user
WHERE name = '张三';

-- 清空表(逐行删除,会触发事务)
DELETE FROM temp_table;

如果要清空表,使用 TRUNCATE TABLE 命令

  • 如果要清空表,不要使用 delete 删除,因为会触发事务,性能很低
  • 直接使用 truncate table 命令,直接清空物理文件,不触发事务(无法回滚)

DQL

DQL(Data Query Language),数据库查询语言,用于查询数据。DQL 是 SQL 最常用也是最复杂的部分。

基础查询语句结构如下:

sql
SELECT
    字段列表
FROM
    表名列表
WHERE
    条件列表
GROUP BY 
    分组字段列表
HAVING 
    分组后条件列表
ORDER BY 
    排序字段列表
LIMIT
    分页参数

selectfromjoinwheregroup byhavingorder bylimit 的执行顺序是什么?

  1. from:先确定数据源
  2. join:执行连接,结合多张表的数据(本质也是确定数据源)
  3. where:过滤不符合条件的行
  4. group by:将数据按指定的列分组
  5. having:过滤分组后的数据
  6. select:选择要返回的列
  7. order by:对查询结果进行排序
  8. limit:限制返回的行数

基础查询

sql
-- 不带任何条件查询所有数据
select * from user;

-- 指定字段
select id, name, age from user;

-- 指定别名
select name as username from user;

-- 去除重复记录
select distinct username from user;

条件查询

sql
-- 比较运算符
select name,age from user where age = 18;

-- 逻辑运算符
select * from user where name != '张三' and age > 18;

-- 范围匹配

select * from user where age between 18 and 25;

select * from user where age in (18,21,25);

select * from user where age not in (18,20);

-- 模糊匹配
select * from user where name like '张%'

-- 空值匹配
select * from user where adress is null;
select * from user where adress is not null;

聚合函数

MySQL 内置了聚合函数,用于对数据进行统计计算,常见的有 count()sum()avg()max()min()等。

sql
-- 求最大年龄
select max(age) from user;
-- 求平均年龄
select avg(age) from user;
-- 求最小年龄
select min(age) from user;
-- 求和
select sum(age) from user;
-- 求一共有多少用户
select count(*) from user;

count(*)count(1)count(字段名) 的区别

  • count(*) 会统计表中所有行的数量,包括 null
  • count(1)count(*) 几乎没差别,也会统计 null 值行,性能也没有差别
  • count(字段名) 只会统计指定字段不为 null 的行数,性能比上面两者差一点

分组查询

使用 group by 对查询进行分组,where 过滤满足条件的数据参与分组;having 对分组之后的结果进行过滤。

分组之后,查询的字段一般为聚合函数或分组字段,查询其他字段没有意义

sql
-- 对姓名进行分组,统计每个姓名的数量
select count(1) as cnt,username from user group by username;

排序查询

  • asc:升序(默认)
  • desc:降序
sql
-- 按年龄排序
select * from user order by age desc ;

分页查询

sql
select * from user limit 起始索引,查询记录数

如果查询的是第一页数据,起始索引可以省略,比如:

sql
-- 查询前 10 条数据
select * from user limit 10;

子查询

  • where 子查询
sql
-- 查询年龄大于平均年龄的用户
select * from user
where age > (select avg(age) from user);
  • from 子查询
sql
-- 查询结果包含 user 表和派生表的字段
select u.username,u.age,a.avg from user u
join (
    select avg(age) as avg from user
) a ;

DCL

DCL (Data Control Language),数据库控制语言,用于控制数据库的访问权限。

用户管理

MySQL 用户可以分为 普通用户root用户

  • 创建用户
sql
# 创建用户 zhang3 密码为 123456
create user 'zhang3'@'%' identified by '123456';

create user 'zhang3'@'localhost' identified by '123456';

mysql 库中的 user 表的主键是由 hostuser 组合的联合主键。% 标识任何主机都可以通过该用户登录,而 localhost 表示只能通过本机登录。

  • 查询用户
sql
select host,user from user;

创建用户.webp

sql
-- 注意这样会把所有 zhang3 用户都修改为 zhang4
update user set User = 'zhang4' where User = 'zhang3';
-- 可以指定 host 修改 zhang3
update user set User = 'zhang4' where User = 'zhang3' and Host = 'localhost';

-- 修改用户后需要刷新权限使其生效
flush privileges;
  • 删除用户
sql
drop user 'zhang3'@'localhost';
  • 修改密码
sql
-- 当前登录用户修改自己密码
alter user user() identified by '111111';
-- 或
set password = '123456';
  
-- root 用户也可直接修改其他用户密码
set password for 'zhang3'@'%' = '111111';
  • 设置密码过期
sql
-- root 用户可以直接让其他用户密码过期
ALTER USER 'zhang3'@'%' PASSWORD EXPIRE;
      
-- 全局设置所有用户密码过期时间180天
SET persist default_password_lifetime = 180;

-- 创建用户时设置密码过期策略  
create user 'li4'@'%' identified by '123456' password expire interval 90 DAY;

-- 修改已经创建的用户密码过期策略       
alter user 'li4'@'%' password expire interval 180 DAY;

权限管理

权限列表

我们主要关注数据库或表权限,需指定 ON database.*ON database.table

权限名描述
SELECT读取数据
INSERT插入数据
UPDATE更新数据
DELETE删除数据
CREATE创建新表/数据库
DROP删除表/数据库
ALTER修改表结构(如修改列)
INDEX创建/删除索引
REFERENCES外键约束权限(通常需要 ALTER
CREATE TEMPORARY TABLES创建临时表
CREATE VIEW创建视图
SHOW VIEW查看视图定义
TRIGGER操作触发器

权限赋予

sql
-- 赋予 mydb 下所有表的查询和更新权限给 zhang3
grant select,update on mydb.* to 'zhang3'@'%';

-- 赋予所有权限给 li4
grant all privileges on *.* to 'li4'@'%';

-- 赋予所有权限给 wang5,并允许 wang5 把自己权限赋予给其他用户
grant all privileges on *.* to 'wang5'@'%' with grant option;

访问控制

我们给用户配置了权限,那么 MySQL 是如何来判断用户是否有权限访问数据库或表呢?

  1. 当用户试图连接到 Mysql 服务器时,Mysql 首先会根据 user 表中的 hostuserauthentication_string 3 个字段来匹配用户。
    • 匹配成功,则接受连接
    • 匹配失败,则拒绝连接
  2. 一旦连接成功,对此连接上的每一个请求,服务器都会根据 userdbtable_privcolumn_priv 表来进行权限检查。
    • 先查找 user 表的权限
    • 如果没有匹配到,则查找 db 表的权限
    • 如果没有匹配到,则查找 table_priv 表的权限
    • 如果没有匹配到,则查找 column_priv 表的权限
    • 如果都没匹配到,则返回错误信息

角色管理

角色 是 MySQL 8.0 中引入的新功能,角色是权限的集合,直接赋予用户,更方便地管理用户权限

  • 创建角色
sql
create role 'manager'@'%';

create role 'boss'@'%';
  • 给角色赋权
sql
grant select,update on mydb.* to 'manager'@'%';

grant all privileges on *.* to 'boss'@'%';
  • 展示角色权限
sql
show grants for 'manager'@'%';

show grants for 'boss'@'%';
  • 回收角色权限
sql
revoke update on mydb.* from 'manager'@'%';
  • 删除角色
sql
drop role 'manager'@'%';
  • 赋予用户角色
sql
grant 'manager'@'%' to 'wang5'@'%';
  • 回收用户角色
sql
revoke 'manager'@'%' from 'wang5'@'%';
  • 为用户激活角色

为用户赋予了角色之后,不会立即生效,还需要激活角色,然后重新登录

sql
set default role 'manager'@'%' to 'wang5'@'%';

数据库三大范式是什么?

  • 第一范式:每个列的值都是原子值,表中的每个字段只能包含单一的数据项,不能出现重复的列和多值字段
  • 第二范式:在满足第一范式基础上,非主键字段依赖于整个主键
  • 第三范式:在满足第二范式基础上,所有非主键字段只能依赖于主键,不能相互依赖

数据库的三大范式是为了减少数据冗余,提高数据的完整性和一致性,使得表的设计更清晰。

不过实际业务的表设计通常都不会完全遵守三大范式,比如故意冗余部分字段,减少联表查询提升性能

MySQL 深分页的问题

当我们同样查询 10 条数据,但 LIMIT 1000000,10LIMIT 10 的执行速度完全不一样。因为 LIMIT 1000000,10 需要先读取并跳过 1000000 行数据,再读取 10 行数据,而 LIMIT 10 只需要读取 10 行数据。

LIMIT 10000000,10 这种情况的效率很差,也就是深分页问题,优化方式有三种:

  1. 子查询

select * from user where name = '张三' limit 99999999,10 这条查询语句可以优化为:

sql
select * from user where name = '张三'
and id >= (select id from user where name = '张三' order by id limit 99999999,1)
order by id limit 10;

name 有索引的情况下,这样的查询会直接扫描 name 的二级索引,二级索引数据量小,比直接扫描主键索引性能高,并且在子查询中能直接得到 id 不需要回表。

  1. 记录 id

每次分页都返回当前的最大 id,然后下次查询的时候带上这个 id 就可以利用 id > maxid 过滤,不过仅适合连续查询的情况。

  1. elasticsearch

利用搜索引擎来解决大数据量下的搜索性能问题

MySQL 中的 int(11) 的 11 表示什么?

最开始我以为 int(11) 表示该字段最多能存储 11 位的数据,但其实是错的。11 表示显示宽度,并不影响存储的大小或数值范围。

int 类型始终占 4 字节也就是 32 位, int(11) 表示如果数值的位数小于 11 位,比如 42 将显示为 0000000000042

所以 int(11) 中的数字只是显示格式的定义,在 MySQL 8.0 中显示宽度被正式弃用

VARCHAR(100) 和 VARCHAR(10) 有什么区别

int(n) 不同的是,varchar(100) 中的数字表示能存储的字符串长度上限

varchar(10)varchar(100)存储相同字符串时占用的空间是一样的,除了存储字符本身外,还需要额外的 1 或 2 个字节存储字符串的长度。

虽然两者存储空间一样,但在排序查询时,varchar(100) 占用的内存会更多

char 和 varchar 有什么区别?

charvarchar 都表示字符串类型,char 表示定长字段;varchar 表示可变长度的字段。

理论上来说 char 会比 varchar 快,因为 varchar 长度不固定,处理需要多一次运算,但实际上这种运算耗时微乎其微, 而固定大小在很多场景下浪费空间(即使存储字段长度小于固定长度也会填充),所以业务上推荐使用 varchar

DATETIME 和 TIMESTAMP 有什么区别?

  • DATETIME:以字符串形式存储,占用 8 个字节,不受时区影响,范围为 1000-01-01 00:00:009999-12-31 23:59:59
  • TIMESTAMP:以 UNIX 时间戳形式存储,占用 4 个字节,受时区影响,范围为 1970-01-01 00:00:002038-01-19 03:14:07

TIMESTAMP 可以有默认的当前时间戳 CURRENT_TIMESTAMP,并且支持在行更新时自动更新为当前时间戳,更适合记录行的创建和修改时间。

inner join 和 left join 及 right join 的区别

  • inner join:只返回两个表中匹配的行,如果没有匹配,则该行不会出现在结果集中
  • left join:返回左表中的所有行,即使右表没有匹配,右表对应列会显示为 NULL
  • right join:返回右表中的所有行

什么是数据库的视图

数据库的视图是一个虚拟表,它并不存储实际的数据,而是通过查询其他表的数据来生成的。

视图可以将复杂的查询封装为简单的视图,使得用户在查询数据时更加方便。

为什么阿里手册禁用存储过程

  • 可移植性差:存储过程是在数据库服务器上执行的,会导致应用程序的数据库依赖增加,难以迁移到其他数据库系统
  • 调试困难:相对应用层代码,特别是复杂业务场景,难以调试定位问题
  • 维护复杂:存储过程与应用代码分离,增加维护成本