参考1:SQL 函数(菜鸟教程)
参考2:SQL 函数(w3cschool)
这里所说的函数基本上都是指的是SQL的内建函数。
有用的聚合函数:
SQL标量函数:
SQL标量函数基于输入值,返回一个单一的值。有用的标量函数:
本次示例由GPT3.5提供。
DELIMITER //
CREATE PROCEDURE procedure_name([parameter_list])
[characteristics]
BEGIN
-- 存储过程的主体,包含一系列 SQL 语句和逻辑
END;
//
DELIMITER;
DELIMITER //
和DELIMITER;
:这两个命令用于指定存储过程的分隔符。在存储过程主体中,可以使用分号作为SQL语句的结束标志,但是在存储过程定义中使用DELIMITER命令来指定其他分隔符,以防止分号干扰存储过程的语法。最后,使用DELIMITER; 恢复默认分隔符。CREATE PROCEDURE procedure_name
:这是创建存储过程的语句,其中procedure_name
是为存储过程指定的名称。parameter_list
:参数列表包含传递给存储过程的输入参数。每个参数由名称、数据类型和方向(IN、OUT 或 INOUT)组成。characteristics
:可以在这里指定存储过程的特性,如语言、安全性、事务性等。这部分是可选的。BEGIN和END
:存储过程的主体,包含一系列SQL语句和逻辑。你可以在这里编写查询、条件判断、循环等操作。DELIMITER //
CREATE PROCEDURE example_procedure(IN param1 INT, OUT param2 VARCHAR(50))
BEGIN
-- 存储过程主体
DECLARE total_users INT;
SELECT COUNT(*) INTO total_users FROM users;
IF total_users > param1 THEN
SET param2 = 'High';
ELSE
SET param2 = 'Low';
END IF;
END;
//
DELIMITER;
CALL example_procedure(100, @result);
SELECT @result;
本次示例由GPT3.5提供。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL
);
DELIMITER //
CREATE PROCEDURE get_user_count()
BEGIN
DECLARE user_count INT;
SELECT COUNT(*) INTO user_count FROM users;
SELECT user_count;
END;
//
DELIMITER;
首先使用DELIMITER
命令将分隔符设置为//
,以便在存储过程中使用分号。然后,我们创建了一个名为get_user_count
的存储过程。在存储过程中,我们使用DECLARE
声明了一个局部变量user_count
,用于存储查询结果。
在存储过程的主体中,我们使用SELECT COUNT(*) INTO user_count
查询 users 表中的用户数量,并将结果存储在user_count
变量中。然后,我们使用SELECT user_count
返回存储过程的结果。最后,我们使用DELIMITER;
将分隔符恢复为默认的分号。
CALL get_user_count();
参考1:SQL触发器
参考2:Oracle 触发器详解(trigger)
触发器(trigger)是个特殊的存储过程,它的执行不是由程序调用,也不是手工启动,而是由事件来触发,比如当对一个表进行操作( insert
,delete
, update
)时就会激活它执行。触发器经常用于加强数据的完整性约束和业务规则等。
触发器可以从DBA_TRIGGERS
,USER_TRIGGERS
数据字典中查到。
触发器的处理是可以任意的事情,包括修改目标表的数据,也可以报一个异常,终止数据的处理。
创建触发器:
create [or replace] trigger 触发器名
触发时间 {before | after} -- view 中是 instead of
触发事件 {insert | update | delete} -- dml、ddl、database
on 触发对象 -- table、view、schema、database
触发频率 {for each row} -- 行级触发器。默认:语句级触发器
[follows 其它触发器名] -- 多个触发器执行的 前后顺序
[when 触发条件]
begin
pl/sql 语句;
end;
删除触发器:
DROP TRIGGER 触发器名称;---删除触发器
create table emp_tagx as select * from emp; --创建测试表
--加工资
create or replace trigger tg_emp_change
before update or insert or delete on emp_tagx ---相关的行为发生,都会执行这个触发器
for each row ---发生变化的每一行|增量
begin ---处理逻辑
dbms_output.put_line('修改前工资:'||:old.sal); ---打印修改前的工资 :old专用的,固定写法 专指修改前的数据
dbms_output.put_line('修改后工资:'||:new.sal); ---打印修改后的工资 :new专用的,固定写法 专指修改后的数据
----insert into target_sal(:old.empno,:old.sal,:new.sal);
---commit;
---每次加工资不能超过2000,否则就失败
if :new.sal - :old.sal >2000 then
raise_application_error(-20001,'加工资步子不要太大'); --报错
end if ;
end;
update emp_tagx set sal = sal+ 2000 where empno=7788;
触发事件
startup:'数据库打开'时,相反的 = shutdown
logon :当用户连接到数据库并 '建立会话' 时,相反的 = logoff
servererror:发生服务器错误时
示例:
create or replace trigger scott.tr_al_database_login_info
after logon on database
declare
v_option_user varchar2(50) := sys_context('USERENV', 'OS_USER'); -- 电脑域账户
begin
insert into scott.database_login_info
(client_ip,
login_user,
database_name,
database_event,
create_user,
create_data)
values
(dbms_standard.client_ip_address,
dbms_standard.login_user,
dbms_standard.database_name,
dbms_standard.sysevent,
v_option_user,
sysdate);
end;
-- 创建数据表 users
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL
);
DELIMITER //
是重新定义sql语句的结束符。-- 1 创建阻止插入用户名为admin的触发器
DELIMITER //
CREATE TRIGGER tr_user_admin_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
IF NEW.username = 'admin' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '用户名 "admin" 不允许插入。';
END IF;
END;
//
DELIMITER;
-- 2 创建阻止修改用户名为admin的触发器
DELIMITER //
CREATE TRIGGER tr_user_admin_update
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
IF old.username = 'admin' THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '用户名 "admin" 不允许修改。';
END IF;
END;
//
DELIMITER;
-- 这将触发触发器并阻止插入操作
INSERT INTO users (username, email) VALUES ('admin', '[email protected]');
-- 这将成功插入记录
INSERT INTO users (username, email) VALUES ('john', '[email protected]');
-- 阻止修改的测试
UPDATE users SET email = '123' WHERE id = 1;
参考1:MySQL的视图 具体更多的视图使用示例可以看参考文章。
介绍:
作用:
简化代码,可以把重复使用的查询封装成视图重复使用,同时可以使复杂的查询易于理解和使用。
安全原因,如果一张表中有很多数据,很多信息不希望让所有人看到,此时可以使用视图视,如:社会保险基金表,可以用视图只显示姓名,地址,而不显示社会保险号和工资数等,可以对不同的用户,设定不同的视图。
CREATE [OR REPLACE] [algorithm = {UNDEFINED| MERGE | TEMPTABLE}]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]
参数说明:
(1)algorithm:可选项,表示视图选择的算法。
(2)view_name :表示要创建的视图名称。
(3)column_list:可选项,指定视图中各个属性的名词,默认情况下与SELECT语句中的查询的属性相同。
(4)select_statement
:表示一个完整的查询语句,将查询记录导入视图中。
(5)[WITH [CASCADED | LOCAL] CHECK OPTION]:可选项,表示更新视图时要保证在该视图的权限范围之内。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL
);
INSERT INTO `users`(`id`, `username`, `email`) VALUES (1, 'admin', '[email protected]');
CREATE OR REPLACE VIEW view1_emp
AS
SELECT id,username,email from users;
show full tables;
SELECT * FROM view1_emp;
格式:
alter view 视图名 as select语句
示例:
alter view view1_emp
as
select a.deptno,a.dname,a.loc,b.ename,b.sal from dept a, emp b where a.deptno = b.deptno;
参考1:MySQL数据库的设计及性能优化
数据库优化
union all
代替union
、避免类型转换、选择合适的索引类型、从全局出发优化,而不是片面调整等等。数据库架构优化
IO
压力。其他优化
参考1:超详细的mysql分库分表方案
MySQL一般安装部署在Linux操作系统上(例如CentOS 7.4),默认都是InnoDB存储引擎,且开启了独立表空间选项(参数innodb_file_per_table=1),此时创建一个表orders
就会自动生成一个数据文件orders.ibd
,文件大小是受操作系统Block
大小限制的,下面是ext3
文件系统块大小和最大尺寸的对应关系。
操作系统块大小 | 最大文件尺寸 | 最大文件系统尺寸 |
---|---|---|
1KB | 16GB | 2TB |
2KB | 256GB | 8TB |
4KB | 2TB | 16TB |
8KB | 16TB | 32TB |
查看操作系统页大小及块大小
这就说明MySQL
单表的最大尺寸不能超过2TB,我们简单来算一下,假设一个表的平均行长度为32KB(InnoDB最大行长度限制65536字节,64KB),那么他最大能存储多少行数据?4 x 1024 x 1024 x 1024 / 32 = 134217728
大约 1.4 亿不到。
分表的应用场景是单表数据量增长速度过快,影响了业务接口的响应时间,但是 MySQL 实例的负载并不高,这时候只需要分表,不需要分库(拆分实例)。
垂直分表、水平分表、按月分表、MySQL分区表。
一个表大小是满足如下公式的:TABLE_SIZE = AVG_ROW_SIZE x ROWS
,从这里可以知道表太大,要么是平均行长度太大,也就说表的字段太多,要么是表的记录数太多。这就产生两种不同的分表方案,即切分字段(垂直分表)和切分记录(水平分表)。
orders
有2000w数据,根据业务的增长,估算一年之后会达到1亿,同时参考阿里云RDS for MySQL
的最佳实践,单表不建议超过500w,1亿数据分20个子表就够了。
- 问题来了,按照什么来拆分呢?主键id还是用户的user_id?
按主键ID拆分数据很均匀,但是通过ID查询orders的场景几乎没有,业务访问orders大部分场景都是根据user_id来过滤的,而且user_id的唯一性又很高(一个user_id对应的orders表记录不多,选择性很好),按照user_id来作为Sharding key能满足大部分业务场景,拆分之后每个子表数据也比较均匀。- 这样就将orders表拆分成20个子表,对应到InnoDB的存储上就是20个数据文件(orders_0.ibd,orders_1.ibd等),这时候执行SQL语句
select order_id, order_sn, source from orders where user_id = 1001;
就能很快的定位到要查找记录的位置是在orders_1
,然后做查询重写,转化为SQL语句select order_id, order_sn, source from orders_01 where user_id = 1001
,这种查询重写功能很多中间件都已经实现了,常用的就是sharding-sphere
或者sharding-jdbc
都可以实现。
按月分表
对于账务或者计费类系统,每天晚上都会做前一天的日结或日账任务,每月的1号都会做月结或月账任务,任务执行完之后相关表的数据都已静态化了(业务层不需要这些数据),根据业务的特性,可以按月创建表,比如对于账单表 bills,就可以创建按月分表(十月份表bills_202010,202011十一月份表),出完月账任务之后,就可以归档到历史库了,用于数据仓库ETL来做分析报表,确认数据都同步到历史库之后就可以删除这些表释放空间。
MySQL分区表
详见超详细的mysql分库分表方案
MySQL的高可用架构大多都是一主多从,所有写入操作都发生在Master上,随着业务的增长,数据量的增加,很多接口响应时间变得很长,经常出现Timeout,而且通过升级MySQL实例配置已经无法解决问题了,这时候就要分库,通常有两种做法:按业务拆库
和按表分库
,下面就介绍这两种分库方案啦。
按业务分库
举个例子,交易系统trade数据库单独部署在一台RDS实例,现在交易需求及功能越来越多,订单,价格及库存相关的表增长很快,部分接口的耗时增加,同时有大量的慢查询告警,升级RDS配置效果不大,这时候就需要考虑拆分业务,将库存,价格相关的接口独立出来。
这样按照业务模块拆分之后,相应的trade数据库被拆分到了三个RDS实例中,数据库的写入能力提升,服务的接口响应时间也变短了,提高了系统的稳定性。
按表分库
上面介绍了分表方案,常见的有垂直分表和水平分表(拆分后的子表都在同一个 RDS 实例中存储),对应的分库就是垂直分库和水平分库,这里的分库其实是拆分 RDS 实例,是将拆分后的子表存储在不同的 RDS 实例中,垂直分库实际业务用的很少,就不介绍了,主要介绍下水平分库。
举个例子,交易数据库的订单表orders有2亿多数据,RDS实例遇到了写入瓶颈,普通的insert都需要50ms,时常也会收到CPU使用率告警,这时就要考虑分库了。根据业务量增长趋势,计划扩容一台同配置的RDS实例,将订单表orders拆分20个子表,每个RDS实例10个。
这样解决了订单表orders太大的问题,查询的时候要先通过分区键user_id定位是哪个RDS实例,再定位到具体的子表,然后做 DML操作,问题是代码改造的工作量大,而且服务调用链路变长了,对系统的稳定性有一定的影响。其实已经有些数据库中间件实现了分库分表的功能,例如常见的mycat,阿里云的DRDS等。
通过上面的分表和分库方案的介绍,主要会遇到下面三类问题:
详见超详细的mysql分库分表方案
来自GPT3.5的回答。
MySQL默认的存储引擎InnoDB
是使用B+树(B-Plus Tree) 数据结构来实现索引的。无论是主键索引还是辅助索引(非主键索引),InnoDB存储引擎都是使用B+树数据结构。B+树在InnoDB中用于实现高效的数据存储和检索,特别适用于处理大量数据和范围查询。
但需要注意的是,MySQL支持多种存储引擎,不同的存储引擎可以使用不同的数据结构。例如,MyISAM存储引擎使用的是B树数据结构实现索引。因此,具体使用哪种数据结构取决于所选的存储引擎。
参考1:深入理解MySQL索引底层数据结构
本质上还是一个二叉树,叫做自平衡二叉树。平衡树在插入和删除的时候,会通过旋转操作
将树的左右节点达到平衡。
红黑树的定义(规则):
红黑树通过这些规则来保持树的平衡,从而确保在最坏情况下(比如所有插入都是递增或递减的)也能保持 O(log n) 的操作复杂度。
若采用红黑树作为索引的数据结构,当数据量特别大的时候,黑红树的高度就特别大了。假如有500w条数据,那么红黑树的高度可能达到几十甚至更多,要经过大量的磁盘I/O操作,性能太差,要解决这一问题,就要减少I/O的次数。
参考1:数据结构-B树
参考2:B树详解
B树的定义:
B树,又称多路平衡查找树,B树中所有结点的孩子个数的最大值称为B树的阶,通常用m表示。 一棵m阶B树或为空树,或为满足如下特性的m叉树:
B树的优点:
来自GPT3.5的回答。
为什么B树要保持平衡?
答:为了保证查找效率。因为B树是需要访问磁盘,树的层数越高,则访问磁盘的次数也就越多,而访问磁盘是比较慢、比较费时间的。所以为了保证查找效率,在构建B树的时候,会对其高度进行限制,尽可能地让其保持平衡。
怎么保持B树的平衡?
答:回忆下AVL(二叉平衡树)中是这么要求的,左右两棵子树的高度差不超过1,即平衡因子不超过1。但是对于B树而言,高度差不超过1,太难为它了,毕竟有那么多的子树,因此,做出了一个简单粗暴的决定,规定在 m 叉查找树(B树)中,对于任何一个节点,它所有子树的高度都要相同,就是不能有高度差。那如果能做到这一点,就可以保证我们的这棵多差查找数它是平衡的,也就可以保证它不会有太多层,从而就保证了查找的效率。
B树的查找:
由于B树相对于二叉树来说矮胖了许多,所以它所涉及的IO操作也相对少了许多。但是其在查找数据的时候并没有减少比较次数。因为比较数据是在内存中进行的,所以相对来说时间上会更加迅速,几乎可以忽略。
B树的插入:
对高度为k的m阶B树,新结点一般是插在叶子层。通过检索可以确定关键码应插入的结点位置。然后分两种情况讨论:
B树的删除:
B树的删除操作相对较为复杂,这里不做整理了。
参考1:数据结构-B树
参考2:B+树总结
来自GPT3.5的回答。
B+树(B+Tree)是一种自平衡的树状数据结构,类似于B树,但在数据库和文件系统中更常见。B+树在数据库系统中广泛应用于实现索引结构,以提供高效的数据检索和范围查询
。以下是B+树的主要定义特点:
叶子节点(最底层节点)具有相同的深度,即都位于同一层,这使得所有查询操作具有相同的时间复杂度
。所有数据都存储在叶子节点上,而非叶子节点仅用于路由和索引,不存储实际数据
。每个叶子节点都有一个指针,指向下一个数据,从而形成一个有序链表。可以通过链表进行范围查询,这对数据库的范围查询非常有用
。B+树的设计使其特别适用于数据库索引。由于非叶子节点仅用于路由和索引,而所有的数据都存储在叶子节点上,这使得B+树的范围查询非常高效。另外,由于叶子节点之间形成有序链表,B+树可以支持快速的范围查询操作,这对于数据库的区间查询操作非常重要。
来自GPT3.5的回答。
总结:
B+树在数据库系统中更为常见,因为它在范围查询等方面的优势。B树和B+树在实现细节上有一些差异,这些差异使得它们适用于不同的应用场景。选择适合的树状数据结构取决于具体的需求和应用环境。
来自GPT3.5的回答。
定义:
哈希数据结构通过使用哈希函数,将数据映射为固定大小的索引值,然后再将数据存储在数组或哈希表中,从而实现高效的数据存储和检索。
特点:
Hash的数组设置的多大比较合适?
最好是和存储数据的大小一样,但是最好事先设置小一点,后面根据实际的数据量再进行扩容。
来自GPT3.5的回答。
MySQL使用B+树(B-Plus Tree)而不是红黑树来实现索引的主要原因是B+树在数据库系统中更适合用于实现高效的数据存储和检索。
以下是一些解释为什么 MySQL 选择使用 B+树 而不是红黑树:
红黑树在某些情况下也可以用于数据库索引,但B+树的设计和特点使得它更适合数据库系统的需求,因为它可以提供高效的数据存储和检索,特别适用于大量数据和范围查询。
来自GPT3.5的回答。
MySQL 使用 B+树 而不是哈希(Hash)索引的主要原因是 B+树 在数据库系统中更适合用于实现高效的数据存储和检索,特别是满足了数据库操作的多样性需求。
以下是为什么MySQL选择 B+树 而不是B树或哈希索引的一些关键原因:
虽然B树也能够提供高效的数据存储和检索,但B+树在数据库系统中更适合处理大量数据和多样化的查询需求。综合考虑性能、数据存储和查询需求,MySQL选择使用B+树来实现索引,以满足数据库操作的多样性和高效性。
参考1:MySQL数据库之索引详解
MySQL数据库有如下索引类型:
WHERE
子句中同时使用多个条件。来自GPT3.5的回答。
前缀索引的语法:
CREATE INDEX index_name ON table_name (column_name(length));
需要注意以下几点:
VARCHAR
、CHAR
、TEXT
等。一般应该根据实际需求选择适当的前缀长度。较短的前缀可以节省存储空间,但可能会减少索引的选择性,从而影响查询性能。来自GPT3.5的回答。
总结:
建议在InnoDB表中建立主键,并且使用整型的自增主键,是为了优化数据库的性能、提高查询效率,并确保数据的完整性。不过,在某些特殊情况下,可能需要根据具体应用需求来选择适合的主键策略。
来自GPT3.5的回答。
%
在索引字段的开头,索引也可能会失效。例如,WHERE column_name LIKE '%value'
。IS NULL
或IS NOT NULL
也可能导致索引失效。总结:
索引失效的情况有很多,很多情况下都涉及到查询条件、字段数据类型、函数使用等。为了避免索引失效,应该理解查询优化的原则,根据具体情况优化查询语句和索引设计。可以通过EXPLAIN
命令来分析查询的执行计划,以确定是否有效使用了索引。
来自GPT3.5的回答。
MySQL中的LIKE查询可能导致索引失效,特别是在通配符%
出现在查询的开头时。这是因为通配符在开头的情况下,索引无法有效地进行前缀匹配。为了解决这个问题,可以考虑以下几种方法:
%
在 LIKE 查询的开头。你可以考虑使用其他查询方式,如精确匹配、范围查询等。总结:
针对LIKE查询导致的索引失效,需要根据具体情况选择合适的解决方法。这可能包括索引优化、查询重构、使用全文索引或搜索引擎等,取决于你的数据和查询需求。
答:MySQL的主键索引是B+Tree结构。同 4.8 为什么mysql的索引使用B+树而不是B树或hash
B+树更适合外部存储(一般指磁盘存储),由于内节点(非叶子节点)不存储data,所以一个节点可以存储更多的内节点,每个节点能索引的范围更大更精确。也就是说使用B+树单次磁盘IO的信息量相比较B树更大,IO效率更高。
mysql是关系型数据库,经常会按照区间来访问某个索引列,B+树的叶子节点间按顺序建立了链指针,加强了区间访问性,所以B+树对索引列上的区间范围查询很友好。而B树每个节点的key和data在一起,无法进行区间查找。
来自GPT3.5的回答。
MySQL
中的表可以没有主键索引,但建议为表添加主键索引以提高数据的完整性和查询性能。
InnoDB
存储引擎中作为聚簇索引,这意味着数据行的物理存储顺序与主键的顺序一致,从而提高范围查询和连续读取的性能。MySQL
仍然会为每一行数据分配一个隐含的rowid(或物理地址)作为唯一标识符。但是,这种隐含的标识符不像主键那样具有明确的业务含义,而且不会自动帮助维护数据的唯一性。此外,没有主键索引也可能导致一些查询操作变得更加低效,因为数据库优化器可能需要使用更多的资源来处理没有主键索引的表。总结:
尽管MySQL的表可以没有主键索引,但在大多数情况下,建议为表添加主键索引,以确保数据的完整性,并提高查询性能。主键索引是数据库设计中的一项重要指导原则。
来自GPT3.5的回答。
主键索引(Primary Key Index):
NULL
,这意味着每一行数据都必须具有主键值。InnoDB
存储引擎中,主键索引同时也是聚簇索引。这意味着数据行的物理存储顺序与主键的顺序一致。唯一性索引(Unique Index):
NULL
值。这意味着表中可以有多行包含NULL
值。InnoDB
存储引擎中是一个辅助索引(Secondary Index),数据行的物理存储顺序与唯一性索引无关。总结:
主键索引和唯一性索引都用于确保数据的唯一性,但主键索引更严格,要求唯一性和非空性,并且在 InnoDB 中通常作为聚簇索引。唯一性索引可以允许一个NULL
值,而且在InnoDB
中是辅助索引。选择使用哪种索引取决于你的数据需求和业务逻辑。
参考1:MySQL-联合索引
两个或更多个列上的索引被称作联合索引,联合索引又叫复合索引
。
对于复合索引:MySQL从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c)
. 可以支持a
| a,b
| a,b,c
3种组合进行查找,但不支持 b,c
进行查找 .当最左侧字段是常量引用时,索引就十分有效。
来自GPT3.5的回答。
当 SQL 命中了联合索引的前缀列(例如,a),在查找到数据后,MySQL 可能会根据需要执行以下两种不同的操作:
总结:
是否会直接返回或者执行回表取决于具体情况,主要取决于查询所需的数据是否能够通过索引本身获取(即覆盖索引扫描)。设计合适的联合索引和查询方式可以最大程度地减少回表操作,从而提高查询性能。
参考1:mysql最左原则,最左前缀匹配原则
参考2:MySQL:最左前缀匹配原则
MySQL只有联合索引(复合索引)才会使用最左匹配原则。
最左匹配原则的成因:
面试官回答:索引是按a,b,c三个字段排序的,跳过一个就没有顺序了,所以就不能再走后面的索引了。
参考:mysql 复合索引(联合索引) a b c的使用
是否用到索引,与查询条件中这三个列的出现顺序无关,而是与查询条件描述的索引key范围有关。含a的都可以用到索引,优化器自动调整顺序,不含a的用不到索引。
在用到 where 条件为:
最左原则:(来自GPT3.5的回答)
复合索引的最左匹配规则指的是,当你在WHERE条件中使用复合索引的多个列时,数据库会优先利用索引的最左边的列开始匹配,然后根据查询条件从左往右依次逐个列进行匹配。这个规则影响了索引的选择和查询优化。
复合索引的最左匹配规则有以下几个要点:
最左匹配规则对于索引的设计和查询性能优化非常重要。在设计复合索引时,要根据查询模式和使用频率选择合适的列顺序,以便让索引可以在查询中被最大程度地利用。
答:走了,看5.12。
答:使用到了回表,根据查询条件先查询主键,然后再使用主键做条件去查询具体的数据。
不知道,未找到。
面试官的回答:是在第一步把c=的条件过滤掉后,采取做回表的。
参考1:MySQL的主从复制
参考2:MySQL主从复制
来自GPT3.5的回答。
MySQL主从复制(Master-Slave Replication)是一种数据库架构,其中一个数据库服务器(主服务器)上的数据会被异步复制到一个或多个其他数据库服务器(从服务器)。主从复制在许多情况下都是非常有用的,以下是一些主要的原因和优势:
需要注意的是,主从复制也有一些潜在的问题和挑战,例如复制延迟、数据一致性等。在设计和实施主从复制架构时,需要仔细考虑数据的一致性、安全性和性能需求。
参考1:【数据库】MySQL的主从复制模式
MySQL的主从复制模式包括异步复制,全同步复制,半同步复制。MySQL默认为异步复制。
异步复制:MySQL的默认复制方案,主库在执行完客户端提交的事务后会立刻将执行结果返回给客户端,并不关心从库是否已经接收处理,这样带来的问题就是当主死掉了,此时主上提交的事务可能还没有传到从上。而强行将从提升为主就会导致新主上的数据不完整。
全同步复制:当主库执行完一个事务,所有的从库都执行了该事务才会将结果返回给客户端。这样保证了数据的安全性,但是因为需要等待所有从库执行完该事务才能返回客户端结果,所以全同步复制的性能必然会受到很大的影响。
对于全同步复制而言,当主库提交一个事务后,要求所有从库节点必须收到,执行并提交这些事务,然后主库线程才能继续做后续操作,而因此带来的问题就是主库完成一个事务的时间被大幅度拉长,性能降低。
半同步复制:介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收并写到relay log
中才返回给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时也会造成一定程度的延迟,这个延迟为一个TCP/IP
往返的时间。所以半同步复制需要在低延时的网络中使用。
对于半同步复制而言,是介于同步复制和异步复制之间的一种,主库需要等待至少一个从库节点收到并且刷新binlog到relay日志中,主库不需要等待所有从库给主库反馈,同时这里只是收到反馈而不是和完全执行并且提交事务的反馈,这样会节省很多的时间。
MySQL主从复制涉及到三个线程,一个运行在主节点(log dump thread),其余两个(I/O thread, SQL thread)运行在从节点,如下图所示:
主节点log dump线程作用
当从节点连接主节点时,主节点会创建一个log dump
线程,用于发送bin-log
的内容。在读取bin log
中的操作时,此线程会对主节点上的bin log
加锁,当读取完成,甚至在发动给从节点之前,锁都不会被释放。
从节点I/O线程作用
当从节点上执行start slave
命令之后,从节点会创建一个I/O线程
用来连接主节点,请求主库中更新的bin log
。I/O线程
接收到主节点log dump线程
发来的更新之后,保存在本地relay log
中。
从节点SQL线程作用
SQL线程负责读取relay log
中的内容,解析成具体的操作并执行,最终保证主从数据的一致性。
三个线程:
对于每一个主从连接,都需要三个线程来完成。当主节点有多个从节点时,主节点会为每一个当前连接的从节点建一个log dump线程
,而每个从节点都有自己的I/O线程、SQL线程。
从节点用两个线程将从主库拉取更新和执行分成独立的任务,这样在执行同步数据任务的时候,不会降低读操作的性能。比如,如果从节点没有运行,此时I/O线程可以很快从主节点获取更新,尽管SQL线程还没有执行。如果在SQL线程执行之前从节点服务停止,至少I/O线程已经从主节点拉取到了最新的变更并且保存在本地relay log
日志中,当服务再次起来之后,就可以完成数据的同步。
要实施复制,首先必须打开Master 端的binary log(bin-log)功能,否则无法实现。因为整个复制过程实际上就是Slave 从Master 端获取该日志然后再在自己身上完全顺序的执行日志中所记录的各种操作。
change master to
语句连接主库,提供了连接主库的相关信息(user 、password、port、ip)。change master to
语句提供的file
名和position
号,IO线程向主库发起bin log
的请求,请求从主库的指定bin log
日志文件的指定位置(或者从最开始位置)开始同步日志内容。dump
线程根据从库的请求,将本地指定文件名称中指定位置之后bin log
内容发给从库IO线程。发送信息中除了日志信息之外,还包括本次读取的bin-log file
文件名称以及bin-log position
结束位置;bin log events
,并存放到本地relay-log
中的末尾位置,传送过来的信息,会记录到master.info
中,以便在下一次读取的时候能够清楚的告诉Master
”我需要从某个bin-log
的哪个位置开始往后的日志内容,请发给我”;SQL
线程读取relay-log
,并且把读取过的记录到relay-log.info
中,默认情况下,已经应用过的relay
会自动被清理。MySQL 主从复制有三种方式:基于SQL语句的复制(statement-based replication,SBR),基于行的复制(row-based replication,RBR),混合模式复制(mixed-based replication,MBR)。
对应的bin log文件的格式也有三种:STATEMENT、ROW、MIXED。
参考1:SQL语句的执行原理
MySQL的引擎主要有:MyISAM
和InnoDB
、MEMORY
的。
参考1:深入理解MySQL索引底层数据结构
MySQL 默认的引擎是 InnoDB
。
InnoDB:
ACID
的事务,⽀持事务的四种隔离级别;InnoDB
引擎存储在⼀个⽂件空间(共享表空间,表⼤⼩不受操作系统控制,⼀个表可能分布在 多个⽂件⾥),也有可能为多个(设置为独⽴表空,表⼤⼩受操作系统⽂件⼤⼩限制,⼀般为 2G),受操作系统⽂件⼤⼩的限制;MyISAM:
MyISAM
表有三个⽂件:索引⽂件、表结构⽂件、数据⽂件;MEMORY:
MySQL
服务重启之后数据会丢失,它不支持事务和外键。参考1:mysql
回表操作是指在使用索引进行查询时,如果需要获取索引列之外的其他列的数据,数据库引擎需要通过索引找到对应的行,然后再根据行的主键ID获取其他列的数据。这个过程就称为回表操作。
所以回表的产生也是需要一定条件的,如果一次索引查询就能获得所有所需的查询列记录就不需要回表,如果查询列中有其他的非索引列,就会发生回表动作。即基于非主键索引的查询需要多扫描一棵索引树。
回表操作的步骤: 来自GPT3.5的回答。
回表操作可能会对查询性能产生影响,特别是当查询中包含大量的回表操作时。为了减少回表操作的次数,可以考虑以下方法:
来自GPT3.5的回答。
WHERE和HAVING都是在SQL
查询中用于筛选数据的关键字,WHERE
用于筛选行级别的数据,而HAVING
用于筛选聚合后的数据。它们的区别主要体现在以下几个方面:
HAVING SUM(sales) > 1000
。尽量少Join、少排序、少使用“or”关键字、尽量用
union all
代替union
、避免类型转换、选择合适的索引类型、从全局出发优化,而不是片面调整等等。
参考1:Mysql的explain详解
参考2:mysql的explain详细
MySQL如何查看一条SQL是否使用索引的关键字?
答:explain。
以下是来自GPT3.5的回答。
在MySQL
中,EXPLAIN
是一个用于查询优化的关键字,它可以用来查看查询执行计划,帮助我们理解查询是如何被数据库引擎处理的。
通过查看EXPLAIN
结果,可以获得有关查询执行的重要信息,包括 索引的使用、表之间的连接方式、数据读取顺序 等,从而帮助优化查询性能。
EXPLAIN
的输出结果将会是一个关于查询执行计划的表格,它包含以下参数指标:
SIMPLE
、PRIMARY
、SUBQUERY
等。const
、eq_ref
、ref
、range
、index
、ALL
等。Using where
、Using index
、Using temporary
等。参考1:MySQL事务的特性
参考2:Mysql的隔离级别
四大特性特性:
JDBC
操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务已经正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成。否则的话就会造成我们虽然看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误,这是不允许的。MySQL默认的隔离级别为可重复读。
隔离级别:
MySQL
默认的隔离级别却不是这种),满足了隔离的最早简单定义:一个事务开始时,只能看见已经提交事务所做的改变,一个事务从开始到提交前,所做的任何数据改变都是不可见的,除非已经提交。这种隔离级别也支持所谓的不可重复读。这意味着用户运行同一个语句两次,看到的结果是不同的。MySQL
数据库默认的隔离级别。它保证了同一事务的多个实例在并发读取数据时,会看到同样的数据。 该级别解决了读未提交(read uncommitted) 导致的问题,但是会导致另外一个问题“幻读”。InnoDB
存储引擎是通过多版本并发控制解决了幻读问题。SERIALIZABLE
是在每个读的数据行加锁,在这个级别上可能导致大量的超时和锁竞争现象,实际应用中很少使用这个级别,但如果用户的应用为了数据的稳定性,需要强制减少并发,也可以选择这种隔离级别。事务并发带来的问题(脏读、不可重复读、幻读):
不可重复读和脏读的区别在于:脏读是一个事务读取了另一未完成的事务执行过程中的数据,而不可重复读是一个事务执行过程中,另一事务提交并修改了当前事务正在读取的数据。
参考1:MySQL是如何实现可重复读的?
参考2:https://myoule.zhipin.com/questions/df4244a81a5220ebtnV_3tW4GFY~.html
可重复读(REPEATABLE READ)是MySQL的默认隔离级别,具体是指在同一事务中多次读取的数据是一致的。
MySQL是使用多版本并发控制(MVCC,Mutil-Version Concurrency Control)实现可重复读的。
MVCC的核心思想:
为每个数据修改保存一个版本,版本与事务时间戳相关联。读操作只读取该事务开始前的数据库快照。这样,即使有其他事务对数据进行修改,也不会影响当前事务的读取结果。同时,由于读操作不需要加锁,也就避免了锁的开销和等待。
MVCC实现的原理:(来自GPT3.5的回答)
MySQL
会为该事务创建一个读视图(read view)。读视图是一个事务在某一时间点可以看到的数据库状态的逻辑表示。读视图包括了事务开始时已提交的所有事务的ID和版本号。MySQL
使用事务的读视图来确定应该读取哪个版本的数据。只有那些在事务开始时已经提交的事务生成的数据版本才会在可重复读事务中可见。MySQL
会为新插入的数据赋予新的版本号和该事务的ID。原始数据行会被保留,以便其他事务在可重复读隔离级别下可以继续读取。MySQL
使用回滚段(undo log)来存储事务的旧数据版本。这允许事务在回滚时恢复到之前的状态。通过上述机制,MySQL
实现了可重复读隔离级别。每个事务在开始时创建一个读视图,然后使用这个视图来决定在事务执行期间哪些数据版本是可见的。这使得在同一时刻执行的多个事务可以看到一致的数据库状态,即使其他事务在执行过程中有所改变。
需要注意的是,尽管可重复读隔离级别提供了一定程度的隔离,但并不意味着不会发生任何并发问题。在实际应用中,仍然需要根据业务需求和查询模式来选择合适的隔离级别,并在编写事务时小心处理并发问题。
参考1:MySQL中是如何实现事务提交和回滚的?
MySQL数据库为了保证事务的原子性和持久性,引入了Redo Log
(重做日志)和Undo Log
(撤销日志),它们分别用于事务的提交和回滚,以及并发控制和事务恢复。
Redo Log(重做日志):
Redo Log
用于记录事务对数据库所做的修改。它是为了确保事务的持久性而存在的,即使数据库在事务提交后崩溃,也可以通过重放Redo Log
来重新执行事务的修改,从而将数据恢复到提交事务后的状态。Redo Log
中,然后将Redo Log
记录写入磁盘中的Redo Log
文件。这样,即使数据库在将修改应用到数据文件之前崩溃,仍然可以通过重放Redo Log
来恢复数据的状态。Undo Log(撤销日志):
Undo Log
用于事务的回滚、并发控制和MVCC(多版本并发控制)。它记录了事务进行修改之前的数据版本,以便在回滚事务时恢复到原始的数据状态,并在并发操作中提供一致性视图。Undo Log
中,以保存原始数据版本。当事务回滚时,数据库会使用Undo Log
中的数据将修改撤销,从而恢复到事务开始前的状态。在并发操作中,数据库通过查询Undo Log
来生成事务的一致性视图,以确保事务之间的隔离性。总结:
Redo Log
(重做日志)和Undo Log
(撤销日志)是MySQL
中关键的日志机制,分别用于记录事务的修改以保证持久性,以及用于回滚、并发控制和一致性视图的生成。它们共同构建了MySQL
强大的事务支持和并发控制能力。
数据库表的索引从数据存储方式上可以分为 聚簇索引 和 非聚簇索引 两种。“聚簇”的意思是数据行被按照一定顺序一个个紧密地排列在一起存储。
非主键的索引一般是非聚簇索引。InnoDB
默认使用聚簇索引,MyISAM
则在表级别上使用非聚簇索引。因此,在选择存储引擎时,也要考虑到索引和数据的组织方式。
聚簇索引:
聚簇索引(Clustered Index
)并不是一种单独的索引类型,而是一种 数据存储方式。聚簇索引表记录的排列顺序和索引的排列顺序是一致的(以InnoDB
聚簇索引的主键索引来说,叶子节点中存储的就是行数据,行数据在物理储器中的真实地址就是按照主键索引树形成的顺序进行排列的)。
聚簇索引在范围查询和基于主键的查询方面非常高效,因为相关的数据行在物理上相邻。然而,频繁的插入、删除操作可能会导致性能下降,因为数据行可能需要重新排序。(在真实物理存储器的存储顺序只能有一种,而插入、删除数据可能会导致主键索引树的变化,主键索引树的顺序发生了改变,叶子节点中存储的行数据也要随之进行改变,就会发生大量的数据移动操作,所以效率会慢)。
因为在物理内存中数据的存储顺序只能有一种,所以聚簇索引在一个表中只能有一个。
聚簇索引默认是主键,如果表中没有定义主键,InnoDB
会选择一个唯一且非空的索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键(类似oracle
中的RowId
)来作为聚簇索引。如果已经设置了主键为聚簇索引又希望再单独设置聚簇索引,必须先删除主键,然后添加我们想要的聚簇索引,最后恢复设置主键即可。
非聚簇索引:
以下是来自GPT3.5的回答。
慢查询产生原因:
MySQL
配置参数不合理,导致资源不足。MySQL
配置参数,根据服务器硬件和负载情况进行适当调整。解决慢查询问题的常见方法:
EXPLAIN
:通过EXPLAIN
关键字分析查询计划,查看是否有不合理的扫描或连接方式。Memcached
或Redis
来减轻数据库压力。MySQL
的慢查询日志,找出频繁出现的慢查询,进行针对性优化。MySQL
提供的性能分析工具,如MySQL
自带的Performance Schema
和外部工具如pt-query-digest
来分析慢查询。总结:
解决MySQL
慢查询问题需要根据具体情况进行分析和优化,从查询语句、索引、配置和硬件等多个角度入手,以提高数据库性能。
以下是来自GPT3.5的回答。
乐观锁和悲观锁概念:
乐观锁和悲观锁实现方式:
乐观锁和悲观锁使用场景:
总结:
乐观锁和悲观锁是不同的并发控制策略,适用于不同的场景。乐观锁通过避免明确的锁定来提高性能,但可能需要更多的逻辑来处理并发冲突。悲观锁通过显式的锁定来确保数据的一致性,但可能会带来更多的锁冲突和性能开销。选择合适的锁策略需要根据业务需求和并发访问模式进行权衡。
以下是来自GPT3.5的回答。
在MySQL
中,SELECT...FOR UPDATE
是一种用于实现悲观锁的查询语句。它的作用是在读取数据的同时,锁定被选中的数据行,防止其他事务对这些数据行进行修改,从而保证数据的一致性和并发性。
当使用SELECT...FOR UPDATE
语句时,MySQL
会为所选中的数据行设置行级锁,这样其他事务就无法在同一时间内对这些数据行进行修改,直到当前事务释放锁。这可以用于确保在某个事务处理数据期间,其他事务不会干扰或修改这些数据。
常见用途包括:
SELECT...FOR UPDATE
可以确保其他事务不会在更新数据之前修改这些数据,从而避免数据冲突。SELECT...FOR UPDATE
来锁定这些资源,确保其他任务无法同时访问。示例使用:
START TRANSACTION;
SELECT * FROM orders WHERE status = 'Pending' FOR UPDATE;
-- 在这里可以进行针对选中数据的处理和更新操作
COMMIT;
需要注意的是,使用SELECT...FOR UPDATE
可能会引起锁冲突,降低并发性能。因此,它应该谨慎使用,特别是在高并发环境中。同时,务必在事务结束后释放锁,以避免死锁和性能问题。
create stu (
id [主键]
name
age
...
create_time
index(create_time)
) innodb
查询SQL:
select name,age from stu where create_time >xxx 1000000,10
问题:因为offset=1000000,比较大,属于深度分页,在某些场景下,查询性能会出现下降,分析可能的原因及如何优化。
针对:MySQL
深度分页,在某些场景下,查询性能会出现下降,分析可能的原因及如何优化。以下是来自GPT3.5的回答。
标识
作为下一次查询的起点),或者使用滚动窗口技术,这些方法在大数据量情况下性能更好。避免了大偏移量的问题。I/O
:大偏移量可能需要跳过大量的数据行,这可能会导致更多的硬盘I/O
操作,从而降低查询性能。MySQL
内部在执行深度分页时可能需要创建临时表或执行多次查询,这也会增加性能开销。MySQL
内部的优化问题。MySQL
的缓存设置,考虑使用内存数据库或缓存来加速查询。MySQL
提供的性能分析工具来识别和优化慢查询。参考1:mysql千万级数据分页查询性能优化
参考1:mysql limit越大越慢(MySQL limit分页大偏移量慢的原因及优化方案)
主要看参考1。
参考1:SQL注入
以下是来自GPT3.5的回答。
SQL
注入是一种常见的安全漏洞,指的是攻击者通过在用户输入的数据中插入恶意的SQL
代码,从而执行未经授权的数据库操作。SQL
注入可以导致数据泄露、数据破坏、服务器被接管等安全问题。
以下是SQL
注入产生原因和解决办法:
产生原因:
SQL
查询中,攻击者可以通过输入特殊字符来修改查询逻辑。解决办法:
SQL
查询,尽量使用ORM
(对象关系映射)框架。WAF
来监测和拦截恶意请求,减少攻击的风险。SQL
注入问题。有一个书店评语表,简化后3个字段:
现在需要给一个SQL,不用子查询,完成: 查出至少被2个不同的人评价为“好”的书。
select book_name from comment_table group by book_name having count(*) >2;
SELECT book_name,user_name FROM comment_table WHERE comment ='好' GROUP BY book_name having count(book_name) >=2;
有三张表:
有买过螺丝商品的订单。
SELECT u.id,u.name FROM user u LEFT JOIN order ON u.id=order.user_id LEFT JOIN product ON product.id=order.product_id WHERE product.name='螺丝';
sql待完成,提示【group by+MAX()】
SELECT product.id,product.name FROM product WHERE product.id IN (SELECT product_id FROM order WHERE price)
答:应该选A。具体原因通过 8.1 MySQL是怎么实现可重复读的
剋深入了解,可重复读(REPEATABLE READ)是MySQL的默认隔离级别,具体是指在同一事务中多次读取的数据是一致的。一个还未提交的事务不会对另一个已开始的事务产生影响,即Session A
事务不会影响Session B
事务。
在Session B
事务开始时Session A
事务还没有执行提交操作,所以Session B开始时age=20 (即age2=20),在Session B中更新了一次age数值,所以age3=21,age4=21;
问题描述:T1数据表,T2是查询结果,写出将T1表的数据按T2显示的SQL。
select name,group_concat(product,"")AS product from sale group by name;