可以使用以下SQL语句来查询课程不同但成绩相同的记录:
SELECT t1.* FROM 表名 t1
INNER JOIN 表名 t2 ON t1.成绩 = t2.成绩 AND t1.课程 <> t2.课程
其中,表名需要替换为实际的表名,成绩和课程需要替换为实际的字段名。这条SQL语句使用了内连接和条件查询的方式,将成绩相同但课程不同的记录进行连接并筛选出来。
那肯定是表数据的底层,B+树了,看过上一篇的小伙伴应该知道,InnoDB存储结构是表空间,区,页,行。
上一篇中,我们已经知道了,B+树的数据存储在叶子结点,非叶子结点存储指针和主键,三层B+树就可以存储二千多万条数据,但这是在行数据小于1k的情况下的。
看看这张表,一行行数据紧挨在一起,实际上,在ibd文件中,他们是分成数据页,一个数据页大小是16k。
那我们知道,页空间大小固定为16kb,行数据大小有时候是超过1K的,这个时候,一行数据就会分开放在很多页里,产生页分裂,为了标识这一行数据具体在哪一页,就需要引入页号,实际上就是一个内存地址偏移量。
同时为了把这些数据页给关联起来,于是引入了前后指针,用于指向前后的页。这些都被加到了页头里。
页是需要读写的,16k写一半电源线被拔了也是有可能发生的,所以为了保证数据页的正确性,还引入了校验码。这个被加到了页尾。
页头放完指针,页尾放完校验码,剩下的空间,才是用来放我们的行数据的。
而如果行数特别多的话,进入到页内时挨个遍历,效率也不太行,所以为这些数据生成了一个页目录,具体实现后面再和大家聊。现在只需要知道,它可以通过二分查找的方式将查找效率从O(n) 变成O(lgn)。
如果想要查询一行数据,我们可以把每一页都捞出来,然后挨个去判断表中数据量小,分成的页比较少的时候,这样做没问题。
行数据量大了,性能就会变慢,为了加快速度,每个数据页选出主键id最小的数据,只要他们的主键id和页号,放入到一个新生成的一个数据页中,这个新数据页跟之前的页结构没啥区别,而且大小还是16k。
行总数计算
这里记住这样一个公式, (x ^ (z-1)) * y 。
已知x=1170,也就是一层B+树的指针数量,y=15,这个是指,除去页头页尾,页目录占据的内存空间后,存储数据可用空间。
假设B+树是两层,那z=2。则是(1170^ (2-1)) * 15 ≈ 1.7w
假设B+树是三层,那z=3。则是(1170 ^ (3-1)) * 15 ≈ 2.0kw
这个2.0kw,就是我们常说的单表建议最大行数2kw的由来。毕竟再加一层,数据就大得有点离谱了。三层数据页对应最多三次磁盘IO,也比较合理。
最后,解答一下上一篇提出的问题。
如果我单行数据用不了这么多,用不到1kb,比如只用了250byte。那么单个数据页能放大约65行数据。
那同样是三层B+树,单表支持的行数就是 (1170 ^ (3-1)) * 65 ≈ 九千多万,四舍五入,就是一个亿。
如果课程和成绩都在同一个表里,那么可以使用 GROUP BY 和 HAVING 子句来查询课程不同但成绩相同的记录。具体的 SQL 语句如下:
SELECT course, score
FROM table_name
GROUP BY course, score
HAVING COUNT(DISTINCT course) = 1;
以上 SQL 语句的作用是对表 table_name
中的记录按照 course
和 score
字段进行分组,并统计每个分组中不同的 course
数量。然后使用 HAVING 子句筛选出仅有一个 course
的分组,即课程不同但成绩相同的记录。
需要注意的是,以上 SQL 语句假设每个学生只能选修一门课程,如果存在一个学生选修了多门课程且成绩相同,那么这样的记录也会被查询出来。如果需要排除这种情况,可以在 GROUP BY 子句中增加学生 ID 字段,并在 HAVING 子句中统计每个分组中不同的学生数量。