Mysql 面试题总结
1. Mysql 数据库,隔离级别有哪几个?
在 MySQL 数据库中,事务的隔离级别决定了一个事务在执行期间对其他事务可见的数据变化情况。MySQL 支持 SQL 标准定义的四种隔离级别,从低到高依次为:
-
读未提交(READ UNCOMMITTED) 在该隔离级别下,事务中的修改即使没有提交,对其他事务也是可见的。 特点:
-
可能出现 脏读(Dirty Read),即一个事务可以读取到其他未提交事务的数据。
-
不建议使用,因为几乎没有应用需要这个级别的隔离。
-
-
读已提交(READ COMMITTED) 在该隔离级别下,事务只能看到其他事务已经提交的修改,未提交的修改对其他事务不可见。 特点:
-
避免了脏读。
-
但是可能出现 不可重复读(Non-repeatable Read),即同一个事务中两次读取同一行,结果可能不同(因为其他事务提交的修改会被读到)。
-
-
可重复读(REPEATABLE READ) 这是 MySQL 默认的隔离级别。在该隔离级别下,事务开始后看到的数据是该事务启动时数据库的快照,因此在整个事务期间,即使其他事务提交了修改,也不会影响该事务的读取结果。 特点:
-
避免了脏读和不可重复读。
-
但是在某些情况下,可能出现 幻读(Phantom Read),即同一个查询在不同的时间点,可能返回的行数不同。
-
-
串行化(SERIALIZABLE) 在该隔离级别下,事务被完全串行化执行,仿佛所有事务都是顺序执行的。 特点:
-
最高的隔离级别,完全避免了脏读、不可重复读和幻读。
-
但是会导致更多的锁争用,性能开销很大。
-
2. 什么样的列不适合建立索引?
1. 基数(唯一值)较低的列
如果一个列中的值只有少数几种可能性(如布尔类型列,性别
列等),即使建立索引,也无法有效地缩小查询范围。此类列的索引选择性差,无法显著加速查询。
例如:
-
性别(
Male
/Female
) -
布尔值(
True
/False
) -
状态字段(如
active
、inactive
等)
这种情况下,索引的过滤效果很差,因为多数记录的值可能是相同的。
2. 频繁更新的列
如果一个列频繁更新,那么每次更新该列时,MySQL 都需要维护相应的索引结构,增加了数据库的开销。因此,频繁更新的列通常不适合建立索引。
例如:
-
计数器(如记录访问次数的列)
-
不断变化的状态(如订单状态频繁变化)
3. 长度过长的列
较长的字符串列,如 VARCHAR(255)
或更长的文本列,建立索引会占用大量的存储空间,且索引操作效率较低。因此,文本内容非常长的列通常不适合索引。
例如:
-
长度较大的文本字段(如描述、备注等)
-
二进制大对象(如图片、视频、音频存储的 BLOB 字段)
4. 经常出现 NULL 的列
如果某列中有大量的 NULL
值,并且查询时不会使用该列进行过滤,那么在此列上建立索引意义不大。NULL 值在某些查询优化情况下无法利用索引,从而无法提升性能。
5. 没有在查询中使用的列
如果某列从未或者很少在 WHERE
、JOIN
、ORDER BY
、GROUP BY
等查询条件中使用,那么即使建立索引也不会带来性能提升。只有在需要频繁查询的列上建立索引才有意义。
6. 小表中的列
对于数据量较小的表,MySQL 即使不使用索引,也能通过全表扫描快速完成查询。对于行数少的小表,索引带来的好处可能非常有限,反而增加了维护成本。
7. 重复值非常多的列
如果列中的值大部分都是相同的,索引的选择性很差,查询的性能不会有显著提升。例如,在记录大量订单的表中,如果有一个列记录了固定的默认值(如 "待处理"),这个列上的索引在查询中几乎没有帮助。
3. 数据库事务的特性有哪些?
ACID 特性总结:
-
原子性:事务要么全部执行成功,要么全部失败。
-
一致性:事务前后数据库必须保持一致。
-
隔离性:事务之间相互隔离,不受干扰。
-
持久性:事务提交后,修改将永久保存。
这些特性共同确保了数据库在事务处理中能够保持高可靠性和一致性。
4. 分页如何实现?
在 MySQL 中,使用 LIMIT
和 OFFSET
实现分页,LIMIT
控制每页的记录数,OFFSET
控制从哪一条开始取
5. jdbc的过程及主要的类?
JDBC 的典型操作步骤:
-
加载数据库驱动程序 首先,必须加载相应的数据库驱动程序,通常使用
Class.forName()
方法动态加载驱动类。例如:Class.forName("com.mysql.cj.jdbc.Driver");
这一步会加载 MySQL JDBC 驱动。
-
建立数据库连接 通过
DriverManager.getConnection()
方法建立与数据库的连接。需要提供数据库的 URL、用户名、密码。Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
-
创建 SQL 语句 使用
Connection
对象创建一个Statement
或PreparedStatement
,用于执行 SQL 语句。-
Statement:用于执行简单的 SQL 语句。
-
PreparedStatement:用于执行预编译的 SQL 语句,能够防止 SQL 注入,并支持参数化查询。
Statement statement = connection.createStatement(); PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM users WHERE id = ?");
-
-
执行 SQL 语句
-
查询时,使用
executeQuery()
方法返回结果集。 -
更新、插入或删除时,使用
executeUpdate()
方法返回影响的行数。
ResultSet resultSet = statement.executeQuery("SELECT * FROM users");
-
-
处理结果集 遍历
ResultSet
来获取查询结果。while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); // 处理结果... }
-
关闭资源 在操作完成后,需要关闭所有的 JDBC 资源(如
ResultSet
、Statement
、Connection
),以释放数据库连接。resultSet.close(); statement.close(); connection.close();
JDBC 主要类和接口:
-
DriverManager 负责管理 JDBC 驱动程序,并通过它们与数据库建立连接。
-
Connection 表示与特定数据库的连接,通过它可以创建
Statement
或PreparedStatement
对象。 -
Statement 用于执行静态 SQL 语句并返回结果。它适用于简单的 SQL 查询。
-
PreparedStatement 是
Statement
的子类,支持预编译 SQL 语句,可以通过参数化防止 SQL 注入。 -
ResultSet 用于存储执行 SQL 查询后返回的结果集。可以逐行遍历结果并读取数据。
-
SQLException JDBC 操作过程中,所有的异常都会以
SQLException
抛出,必须进行异常处理。
6. #{} 和 ${} 的区别?
区别 | #{} | ${} |
---|---|---|
机制 | 预编译处理,安全地传递参数 | 直接字符串替换 |
是否防止 SQL 注入 | 是,使用 PreparedStatement 机制,防止 SQL 注入 | 否,直接拼接参数,可能导致 SQL 注入风险 |
适用场景 | 传递参数,如 WHERE 子句中的参数 | 动态 SQL,如列名、表名等(不能用于传递参数) |
SQL 执行效率 | 较高,参数通过占位符传递,性能较好 | 较低,字符串拼接后再执行 |
7. Mysql 都有哪些锁?
MySQL 中锁的类型主要分为以下几类:
1. 全局锁
-
全局锁 用于锁定整个数据库,通常用于数据库备份、恢复等操作。
-
典型的全局锁
-
FLUSH TABLES WITH READ LOCK (FTWRL)
:锁住整个数据库,防止其他线程对数据库进行读写操作,常用于备份操作。
-
2. 表级锁
表级锁作用于整个表,通常分为两种:
-
表锁(Table Lock):锁住整张表,阻塞其他线程对该表的读写操作。MyISAM 和 InnoDB 都支持表锁。
-
意向锁(Intention Lock)
3. 行级锁
行级锁是作用于某一行的锁,InnoDB 引擎支持行级锁,主要有以下两种:
-
共享锁(S 锁,Shared Lock):允许其他事务读取被锁定的行,但不允许修改。多个事务可以同时持有同一行的共享锁。
-
排他锁(X 锁,Exclusive Lock):排他锁不允许其他事务访问该行,只有持有排他锁的事务可以读取或修改这行。
4. 间隙锁(Gap Lock)
-
间隙锁 是 InnoDB 在可重复读隔离级别下,避免幻读的一种锁机制。它锁定的是索引记录之间的间隙,而不是具体的记录,防止在事务运行期间有新的数据插入到这一区间内。
5. 意向锁
-
意向锁 是表级别的锁,主要用于在加行级锁之前声明意图。意向锁不会阻塞其他事务,但是能与表锁配合工作,确保在必要时阻止其他事务修改表。
用于 InnoDB 引擎,表明事务计划对表中的某些行加锁,有两种类型:
-
意向共享锁(IS):事务打算对表中的某些行加共享锁。
-
意向排他锁(IX):事务打算对表中的某些行加排他锁。
8. 死锁的判断原理和具体场景,死锁怎么解决?
死锁是指在并发操作中,不同事务因相互持有对方需要的资源而陷入永久等待的状态。如果没有外力干涉,这些事务将无法继续执行。
MySQL 在遇到死锁时,会通过 死锁检测机制 来判断并解决死锁。它的基本原理如下:
-
等待图(Wait-for Graph):MySQL 会构建一个事务等待图,图中的每个节点代表一个事务,边表示事务之间的资源等待关系。如果在图中发现了环路(循环依赖),则说明发生了死锁。
-
死锁检测:MySQL 的存储引擎(如 InnoDB)会通过定期检测或在资源请求时进行死锁检测。当一个事务等待另一个事务持有的锁时,MySQL 会更新等待图。如果发现等待图中存在环路,MySQL 就认为发生了死锁。
-
死锁解决:MySQL 检测到死锁后,会通过回滚其中一个事务来打破死锁。通常会选择 回滚持有较少锁或执行时间较短 的事务,以尽量减少损失。
解决死锁的常见方法
-
设置合理的锁顺序
-
避免交叉锁定资源:在多个事务中,确保它们以相同的顺序获取锁。这可以避免事务之间相互等待资源,减少死锁的发生几率。
-
-
使用锁超时
-
设置合理的锁等待超时(
innodb_lock_wait_timeout
):可以通过设置一个较短的锁超时,避免长时间等待而进入死锁。例如,设置innodb_lock_wait_timeout
为 5 秒,防止事务等待过久导致死锁。
-
-
合理设计索引
-
优化查询语句,避免全表扫描:查询操作最好利用索引来锁定更少的行,减少锁竞争。例如,增加合理的索引可以减少 InnoDB 锁住的行数,从而减少锁竞争的机会。
-
-
死锁日志分析
-
分析死锁日志:当死锁发生时,MySQL 会将死锁信息记录到
SHOW ENGINE INNODB STATUS
中的死锁日志。可以通过分析这些日志来找到导致死锁的具体查询和表设计,并进行优化。
-
9. Mysql 分组的关键字是什么,怎么用?
在 MySQL 中,分组 的关键字是 GROUP BY
,它用于将查询结果集按照某一列或多列的值进行分组,并可以对每个分组执行聚合函数
-
聚合函数 如
COUNT()
、SUM()
、AVG()
常用于对分组后的数据进行统计。 -
HAVING
用于过滤分组后的结果,而WHERE
用于过滤分组前的数据。 -
可以根据单列或多列分组,以满足复杂的数据分析需求。
10. 说一说 drop delete truncate 的区别
在 SQL 中,DROP
、DELETE
和 TRUNCATE
是用来删除数据的,但它们的功能、影响和执行方式各不相同。
1. DROP
-
功能:删除整个表、数据库、视图、索引等数据库对象。执行
DROP
后,整个对象及其数据被彻底删除,无法恢复。 -
影响
-
删除表会删除表结构及所有数据。
-
删除表后,表的定义、数据、索引、触发器等都被删除。
-
删除数据库会删除数据库中所有的表及数据。
-
-
事务处理:
DROP
操作不能被回滚,一旦执行,数据和表结构会立即被删除。
2. DELETE
-
功能:删除表中的数据。
DELETE
可以使用WHERE
子句来指定删除的条件,如果没有WHERE
子句,则删除表中的所有数据。 -
影响
-
只删除数据,不删除表结构、索引等。
-
删除操作可以被回滚(如果在事务中执行)。
-
删除操作会触发相关的触发器(如果存在)。
-
性能较低,尤其是在删除大量数据时。
-
-
事务处理:
DELETE
操作可以被回滚。如果在事务中执行,可以通过ROLLBACK
恢复数据。
3. TRUNCATE
-
功能:删除表中的所有数据,但保留表的结构。
TRUNCATE
通常比DELETE
更快,因为它不逐行删除数据,而是直接释放表的空间。 -
影响
-
删除所有行,但保留表结构、索引和触发器。
-
不会触发
DELETE
触发器。 -
不能使用
WHERE
子句。 -
重置自增列的计数器(对于支持自增列的数据库)。
-
-
事务处理:在大多数数据库系统中,
TRUNCATE
是不可回滚的(或者说,回滚支持有限 InnoDB 支持回滚)
11. 什么是内连接,左外连接和右外连接?
内连接:只包含两个表中匹配的行。如果一个表中的行在另一个表中没有匹配,则这些行不会出现在结果集中。
左外连接:包括左表中的所有行,以及右表中匹配的行。对于没有匹配的右表行,结果中对应的列值为 NULL
。
右外连接:包括右表中的所有行,以及左表中匹配的行。对于没有匹配的左表行,结果中对应的列值为 NULL
。
12. 索引的类型有哪些?
主键索引 是唯一的索引,用于唯一标识表中的每一行。每个表只能有一个主键索引。
唯一索引 确保索引列中的所有值都是唯一的,但允许 NULL
值(在 MySQL 中,多个 NULL
值被视为不同的值)。
普通索引 用于提高查询速度,但不要求列的值唯一。
全文索引 用于对文本数据进行全文搜索。通常用于 CHAR
、VARCHAR
和 TEXT
列。
组合索引 是由多个列组成的索引。用于加速涉及多个列的查询。
哈希索引 基于哈希表,用于快速定位数据。
空间索引 用于地理信息系统(GIS)中的空间数据(如点、线、多边形等)的快速检索。
13. Mysql 查看索引使用情况的命令是什么?
SHOW INDEX FROM table_name
:显示表的索引信息。
SHOW CREATE TABLE table_name
:显示创建表的 SQL 语句,包括索引的定义。
EXPLAIN query
:分析查询的执行计划,查看索引的使用情况,包括使用的索引、扫描的表、行数等信息。
14. select * from user where userId = 5 for update 是什么锁?假如 useid 是索引但是没有这个数据,锁的是什么?假如没有索引也没有 5 这个数据锁的是什么?
排它锁
如果有索引但没有匹配的数据:会对相关的索引页加锁,防止其他事务在这个索引页上插入匹配的数据。
如果没有索引也没有匹配的数据:会对涉及的数据页加锁,以防止其他事务对这些数据页进行修改或插入。
数据页 存储表中的实际数据行,是表中数据的主要存储位置。
索引页 存储索引数据和结构,用于加速数据的检索和定位。
15. Mysql 索引失效的场景有哪些?索引优化的方式有哪些?
索引失效的场景
-
不使用索引列进行查询
如果查询条件不涉及索引列,索引就无法发挥作用。例如:
SELECT * FROM employees WHERE name = 'Alice'; -- 如果没有为 name 列创建索引,则会导致全表扫描
-
函数或表达式作用于索引列
如果在查询条件中对索引列使用了函数或表达式,MySQL 将无法使用该索引。例如:
SELECT * FROM employees WHERE YEAR(birthdate) = 1990; -- YEAR(birthdate) 使索引失效
-
隐式数据类型转换
如果查询中涉及数据类型转换,可能导致索引失效。例如:
SELECT * FROM employees WHERE employee_id = '5'; -- 如果 employee_id 是整数类型,字符串'5'可能会导致索引失效
-
使用
LIKE
语句的开头字符如果
LIKE
查询以通配符开头,索引将不会被使用。例如:SELECT * FROM employees WHERE name LIKE '%Alice'; -- 通配符 % 在前面
-
索引列包含
NULL
值如果索引列包含
NULL
值,并且查询条件涉及NULL
,可能会导致索引无法使用。例如:SELECT * FROM employees WHERE name IS NULL; -- 如果 name 列包含 NULL 值,索引可能无法使用
-
使用不等于(
<>
或!=
)操作符对于
<>
或!=
操作符,索引可能无法有效使用。例如:SELECT * FROM employees WHERE employee_id <> 5; -- 这种查询可能会导致全表扫描
-
使用 OR 连接多个条件
如果查询条件中有多个 OR 连接的条件,可能会导致部分或全部索引失效。例如:
SELECT * FROM employees WHERE name = 'Alice' OR department_id = 10; -- 可能导致索引失效
索引优化的方式
-
选择合适的索引类型
-
对于等值查询,使用 B-tree 索引(默认)。
-
对于全文搜索,使用 FULLTEXT 索引。
-
对于空间数据,使用 SPATIAL 索引。
-
-
创建复合索引
如果查询条件涉及多个列,可以考虑创建复合索引(组合索引)。复合索引可以覆盖多个列,优化多列查询性能。
CREATE INDEX idx_name_department ON employees (name, department_id);
-
避免冗余索引
删除不必要的索引,避免索引冗余。一个表中不应存在功能重叠的索引。
-
优化查询语句
-
避免在索引列上使用函数或表达式。
-
使用等值查询而不是范围查询。
-
避免在
LIKE
查询中使用前导通配符(%
)。 -
使用
EXPLAIN
分析查询,确保索引被有效利用。
-
-
定期更新统计信息
定期更新表的统计信息,以确保优化器能够做出最佳的索引选择:
ANALYZE TABLE employees;
-
利用索引覆盖(Index Covering)
确保查询只访问索引中的列而不是数据页。通过选择适当的索引列,可以使查询只需要访问索引即可完成。
SELECT name FROM employees WHERE employee_id = 5; -- 如果 employee_id 和 name 都在索引中,可以减少对数据页的访问
16. 什么叫回表查询?如果没有创建主键,怎么进行回表查询?
回表查询 是指在使用索引进行查询时,如果索引不包含所需的所有列数据,数据库引擎会额外访问数据表的实际数据页来获取缺失的列数据的过程。回表查询通常发生在 非覆盖索引 查询中。
没有主键时的回表查询
如果表没有主键,那么回表查询的过程略有不同,因为 MySQL 无法使用主键来唯一标识行。以下是处理这种情况的方法:
-
唯一索引:即使没有主键,但如果表上存在其他唯一索引,数据库可以使用这些唯一索引来进行回表查询。例如,如果
employee_id
列有唯一索引,可以用它来查找数据。 -
普通索引:如果没有唯一索引,数据库需要通过非唯一索引查找到所有满足条件的行,然后通过查找行的位置来访问数据页。在这种情况下,查询性能可能会受到影响,因为数据库需要处理更多的数据行。
-
数据页扫描:如果表没有任何索引,数据库需要对整个表进行全表扫描来找到匹配的记录,然后读取数据页。这将导致较高的 I/O 和处理开销。
17. SQL 慢查询的常见优化步骤是什么?
1. 分析慢查询
-
启用慢查询日志:在 MySQL 中,可以通过配置
slow_query_log
选项启用慢查询日志,并设置long_query_time
来定义查询的慢查询阈值。例如:SET GLOBAL slow_query_log = 'ON'; SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log'; SET GLOBAL long_query_time = 2; -- 记录执行时间超过 2 秒的查询
-
使用
EXPLAIN
分析查询:EXPLAIN
语句可以显示查询的执行计划,包括表的访问顺序、索引使用情况等。例如:EXPLAIN SELECT * FROM employees WHERE department_id = 10;
2. 优化查询语句
-
选择性查询:只选择所需的列,而不是使用
SELECT *
。例如:SELECT name, email FROM employees WHERE department_id = 10;
-
避免子查询:尽量将子查询转换为连接查询,以减少数据库的工作量。例如:
SELECT e.name FROM employees e JOIN departments d ON e.department_id = d.id WHERE d.name = 'Sales';
-
优化排序和分组:确保排序 (
ORDER BY
) 和分组 (GROUP BY
) 使用索引,以提高性能。例如:SELECT department_id, COUNT(*) FROM employees GROUP BY department_id;
-
使用合适的 WHERE 条件:避免在 WHERE 子句中使用不必要的函数或表达式。例如:
-- 优化前 SELECT * FROM employees WHERE YEAR(birthdate) = 1990; -- 优化后 SELECT * FROM employees WHERE birthdate BETWEEN '1990-01-01' AND '1990-12-31';
3. 优化索引
-
创建和调整索引:根据查询的使用情况创建合适的索引,特别是 WHERE 子句、JOIN 子句和 ORDER BY 子句中涉及的列。例如:
CREATE INDEX idx_dept_id ON employees (department_id);
-
使用复合索引:对于涉及多个列的查询,创建复合索引可以提高查询性能。例如:
CREATE INDEX idx_dept_name ON employees (department_id, name);
-
删除冗余索引:避免创建重复或不必要的索引,这些索引会消耗额外的存储空间并降低插入和更新性能。
4. 优化表结构
-
规范化和反规范化:根据实际应用情况进行表的规范化或反规范化。规范化可以减少数据冗余,而反规范化可以提高查询效率。
-
优化数据类型:确保表列使用合适的数据类型。使用过大的数据类型会浪费存储空间并影响性能。例如:
-- 不推荐 CREATE TABLE employees (employee_id CHAR(10), ...); -- 推荐 CREATE TABLE employees (employee_id INT, ...);
5. 优化数据库配置
-
调整数据库缓存设置:例如调整
innodb_buffer_pool_size
、query_cache_size
等参数,以提高缓存命中率并减少磁盘 I/O 操作。 -
配置并发设置:调整数据库的并发设置,以优化处理大量并发查询的能力。
6. 使用数据库维护工具
-
执行
ANALYZE TABLE
:更新表的统计信息,以帮助优化器做出更好的索引选择。ANALYZE TABLE employees;
-
执行
OPTIMIZE TABLE
:重建表和索引,优化表的存储和性能。OPTIMIZE TABLE employees;
7. 分区和分表
-
表分区:对于非常大的表,可以使用表分区来分割数据,以提高查询性能。例如:
CREATE TABLE orders ( order_id INT, order_date DATE, ... ) PARTITION BY RANGE (YEAR(order_date)) ( PARTITION p2019 VALUES LESS THAN (2020), PARTITION p2020 VALUES LESS THAN (2021), PARTITION p2021 VALUES LESS THAN (2022) );
-
分表:将数据分散到多个表中,以减少单个表的大小并提高查询性能。
8. 监控和调优
-
使用性能监控工具:使用如
mysqltuner
、performance_schema
、SHOW PROCESSLIST
等工具来监控数据库性能并进行调优。 -
分析执行计划:定期检查慢查询日志和执行计划,识别性能瓶颈并进行优化。
18. explain 命令可以看什么,怎么看用了哪些索引,如果是联合索引的话,怎么知道他用上了哪些字段
explain 可以查看查询的执行顺序标识符,查询的类型,表的名称,可能会使用到的索引,实际使用的索引,表示 MySQL 使用的索引中前多少个字节,MySQL 估算需要读取的行数
possible_keys
和 key
字段:
-
possible_keys
:显示可能用到的索引,但不一定使用。 -
key
:显示实际使用的索引。
通过 key
字段可以确认查询实际使用了哪个索引。
联合索引: 如果查询使用的是联合索引,你可以通过 key_len
字段来判断实际用了联合索引的哪几列。
-
key_len
表示索引使用的字节长度,你可以根据不同数据类型的长度来推断索引使用了哪些列。-
对于
INT
类型,使用 4 个字节。 -
对于
VARCHAR(n)
类型,使用n+1
个字节。 -
对于
CHAR(n)
类型,使用n
个字节。
-
19. 只考虑 Mysql 如何解决超卖问题?
-
使用行级锁
使用事务和行级锁可以防止多个并发操作修改同一条数据,使用
SELECT ... FOR UPDATE
读取库存数据并锁定行。 -
乐观锁
通过在更新时判断数据版本或时间戳是否与读取时一致,来检测数据是否被其他事务修改过。最常见的方法是添加一个版本号(version)*或*时间戳 字段。
-
原子操作
在
UPDATE
语句中加入条件判断,直接通过一个 SQL 语句完成库存的扣减操作。这种方式依赖 MySQL 的原子性,避免了显式锁的使用。示例:
1. START TRANSACTION; -- 锁定库存行,防止其他事务修改 SELECT stock FROM products WHERE product_id = 1 FOR UPDATE; -- 假设库存足够,进行扣减 UPDATE products SET stock = stock - 1 WHERE product_id = 1 AND stock > 0; -- 如果更新成功,表示库存充足,操作完成 COMMIT; 2. START TRANSACTION; -- 获取库存信息以及版本号 SELECT stock, version FROM products WHERE product_id = 1; -- 更新时判断版本号是否未被修改 UPDATE products SET stock = stock - 1, version = version + 1 WHERE product_id = 1 AND version = 1 AND stock > 0; -- 如果影响行数为 1,则更新成功 COMMIT; 3. UPDATE products SET stock = stock - 1 WHERE product_id = 1 AND stock > 0;
20. B+ 树和 B 树的区别
1. 数据存储位置
-
B树:内部节点和叶子节点都存储 数据和键。
-
B+树:所有的 数据都存储在叶子节点,内部节点仅存储键,用于索引。内部节点不存储实际的数据,这样树的高度可以保持更低。
2. 叶子节点的链表结构
-
B树:叶子节点之间没有额外的链接,B树中没有叶子节点的链表结构。
-
B+树:所有的叶子节点通过一个链表进行 顺序链接。这使得在进行区间查询和范围查询时,B+树效率更高,因为可以直接从某个叶子节点顺序遍历数据。
3. 树的高度
-
B树:由于每个节点都存储数据,当数据量较大时,B树的高度相对较高。
-
B+树:B+树可以在同样大小的节点中存储更多的键,使得树的分支更多,密度更高,导致树的高度更低,这样的结构可以减少磁盘 I/O,提高查询效率。
4. 查询效率
-
B树:在 B树中,数据可能存储在内部节点或叶子节点,因此查找数据时可能需要遍历到非叶子节点的某个位置即可终止查找。查询效率相对不稳定,尤其在插入或删除数据后,数据分布可能影响查询路径。
-
B+树:所有数据都存储在叶子节点,查找数据时必须遍历到叶子节点才能找到实际数据。由于树的高度较低,整体查询效率更高、更稳定。