目录
查询语句中的各类关键字执行优先级为:from → where → select → group by → having → order by
varchar(255)表示最多存储255个字符,而非字节,
对于 DECIMAL(m,n) 和 NUMERIC(m,n) 类型,精度m指的是一个数可以拥有的总位数,而标度n是指小数点后面可以拥有的位数。
定点数 (Fixed-point)
浮点数 (Floating-point)
CREATE TABLE docpreview.file_info (
id INTEGER(16) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
file_name VARCHAR(50) COMMENT '文件名',
file_path VARCHAR(50) COMMENT '文件路径',
file_type VARCHAR(10) COMMENT '文件类型,后缀',
file_size INTEGER(16) COMMENT '文件大小,字节',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
modify_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
启动mysql:service mysql start
修改远程连接时用的密码(可以与"本机连接"设置为相同的)
alter user 'root'@'%' identified with mysql_native_password by '新密码';修改本机连接时用的密码(可以与"远程连接"设置为相同的)
alter user 'root'@'localhost' identified with mysql_native_password by '新密码';
是一个特殊的系统数据库,用于存储MySQL服务器自身的元数据(metadata)。这个数据库包含了关于MySQL服务器运行所需的信息,比如用户账号信息、权限设置、全局变量以及一些系统表等。以下是“mysql”数据库中几个重要的表:
user 表:存储了MySQL用户的用户名、密码以及其他授权信息。每个MySQL用户的信息都会在这个表中有记录。
db 表:用于存储每个用户对不同数据库的访问权限。包括了用户可以访问哪些数据库以及他们在这些数据库上的权限级别。
tables_priv 表:存储了用户对特定表的访问权限。记录了用户是否可以访问某个特定表及其相应的权限。
columns_priv 表:存储了用户对特定表中列的访问权限。记录了用户是否可以访问某个特定表中的特定列。
procs_priv 表:存储了用户对存储过程和函数的访问权限。记录了用户是否可以调用某个特定的存储过程或函数。
tables 表:存储了表的定义信息。不同于上面提到的权限表,此表包含的是表的结构定义。
columns 表:存储了列的定义信息。包含了每个列的数据类型、默认值等详细信息。
global_variables 表:存储了MySQL服务器的全局变量设置。
time_zone 表:存储了MySQL服务器支持的不同时区信息。
performance_schema 数据库
sys 数据库
information_schema 数据库
with toronto_ppl as (
SELECT DISTINCT name
FROM population
WHERE country = "Canada"
AND city = "Toronto"
), avg_female_salary as (
SELECT AVG(salary) as avgSalary
FROM salaries
WHERE gender = "Female")
SELECT name, salary
FROM People
WHERE name in (SELECT DISTINCT FROM toronto_ppl)
AND salary >= (SELECT avgSalary FROM avg_female_salary)
CREATE TEMPORARY FUNCTION get_seniority(tenure INT64) AS (
CASE WHEN tenure < 1 THEN "analyst"
WHEN tenure BETWEEN 1 and 3 THEN "associate"
WHEN tenure BETWEEN 3 and 5 THEN "senior"
WHEN tenure > 5 THEN "vp"
ELSE "n/a"
END
);
SELECT name, get_seniority(tenure) as seniority
FROM employees
从最优到最差为:system > const > eq_ref > ref > range > index > ALL。一般来说,得保证查询达到range级别,最好达到ref级别。
语法:SELECT column1 FROM t1 WHERE [conditions] and EXISTS (SELECT * FROM t2 WHERE column2 = column1)
说明:官方连接
EXISTS子句中的子查询不会返回具体的查询到的语句,只是会返回true或false,如果外层sql的字段在子查询中存在则返回true,不存在返回false
即使子查询的结果是null,只有对应字段是存在的(如果子查询返回至少一行结果),子查询就会返回true
执行过程:
exists和in查询原理的区别
结论
Show profile是mysql提供的用来分析当前sql语句执行的资源消耗情况的工具。在执行完select语句后,执行show profile语句,只显示最近发送给服务器的sql语句,默认是已执行的15条,可以通过peofiling_history_size重新设置,最大值为100
select @@have_profiling:查看mysql版本是否支持show profile工具
show variables like 'profliling':查看是否开启show profile工具
show profile使用及查询参数:
SHOW PROFILE [type [,type]...]
[FOR QUERY query_id]
[LIMIT row_count [OFFSET offset]]
type参数:
-- 创建一个普通索引(方式①)
create index 索引名 ON 表名(列名(索引键长度)[ASC|DESC]);
-- 创建一个普通索引(方式②)
alter table 表名 add index 索引名(列名(索引键长度)[ASC|DESC]);
-- 创建一个普通索引(方式③)
CREATE TABLE tableName(
columnName1 INT(8) NOT NULL,
columnName2 ....,
.....,
index [索引名称](列名(长度))
);
-- 后续其他类型的索引都可以通过这三种方式创建
-- 创建一个唯一索引
create unique 索引名 ON 表名(列名(索引键长度)[ASC|DESC]);
-- 创建一个主键索引
alter table 表名 add primary key 索引名(列名);
-- 创建一个全文索引
create fulltext index 索引名ON表名(列名);
-- 创建一个前缀索引
create index 索引名ON表名(列名(索引键长度));
-- 创建一个空间索引
alter table表名add spatial key 索引名(列名);
-- 创建一个联合索引
create index 索引名 ON 表名(列名1(索引键长度),列名2,...列名n);
-- 查看一张表上的所有索引
show index from 表名;
-- 删除一张表上的某个索引
drop index 索引名 on 表名;
-- 强制指定一条SQL走某个索引查找数据
select * from 表名 force index(索引名)where.....;
-- 使用全文索引(自然搜索模式)
select * from 表名 where match(索引列) against('关键字');
-- 使用全文索引(布尔搜索模式)
select * from 表名 where match(索引列) against('布尔表达式'inboolean mode);
-- 使用全文索引(拓展搜索模式)
select * from 表名 where match(索引列) against('关键字'with query expansion);
-- 分析一条SQL是否命中了索引
explain select * from 表名 where 条件....;
数据结构层次:
字段数量层次:
功能逻辑层次:
存储方式层次:
主键索引一般选用有序的自增值,保证插入数据时的性能,避免数据移动
如果表中存在主键索引或聚簇索引,对其他字段建立的索引,都是次级索引,也被称为辅助索引,其节点上的值,存储的并非一条完整的行数据,而是指向聚簇索引的索引字段值,拿到主键值再去查完整的记录(回表)。
同时,除开上述一些建立索引的原则外,在建立索引时还需有些注意点:
❶值经常会增删改的字段,不合适建立索引,因为每次改变后需维护索引结构。
❷一个字段存在大量的重复值时,不适合建立索引,比如之前举例的性别字段。
❸索引不能参与计算,因此经常带函数查询的字段,并不适合建立索引。
❹一张表中的索引数量并不是越多越好,一般控制在3,最多不能超过5。
❺建立联合索引时,一定要考虑优先级,查询频率最高的字段应当放首位。
❻当表的数据较少,不应当建立索引,因为数据量不大时,维护索引反而开销更大。
❼索引的字段值无序时,不推荐建立索引,因为会造成页分裂,尤其是主键索引。
SQL是否走联合索引查询跟where后的条件顺序无关,因为MySQL优化器会优化,对SQL查询条件进行重排序
查询中带有OR会导致索引失效
模糊查询中like以%开头导致索引失效
字符类型查询时不带引号导致索引失效(发生类型转换)
索引字段参与计算导致索引失效
字段被用于函数计算导致索引失效
违背最左前缀原则导致索引失效(必须包含最左边的字段)
不同字段值对比导致索引失效(user_name=user_addr)
反向范围操作导致索引失效(NOT IN、NOT LIKE、IS NULL、IS NOT NULL、!=、<>)
索引覆盖:要查询的列,在使用的索引中已经包含,被所使用的索引覆盖,这种情况称之为索引覆盖。(查询的就是索引字段,自然就不需要回表)
索引下推:在存储引擎层面进行数据筛选工作,而非在服务层。将Server层筛选数据的工作,下推到引擎层处理。对于user_name LIKE "竹%" AND user_sex="男"来说,联合索引查询条件就是竹x男,在索引扫描的时候就对user_name和user_sex进行筛选,如果没有索引下推,通过竹x男可能找到两条记录,而通过user_sex筛选后,也许就剩一条记录,减少一次回表
MRR(Multi-Range Read)机制:MRR机制中,对于辅助索引中查询出的ID,会将其放到缓冲区的read_rnd_buffer中,然后等全部的索引检索工作完成后,或者缓冲区中的数据达到read_rnd_buffer_size大小时,此时MySQL会对缓冲区中的数据排序,从而得到一个有序的ID集合:rest_sort,最终再根据顺序IO去聚簇/主键索引中回表查询数据。(将在同一页的记录的主键先汇集,然后一次回表查询同一页的记录)
Index Skip Scan索引跳跃式扫描:对于联合而索引没有使用第一个字段,优化器把所有第一个字段的值去重后加在SQL语句中,使得可以走联合索引
ACID主要涵盖四条原则,即:
事务回滚:
回滚到事务点后不代表着事务结束了,只是事务内发生了一次回滚,如果要结束当前这个事务,还依旧需要通过commit|rollback;命令处理。
MySQL事务的隔离机制,默认为第三级别:Repeatable read可重复读
脏读、幻读、不可重复读问题
事务的四大隔离级别,数据库不同的事务隔离级别,是基于不同类型、不同粒度的锁实现的
MVCC机制则会基于表数据的快照创建一个ReadView,然后读取原本表中上一次提交的老数据。然后等事务A提交之后,事务B再次读取数据,此时MVCC机制又会创建一个新的ReadView,然后读取到最新的已提交的数据,此时事务B中两次读到的数据并不一致,因此出现了不可重复读问题。
事务隔离机制的命令:
-- 方式①:查询当前数据库的隔离级别
SELECT @@tx_isolation;
-- 方式②:查询当前数据库的隔离级别
show variables like '%tx_isolation%';
-- 设置隔离级别为RU级别(当前连接生效)
set transaction isolation level read uncommitted;
-- 设置隔离级别为RC级别(全局生效)
set global transaction isolation level read committed;
-- 设置隔离级别为RR级别(当前连接生效)
-- 这里和上述的那条命令作用相同,是第二种设置的方式
set tx_isolation = 'repeatable-read';
-- 设置隔离级别为最高的serializable级别(全局生效)
set global.tx_isolation = 'serializable';
如果想要让设置的隔离级别在全局生效,一定要记得加上global关键字,否则生效范围是当前会话,也就是针对于当前数据库连接有效,在其他连接中依旧是原本的隔离级别。
MySQL默认开启事务的自动提交,并且将一条SQL视为一个事务
redo-log是一种WAL(Write-ahead logging)预写式日志,在数据发生更改之前会先记录日志,也就是在SQL执行前会先记录一条prepare状态的日志,然后再执行数据的写操作。
以锁粒度的维度划分:
以互斥性的维度划分:
以操作类型的维度划分:
以加锁方式的维度划分:
以思想的维度划分:
实际只有共享锁(S锁)和排它锁(X锁),只是加锁的地方不同(行,表,页)
不同的存储引擎的表锁在使用方式上也有些不同,比如InnoDB是一个支持多粒度锁的存储引擎,它的锁机制是基于聚簇索引实现的,当SQL执行时,如果能在聚簇索引命中数据,则加的是行锁,如无法命中聚簇索引的数据则加的是表锁
redo-log主要用来实现数据的恢复
MySQL启动后就会在内存中创建一个BufferPool,运行过程中会将大量操作汇集在内存中进行,比如写入数据时,先写到内存中,然后由后台线程再刷写到磁盘。如果出现宕机,则重启的时候通过redo-log回复数据
工作线程执行SQL前,写的Redo-log日志,也是写在了内存中的redo_log_buffer缓冲区。
Redo-log的刷盘策略
刷盘的时机由innodb_flush_log_at_trx_commit参数来控制
主要是记录所有对数据库表结构变更和表数据修改的操作,对于select、show这类读操作并不会记录
写bin-log日志时,也会先写缓冲区,然后由后台线程去刷盘。
bin-log日志的刷盘策略则可以通过sync_binlog参数控制
前面分析的两种日志缓冲区,都位于InnoDB创建的共享BufferPool中,而bin_log_buffer是位于每条线程中的,关系图如下:

bin_log_buffer的设计,就类似于ThreadLocal线程变量副本。
Redo-log的两阶段提交
写SQL执行流程,第⑤、⑩步,分别会写两次Redo-log日志

如果redo-log只写一次,那不管谁先写,都有可能造成主从同步数据时的不一致问题出现,为了解决该问题,redo-log就被设计成了两阶段提交模式,设置成两阶段提交后,整个执行过程有三处崩溃点:
通过这种两阶段提交的方案,就能够确保redo-log、bin-log两者的日志数据是相同的,bin-log中有的主机再恢复,如果bin-log没有则直接回滚主机上写入的数据,确保整个数据库系统的数据一致性。
error-log:MySQL线上MySQL由于非外在因素(断电、硬件损坏...)导致崩溃时,辅助线上排错的日志。
slow-log:系统响应缓慢时,用于定位问题SQL的日志,其中记录了查询时间较长的SQL。
set global long_query_time = 1;general log即查询日志,MySQL会向其中写入所有收到的查询命令,如select、show。general_log:是否开启查询日志,默认OFF关闭。
relay-log:搭建MySQL高可用热备架构时,用于同步数据的辅助日志。当主机的增量数据被复制到中继日志后,从机的线程会不断从relay-log日志中读取数据并更新自身的数据
MERGE语句(也称为"upsert")是SQL中一个强大的操作,它允许在单个原子操作中执行插入、更新或删除操作。不同数据库系统对MERGE的实现有所差异,下面我将详细介绍MERGE的概念和各主要数据库的实现区别。
MERGE语句基本概念:MERGE语句的核心思想是:"如果记录存在则更新,不存在则插入"。它通常包含以下几个关键部分:
基本形式
MERGE INTO 目标表 [AS] 目标别名
USING 源表或子查询 [AS] 源别名
ON (匹配条件)
[WHEN MATCHED [AND 额外条件] THEN 操作]
[WHEN NOT MATCHED [BY TARGET] [AND 额外条件] THEN 操作]
[WHEN NOT MATCHED BY SOURCE [AND 额外条件] THEN 操作];
-- 示例(操作的始终是目标表)
MERGE INTO customer_accounts t
USING (
SELECT customer_id, new_balance, is_active
FROM transaction_updates
WHERE transaction_date > CURRENT_DATE - 7
) s
ON (t.customer_id = s.customer_id)
-- 当目标表和源数据匹配(customer_id相同)且源数据的new_balance与目标表的balance不相等时
-- 更新目标表的balance和last_updated字段
WHEN MATCHED AND s.new_balance != t.balance THEN
UPDATE SET
t.balance = s.new_balance,
t.last_updated = SYSDATE
-- 当目标表和源数据匹配且源数据的is_active标志为0时
-- 从目标表中删除该记录
WHEN MATCHED AND s.is_active = 0 THEN
DELETE
-- 当源数据中的记录在目标表中找不到匹配时(即新客户)
-- 向目标表插入新记录
WHEN NOT MATCHED THEN
INSERT (customer_id, balance, open_date)
VALUES (s.customer_id, s.new_balance, SYSDATE)
-- 当目标表中的记录在源数据中找不到匹配且目标表记录的最后更新时间超过365天
-- 从目标表中删除这些记录
WHEN NOT MATCHED BY SOURCE AND t.last_updated < SYSDATE - 365 THEN
DELETE;
WHEN MATCHED,表示两表中都有,执行更新WHEN NOT MATCHED BY SOURCE表示目标中有,源表中没有,执行删除WHEN NOT MATCHED BY TARGET表示目标中没有,源表中有,执行插入| 目标表(Target) | 源表(Source) | 匹配类型 | 执行操作 |
|---|---|---|---|
| 记录1 | 记录1 | WHEN MATCHED | UPDATE |
| 记录2 | - | WHEN NOT MATCHED BY SOURCE | DELETE |
| - | 记录3 | WHEN NOT MATCHED [BY TARGET] | INSERT |
WHEN NOT MATCHED BY TARGET:意味着目标中匹配不上,目标中没有,源表中有WHEN NOT MATCHED BY SOURCE:意味着是源表中匹配不上,目标中有,源表没有-- sql server
MERGE INTO target_table AS target
USING source_table AS source
ON (target.id = source.id)
WHEN MATCHED THEN
UPDATE SET target.name = source.name, target.value = source.value
WHEN NOT MATCHED THEN
INSERT (id, name, value) VALUES (source.id, source.name, source.value)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
-- oracle
MERGE INTO target_table target
USING (SELECT id, name, value FROM source_table) source
ON (target.id = source.id)
WHEN MATCHED THEN
UPDATE SET target.name = source.name, target.value = source.value
WHEN NOT MATCHED THEN
INSERT (id, name, value) VALUES (source.id, source.name, source.value);
-- pg
INSERT INTO target_table (id, name, value)
SELECT id, name, value FROM source_table
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name, value = EXCLUDED.value;
-- mysql
-- 方式1: INSERT ... ON DUPLICATE KEY UPDATE
INSERT INTO target_table (id, name, value)
VALUES (1, 'test', 100)
ON DUPLICATE KEY UPDATE
name = VALUES(name), value = VALUES(value);
-- 方式2: 使用REPLACE语句(实际是先DELETE后INSERT)
REPLACE INTO target_table (id, name, value)
VALUES (1, 'test', 100);
-- sqlite
INSERT INTO target_table (id, name, value)
VALUES (1, 'test', 100)
ON CONFLICT(id) DO UPDATE
SET name = excluded.name, value = excluded.value;
select a left join b on a.id = b.id and b.age = 18;
select a left join b on a.id = b.id where b.age = 18;
a 中的所有行,连接条件中决定 b 中有多少行能满足条件,只作用在 b 表上,b 中没有满足的条件的记录会导致 b的字段为 nullON 条件中的过滤:在 JOIN 时过滤,不影响左表记录数WHERE 条件中的过滤:在 JOIN 后过滤,会减少结果集记录数select * from a WHERE id NOT IN (SELECT a_id FROM table_b) 只要子查询里有一个 NULL,结果就是全表无数据。NULL 在 SQL 中表示"未知"或"缺失"NULL 与任何值的比较结果都是 UNKNOWN,包括与 NULL 自己比较WHERE id NOT IN (2, 3, NULL)WHERE id != 2 AND id != 3 AND id != NULLid != NULL 的结果是 NULL(不是 true 或 false)true AND true AND NULLNULLWHERE 子句中,只有 true 才满足条件false 和 NULL 都被视为不满足SELECT * FROM table_a
WHERE id NOT IN (
SELECT a_id FROM table_b
WHERE a_id IS NOT NULL -- 关键!
);
-- NOT EXISTS 正确处理 NULL
SELECT * FROM table_a a
WHERE NOT EXISTS (
SELECT 1 FROM table_b b
WHERE b.a_id = a.id
);
-- 使用 LEFT JOIN 和 NULL 检查
SELECT a.*
FROM table_a a
LEFT JOIN table_b b ON a.id = b.a_id
WHERE b.a_id IS NULL;