08-高性能表结构及索引设计最佳实践-05

本文探讨了磁盘的工作原理及其对数据库性能的影响,详细解释了为何B+树成为关系型数据库索引的首选结构。文章还介绍了磁盘读写的特性,包括预读机制和局部性原理,并分析了这些因素如何影响B+树的设计。

补充资料:磁盘和B+树

为什么关系型数据库都选择了B+树,这个和磁盘的特性有着非常大的关系。
在这里插入图片描述
一个磁盘由大小相同且同轴的圆形盘片组成,磁盘可以转动(各个磁盘必须同步转动)。在磁盘的一侧有磁头支架,磁头支架固定了一组磁头,每个磁头负责存取一个磁盘的内容。磁头不能转动,但是可以沿磁盘半径方向运动。盘片被划分成一系列同心环,圆心是盘片中心,每个同心环叫做一个磁道,所有半径相同的磁道组成一个柱面。磁道被沿半径线划分成一个个小的段,每个段叫做一个扇区,每个扇区是磁盘的最小存储单元也是最小读写单元。现在磁盘扇区一般是512个字节~4k个字节。
磁盘上数据必须用一个三维地址唯一标示:柱面号、盘面号、扇区号。读/写磁盘上某一指定数据需要下面步骤:
(1) 首先移动臂根据柱面号使磁头移动到所需要的柱面上,这一过程被称为定位或查找。
(2)所有磁头都定位到磁道上后,这时根据盘面号来确定指定盘面上的具体磁道。
(3) 盘面确定以后,盘片开始旋转,将指定块号的磁道段移动至磁头下。
经过上面步骤,指定数据的存储位置就被找到。这时就可以开始读/写操作了。

在这里插入图片描述

可以看见,磁盘读取依靠的是机械运动,分为寻道时间、旋转延迟、传输时间三个部分,这三个部分耗时相加就是一次磁盘IO的时间,一般大概9ms左右。寻道时间(seek)是将读写磁头移动至正确的磁道上所需要的时间,这部分时间代价最高;旋转延迟时间(rotation)是磁盘旋转将目标扇区移动到读写磁头下方所需的时间,取决于磁盘转速;数据传输时间(transfer)是完成传输数据所需要的时间,取决于接口的数据传输率,在纳秒级,远小于前两部分消耗时间。磁盘读取时间成本是访问内存的几百倍到几万倍之间。
为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存,这个称之为预读。这样做的理论依据是计算机科学中著名的局部性原理:
当一个数据被用到时,其附近的数据也通常会马上被使用。
程序运行期间所需要的数据通常比较集中。

大家可以试运行下面这段代码:

public static void main(String[] args) {
	int[][] arr = new int[10000][10000];
	int sum = 0;
	long startTime = System.currentTimeMillis();
	for (int i = 0; i < arr.length; i++) {
		for (int j = 0; j < arr[0].length; j++) {
			/*按行访问数组*/
			sum += arr[i][j];
		}
	}
	System.out.println("按行耗时:" + (System.currentTimeMillis() - startTime) + "ms");
	sum = 0;
	startTime = System.currentTimeMillis();
	for (int i = 0; i < arr.length; i++) {
		for (int j = 0; j < arr[0].length; j++) {
		/*按列访问数组*/
		sum += arr[j][i];
		}
	}
	System.out.println("按列耗时:" + (System.currentTimeMillis() - startTime) + "ms");
}

在这里插入图片描述

就能看到局部性原理对程序性能的影响。
由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),一般来说,磁盘的顺序读的效率是随机读的40到400倍都有可能,顺序写是随机写的10到100倍(SSD盘则差距要小的多,顺序读写的效率是随机读写的7到10倍,但是有评测表明机械硬盘的顺序写性能稍优于SSD。总的来说Mysql数据库如果由硬盘由机械的换成SSD的,性能会有很大的提升),因此对于具有局部性的程序来说,预读可以提高I/O效率。预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页,页大小通常为4k当然也有16K的,主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。
按照磁盘的这种性质,如果是一个页存放一个B+树的节点,自然是可以存放很多的数据的,比如InnoDB里,默认定义的B+树的节点大小是16KB,这就是说,假如一个Key是8个字节,那么一个节点可以存放大约1000个Key,意味着B+数可以有1000个分叉。同时InnoDB每一次磁盘I/O,读取的都是 16KB的整数倍的数据。也就是说InnoDB在节点的读写上是可以充分利用磁盘顺序IO的高速读写特性。同时按照B+树逻辑结构来说,在叶子节点一层,所有记录的主键按照从小到大的顺序排列,并且形成了一个双向链表。同一层的非叶子节点也互相串联,形成了一个双向链表。那么在实际读写的时候,很大的概率相邻的节点会放在相邻的页上,又可以充分利用磁盘顺序IO的高速读写特性。所以我们对MySQL优化的一大方向就是尽可能的多让数据顺序读写,少让数据随机读写。

<think>我们正在设计一个菜单表结构,需要遵循数据库设计最佳实践。根据引用内容,我们可以参考数据库设计的基础理论和关键技术,以及实际应用场景。菜单表通常用于存储系统中的菜单项,比如导航菜单、权限菜单等。设计菜单表时,需要考虑以下几点:1.菜单项的层级关系(父子关系)2.菜单的排序3.菜单的元数据(名称、图标、路径等)4.菜单的权限控制(可选,如果与权限关联)常见的菜单表结构设计有两种方式:方式一:邻接表(AdjacencyList)这是最简单的树形结构存储方式,每个菜单项存储其父菜单的ID。优点:结构简单,操作方便。缺点:递归查询效率较低(在MySQL8.0之前需要使用递归查询或多次查询,MySQL8.0支持CTE递归查询)。方式二:嵌套集(NestedSet)每个菜单项存储左值和右值,通过左右值来确定层级关系。优点:查询子树和祖先路径效率高。缺点:插入和删除节点时需要调整多个节点的左右值,操作复杂。在实际应用中,邻接表因为简单而被广泛采用,特别是当菜单层级不深(如3-4级)且数据量不大时。此外,还可以考虑使用闭包表(ClosureTable),它通过一个额外的表来存储节点之间的关系,从而解决邻接表递归查询效率低的问题。但是,根据最佳实践,对于菜单这种通常层级不深且变动不频繁的数据,邻接表是足够且易于维护的。因此,我们可以设计一个使用邻接表方式的菜单表:表名:`menu`字段:-`id`:主键,自增整数-`parent_id`:父菜单ID,外键引用本表的id,可为NULL(表示顶级菜单)-`name`:菜单名称,VARCHAR(50)-`path`:菜单对应的路径(或路由),VARCHAR(255),可为NULL(如果是分组菜单)-`icon`:菜单图标,VARCHAR(50),可为NULL-`sort_order`:排序权重,INT,用于同级菜单的排序-`is_enabled`:是否启用,BOOLEAN,默认为1(启用)-`created_at`:创建时间,TIMESTAMP-`updated_at`:更新时间,TIMESTAMP此外,如果需要权限控制,可以关联权限表,比如每个菜单项可以关联一个权限标识(permission_code),或者通过角色-菜单关联表来实现。示例SQL创建语句:```sqlCREATETABLEmenu(idINTAUTO_INCREMENTPRIMARYKEY,parent_idINTNULL,nameVARCHAR(50)NOTNULL,pathVARCHAR(255)NULL,iconVARCHAR(50)NULL,sort_orderINTDEFAULT0,is_enabledBOOLEANDEFAULT1,created_atTIMESTAMPDEFAULTCURRENT_TIMESTAMP,updated_atTIMESTAMPDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,FOREIGNKEY(parent_id)REFERENCESmenu(id)ONDELETECASCADE);```注意:-外键约束`ONDELETECASCADE`表示当父菜单被删除时,子菜单自动删除。-如果使用InnoDB引擎,外键约束才有效。查询所有菜单并按层级和排序权重展示:在MySQL8.0以上,可以使用递归CTE(CommonTableExpression)查询:```sqlWITHRECURSIVEmenu_treeAS(SELECTid,parent_id,name,path,icon,sort_order,1ASlevelFROMmenuWHEREparent_idISNULLUNIONALLSELECTm.id,m.parent_id,m.name,m.path,m.icon,m.sort_order,mt.level+1FROMmenumINNERJOINmenu_treemtONmt.id=m.parent_id)SELECT*FROMmenu_treeORDERBYlevel,sort_order;```在MySQL8.0以下,通常需要在应用层进行递归查询(多次查询,然后构建树形结构)。另外,如果菜单需要支持多语言,那么还需要设计菜单名称的多语言表,这里不展开。总结:菜单表结构设计采用邻接表模式是常见且实用的做法。同时,通过外键约束保证数据完整性,通过递归查询(或应用层构建树)来获取层级结构。参考引用:[^1]提到数据库设计的基础理论和关键技术,以及实际应用场景。[^2]提供了一个数据库设计的实例(个人博客数据管理系统),其中包含了表结构设计。[^3]介绍了使用MySQLWorkbench进行数据库设计与管理,我们可以使用该工具来设计并生成菜单表。根据以上设计,我们可以满足菜单的基本管理需求。§§相关问题§§1.菜单表如何支持无限层级?2.在菜单表设计中,如何优化递归查询的性能?3.菜单表与权限系统如何关联设计?</think>### 数据库菜单表结构设计最佳实践设计菜单表结构时,需考虑**层级关系、权限控制、灵活扩展**等核心需求。以下是经过验证的最佳实践方案: #### 基础表结构设计(邻接表模型) ```sql CREATE TABLE sys_menu ( menu_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '菜单ID', parent_id INT DEFAULT 0 COMMENT '父菜单ID(0表示根节点)', menu_name VARCHAR(50) NOT NULL COMMENT '菜单名称', menu_path VARCHAR(255) COMMENT '路由路径', menu_icon VARCHAR(50) COMMENT '图标类名', sort_order INT DEFAULT 0 COMMENT '排序权重', is_visible TINYINT(1) DEFAULT 1 COMMENT '是否可见', permission_tag VARCHAR(50) COMMENT '权限标识', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_parent (parent_id), INDEX idx_sort (sort_order) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` **关键字段说明:** - `parent_id`:实现层级结构的基础字段,0表示顶级菜单 - `sort_order`:控制同级菜单的显示顺序 - `permission_tag`:关联权限系统的标识符(如`system:user:view`) - `is_visible`:动态控制菜单显示状态 #### 高级优化方案 1. **闭包表优化深层级查询** ```sql -- 路径关系表 CREATE TABLE menu_closure ( ancestor INT NOT NULL COMMENT '祖先节点', descendant INT NOT NULL COMMENT '后代节点', depth INT NOT NULL COMMENT '层级深度', PRIMARY KEY (ancestor, descendant), FOREIGN KEY (ancestor) REFERENCES sys_menu(menu_id), FOREIGN KEY (descendant) REFERENCES sys_menu(menu_id) ); ``` **优势**:解决邻接表递归查询性能问题,特别适合超过3层的深菜单树[^1] 2. **多语言支持设计** ```sql CREATE TABLE menu_i18n ( menu_id INT NOT NULL, lang_code CHAR(2) NOT NULL COMMENT '语言代码', display_name VARCHAR(100) NOT NULL COMMENT '本地化名称', PRIMARY KEY (menu_id, lang_code) ); ``` #### 设计原则 1. **层级控制**:建议不超过5级,超过3级时添加面包屑导航字段 2. **权限分离**:菜单表只存储权限标识,具体验证逻辑在业务层实现 3. **类型区分**:添加`menu_type`字段(如:0-菜单项,1-功能按钮) 4. **元数据扩展**:使用JSON字段存储动态配置(如:`extra_config JSON`) #### 查询示例 获取用户可见菜单树(MySQL 8.0+ 递归查询): ```sql WITH RECURSIVE menu_tree AS ( SELECT * FROM sys_menu WHERE parent_id = 0 AND is_visible = 1 UNION ALL SELECT m.* FROM sys_menu m INNER JOIN menu_tree mt ON m.parent_id = mt.menu_id WHERE m.is_visible = 1 ) SELECT * FROM menu_tree ORDER BY sort_order; ``` #### 性能优化建议 1. 为`parent_id`和`sort_order`创建组合索引 2. 高频访问系统添加菜单缓存层(Redis/Memcached) 3. 定期执行`OPTIMIZE TABLE`维护索引碎片 > 使用MySQL Workbench可视化设计时,可通过EER图建立表关系,并自动生成DDL脚本[^3]。实际部署前建议用工具(如pt-query-digest)进行压力测试。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值