数据库优化及锁机制详解
数据库对象优化
优化表的数据类型
选择合适的数据类型对数据库性能影响巨大。例如,能用TINYINT
就别用INT
,因为前者只占用1个字节,而INT
占用4个字节,存储空间的节省能减少磁盘I/O。对于字符型数据,如果字段长度固定且较短,CHAR
类型比VARCHAR
更高效,因为VARCHAR
还需额外存储长度信息。
拆分表提升访问效率
- 垂直拆分:把包含很多列的宽表按业务逻辑拆分成多个窄表。比如电商订单表,将订单基础信息、订单商品详情、订单物流信息分别拆成独立表。如此一来,查询订单基础信息时,就无需读取大量无关的商品详情与物流数据,加快查询速度。
- 水平拆分:当表数据量超大时,依据某一规则(如时间、地域、用户ID范围)把数据分散到多个表。像社交平台的用户消息表,可按月份拆分,每月数据存于不同表,查询某个时间段消息时,直接定位对应表即可。
逆规范化
与规范化减少数据冗余相反,逆规范化适当引入冗余来提升查询性能。例如,在多表关联查询频繁的场景下,把常用关联字段冗余到主表,可减少复杂的连接操作,用空间换时间。
使用中间表加速统计查询
针对复杂统计需求,创建中间表预先计算、存储部分结果。例如电商平台要统计每日各品类销售总额,可每晚定时将当天订单数据聚合处理,存入中间表,后续直接查询该表,避免实时对海量订单表做聚合运算。
小结 综合运用上述优化手段,可显著提升数据库读写性能,降低系统响应时间,但要权衡空间、维护成本与性能提升之间的关系。
锁问题
MySQL锁概述
MySQL有多种锁机制,用来控制并发访问,保障数据一致性。不同存储引擎的锁实现差异很大,主流的MYISAM
和INNODB
各有特点。
MYISAM表锁
- 查询表级锁争用情况 :可通过
SHOW STATUS LIKE 'Table_locks_waited';
查看等待表锁的次数,Table_locks_immediate
则表示立即获取到表锁的次数,二者比值能反映表锁争用程度。 - MySQL表级锁的锁模式 :
MYISAM
有读锁(共享锁)和写锁(排他锁) 。读锁允许多个线程同时读取,但写锁会阻塞其他读、写操作。 - 如何加表锁 :加读锁用
LOCK TABLES table_name READ;
,加写锁则是LOCK TABLES table_name WRITE;
,操作完要用UNLOCK TABLES;
释放。 - 并发插入 :
MYISAM
支持并发插入,在满足一定条件下,即便表有读锁,新数据也能插入到表尾,提升插入性能。 - MYISAM的锁调度:它的锁调度比较简单,写锁优先级高于读锁,所以读操作频繁的场景,容易因写锁等待出现阻塞。
INNODB锁问题
- 背景知识 :
INNODB
基于行锁设计,不过也有表锁辅助,在高并发场景更具优势。 - 获取INNODB行锁争用情况 :借助
INFORMATION_SCHEMA.INNODB_LOCKS
和INFORMATION_SCHEMA.INNODB_LOCK_WAITS
两张视图,能定位行锁争用的事务、锁类型与等待关系。 - INNODB的行锁模式及加锁方法 :有共享锁(
S
锁)、排他锁(X
锁)等。普通SELECT
语句加S
锁,UPDATE
、DELETE
则加X
锁。可以用SELECT... FOR UPDATE
显式加排他锁,SELECT... LOCK IN SHARE MODE
加共享锁。 - INNODB行锁实现方式:通过索引实现精准锁定,若没有合适索引,会升级为表锁,性能大打折扣。
- 间隙锁 :
INNODB
为防止幻读引入间隙锁,它锁定索引记录间的间隙,不过这也可能在并发插入时导致锁争用。 - 对INNODB锁机制的影响:不合理的索引、长事务都易引发锁问题,拖慢系统。要定期清理长事务,优化索引来维持高效并发。
- INNODB在不同隔离级别下的一致性及读锁的差异 :
READ UNCOMMITTED
最低隔离级别,允许脏读,读锁限制少;READ COMMITTED
避免脏读,但不可重复读;REPEATABLE READ
是默认级别,保证可重复读,读锁管控更严格;SERIALIZABLE
最高级别,完全串行化,锁争用最严重。
以下是一些与上述数据库操作和锁相关的代码示例,以 MySQL 为例:
sql
-- 创建表
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
order_date DATE,
order_amount DECIMAL(10, 2)
);
-- 插入数据
INSERT INTO orders (customer_id, order_date, order_amount) VALUES (1, '2025-01-01', 100.00);
INSERT INTO orders (customer_id, order_date, order_amount) VALUES (2, '2025-01-02', 200.00);
-- 加 MYISAM 表读锁
LOCK TABLES orders READ;
-- 在此处可以进行读取操作,如
SELECT * FROM orders;
-- 释放锁
UNLOCK TABLES;
-- 加 MYISAM 表写锁
LOCK TABLES orders WRITE;
-- 在此处可以进行修改操作,如
UPDATE orders SET order_amount = 150.00 WHERE order_id = 1;
-- 释放锁
UNLOCK TABLES;
-- 使用 INNODB 行锁
-- 开启事务
START TRANSACTION;
-- 显式加共享锁,可用于读取,其他事务也能加共享锁,但不能加排他锁
SELECT * FROM orders WHERE order_id = 1 LOCK IN SHARE MODE;
-- 显式加排他锁,用于修改等操作,其他事务不能加共享或排他锁
SELECT * FROM orders WHERE order_id = 1 FOR UPDATE;
-- 执行修改操作
UPDATE orders SET order_amount = 120.00 WHERE order_id = 1;
-- 提交事务
COMMIT;
-- 查看 INNODB 行锁争用情况
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
-- 垂直拆分表的示例,将订单表拆分为订单信息表和订单商品表
CREATE TABLE order_info (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
order_date DATE
);
CREATE TABLE order_products (
order_product_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT,
FOREIGN KEY (order_id) REFERENCES order_info(order_id)
);
-- 水平拆分表的示例,假设按年份拆分订单表,这里创建 2025 年的订单表
CREATE TABLE orders_2025 (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
order_date DATE,
order_amount DECIMAL(10, 2)
);
-- 创建中间表用于统计查询
CREATE TABLE daily_sales_summary (
summary_date DATE PRIMARY KEY,
total_sales DECIMAL(10, 2)
);
-- 插入统计数据到中间表
INSERT INTO daily_sales_summary (summary_date, total_sales)
SELECT order_date, SUM(order_amount)
FROM orders
WHERE order_date = '2025-01-01'
GROUP BY order_date;
代码解释:
- 创建表和插入数据 :
CREATE TABLE
语句用于创建表,指定表名、列名、数据类型和约束条件(如主键)。INSERT INTO
语句向表中插入数据。
- MYISAM 表锁操作 :
LOCK TABLES orders READ;
给orders
表加读锁,允许多个事务同时读取,但不允许写操作。LOCK TABLES orders WRITE;
加写锁,阻塞其他读和写操作,确保独占修改权限。UNLOCK TABLES;
释放锁,确保锁在使用后及时释放,以免影响其他事务。
- INNODB 行锁操作 :
START TRANSACTION;
开启事务,为使用行锁提供事务环境。SELECT... LOCK IN SHARE MODE;
对指定行加共享锁,允许多个事务读取,但不允许修改。SELECT... FOR UPDATE;
加排他锁,仅允许当前事务修改数据。COMMIT;
提交事务,释放锁。
- 查看 INNODB 行锁争用情况 :
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
和SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
可查看正在使用的锁和等待锁的信息,帮助诊断锁冲突。
- 表拆分示例 :
- 垂直拆分将订单表的不同信息分到不同表,通过外键关联。
- 水平拆分按时间范围将数据分到不同表,如
orders_2025
存储 2025 年的订单。
- 中间表创建和使用 :
CREATE TABLE daily_sales_summary
创建用于存储统计数据的中间表。INSERT INTO daily_sales_summary
插入统计数据,从orders
表中聚合而来,方便后续统计查询。
这些示例涵盖了 MySQL
数据库操作、锁使用、表拆分和中间表创建的基本操作,你可根据具体场景和性能需求灵活运用,同时注意在高并发场景下优化锁使用和表结构,避免性能瓶颈。在使用锁时,确保锁的使用范围和时长合理,避免长时间占用锁影响系统性能。