阿里規定超過3張表禁止JOIN,為啥?
圖片來自 包圖網
問題分析
對這個結論,你是否有懷疑呢?也不知道是哪位先哲說的不要人云亦云,今天我設計 sql,來驗證這個結論。(實驗沒有從代碼角度分析,目前達不到。可以把 MySQL 當一個黑盒,使用角度來驗證這個結論)
驗證結論的時候,會有很多發現,各位往后看。
實驗環境
VMware 10+Centos 7.4+MySQL 5.7.22 ,Centos 7 內存 4.5G,4 核,50G 硬盤。MySQL 配置為 2G,特別說明硬盤是 SSD。
我的實驗
有 4 張表,student 學生表,teacher 老師表,course 課程表,sc 中間關系表,記錄了學生選修課程以及分數。
具體 sql 腳本,看文章結尾,我附上。中間我自己寫了造數據的腳本,也在結尾。
實驗是為解決一個問題的:查詢選修“tname553”老師所授課程的學生中,成績最高的學生姓名及其成績 。
查詢 sql 是:
- select Student.Sname,course.cname,score
- from Student,SC,Course ,Teacher
- where Student.s_id=SC.s_id and SC.c_id=Course.c_id and sc.t_id=teacher.t_id
- and Teacher.Tname='tname553'
- and SC.score=(select max(score)from SC where sc.t_id=teacher.t_Id);
我來分析一下這個語句:4 張表等值 join,還有一個子查詢。算是比較簡單的 sql 語句了(相比 ERP 動就 10 張表的哦,已經很簡單了)。
我還會分解這個語句成 3 個簡單的 sql:
- select max(score) from SC ,Teacher where sc.t_id=teacher.t_Id and Teacher.Tname='tname553';
- select sc.t_id,sc.s_id,score from SC ,Teacher
- where sc.t_id=teacher.t_Id
- and score=590
- and Teacher.Tname='tname553';
- select Student.Sname,course.cname,score
- from Student,SC ,course
- where Student.s_id=SC.s_id and sc.s_id in (20769800,48525000,26280200) and course.c_id = sc.c_id;
我來分析下:第一句,就是查詢最高分,得到最高分 590 分。第二句就是查詢出最高分的學生 id,得到:
- 20769800,48525000,26280200
第三句就是查詢出學生名字和分數。這樣這 3 個語句的就可以查詢出來成績最高的學生姓名及其成績。
接下來我會分別造數據:1 千萬選課記錄(一個學生選修 2 門課),造 500 萬學生,100 萬老師(一個老師帶 5 個學生,挺高端的吧),1000 門課。
用上面查詢語句查詢。其中 sc 表我測試了下有索引和沒有索引情況,具體見下表。
再接下來,我會造 1 億選課記錄(一個學生選修 2 門課)5000 萬學生,1000 萬老師,1000 門課。然后分別執行上述語句。最后我會在 oracle 數據庫上執行上述語句。
下面兩張表是測試結果:
仔細看上表,可以發現?
①步驟 3.1 沒有在連接鍵上加索引,查詢很慢,說明:“多表關聯查詢時,保證被關聯的字段需要有索引”。
②步驟 6.1,6.2,6.3,換成簡單 sql,在數據量 1 億以上, 查詢時間還能勉強接受。此時說明 MySQL 查詢有些吃力了,但是仍然嫩查詢出來。
③步驟 5.1,MySQL 查詢不出來,4 表連接,對我本機 MySQL 來說,1.5 億數據超過極限了(我調優過這個 SQL,執行計劃和索引都走了,沒有問題,show profile 顯示在 sending data,這個問題另外文章詳談)。
④對比 1.1 和 5.1 步驟 sql 查詢,4 表連接,對我本機 MySQL 來說 ,1.5 千萬數據查詢很流利,是一個 MySQL 數據量流利分水嶺。(這個只是現象,不太準確,需要同時計算表的容量)。
⑤步驟 5.1 對比 6.1,6.2,6.3,多表 join 對 MySQL 來說,處理有些吃力。
⑥超過三張表禁止 join,這個規則是針對 MySQL 來說的。后續會看到我用同樣機器,同樣數據量,同樣內存,可以完美計算 1.5 億數據量 join。
針對這樣一個規則,對開發來說 ,需要把一些邏輯放到應用層去查詢。
總結:這個規則,超過三張表禁止 join,由于數據量太大的時候,MySQL 根本查詢不出來,導致阿里出了這樣一個規定。
其實如果表數據量少,10 張表也不成問題,你自己可以試試。而我們公司支付系統朝著大規模高并發目標設計的,所以,遵循這個規定。
在業務層面來講,寫簡單 sql,把更多邏輯放到應用層,我的需求我會更了解,在應用層實現特定的 join 也容易得多。
讓我們來看看 oracle 數據庫的優秀表現:
看步驟 7.1,就是沒有索引,join 表很多的情況下,oracle 仍然 26 秒查詢出結果來。所以我會說 MySQL 的 join 很弱。
那么問題來了,為什么現在使用很多人使用 MySQL 呢?這是另外一個問題,我會另外說下我的思考。
看完本篇文章,另外我還附加贈送,所謂摟草打兔子。就是快速造數據。你可以自己先寫腳本造數據,看看我是怎么造數據的,就知道我的技巧了。
附上部分截圖:
附上 sql 語句和造數據腳本:
- use stu;
- drop table if exists student;
- create table student
- ( s_id int(11) not null auto_increment ,
- sno int(11),
- sname varchar(50),
- sage int(11),
- ssex varchar(8) ,
- father_id int(11),
- mather_id int(11),
- note varchar(500),
- primary key (s_id),
- unique key uk_sno (sno)
- ) engine=innodb default charset=utf8mb4;
- truncate table student;
- delimiter $$
- drop function if exists insert_student_data $$
- create function insert_student_data()
- returns int deterministic
- begin
- declare i int;
- set i=1;
- while i<50000000 do
- insert into student values(i ,i, concat('name',i),i,case when floor(rand()*10)%2=0 then 'f' else 'm' end,floor(rand()*100000),floor(rand()*1000000),concat('note',i) );
- set i=i+1;
- end while;
- return 1;
- end$$
- delimiter ;
- select insert_student_data();
- select count(*) from student;
- use stu;
- create table course
- (
- c_id int(11) not null auto_increment ,
- cname varchar(50)
- note varchar(500), primary key (c_id)
- ) engine=innodb default charset=utf8mb4;
- truncate table course;
- delimiter $$
- drop function if exists insert_course_data $$
- create function insert_course_data()
- returns int deterministic
- begin
- declare i int;
- set i=1;
- while i<=1000 do
- insert into course values(i , concat('course',i),floor(rand()*1000),concat('note',i) );
- set i=i+1;
- end while;
- return 1;
- end$$
- delimiter ;
- select insert_course_data();
- select count(*) from course;
- use stu;
- drop table if exists sc;
- create table sc
- (
- s_id int(11),
- c_id int(11),
- t_id int(11),
- score int(11)
- ) engine=innodb default charset=utf8mb4;
- truncate table sc;
- delimiter $$
- drop function if exists insert_sc_data $$
- create function insert_sc_data()
- returns int deterministic
- begin
- declare i int;
- set i=1;
- while i<=50000000 do
- insert into sc values( i,floor(rand()*1000),floor(rand()*10000000),floor(rand()*750)) ;
- set i=i+1;
- end while;
- return 1;
- end$$
- delimiter ;
- select insert_sc_data();
- commit;
- select insert_sc_data();
- commit;
- create index idx_s_id on sc(s_id) ;
- create index idx_t_id on sc(t_id) ;
- create index idx_c_id on sc(c_id) ;
- select count(*) from sc;
- use stu;
- drop table if exists teacher;
- create table teacher
- (
- t_id int(11) not null auto_increment ,
- tname varchar(50) ,
- note varchar(500),primary key (t_id)
- ) engine=innodb default charset=utf8mb4;
- truncate table teacher;
- delimiter $$
- drop function if exists insert_teacher_data $$
- create function insert_teacher_data()
- returns int deterministic
- begin
- declare i int;
- set i=1;
- while i<=10000000 do
- insert into teacher values(i , concat('tname',i),concat('note',i) );
- set i=i+1;
- end while;
- return 1;
- end$$
- delimiter ;
- select insert_teacher_data();
- commit;
- select count(*) from teacher;
這個是 oracle 的測試和造數據腳本:
- create tablespace scott_data datafile '/home/oracle/oracle_space/sitpay1/scott_data.dbf' size 1024m autoextend on;
- create tablespace scott_index datafile '/home/oracle/oracle_space/sitpay1/scott_index.dbf' size 64m autoextend on;
- create temporary tablespace scott_temp tempfile '/home/oracle/oracle_space/sitpay1/scott_temp.dbf' size 64m autoextend on;
- drop user scott cascade;
- create user scott identified by tiger default tablespace scott_data temporary tablespace scott_temp ;
- grant resource,connect,dba to scott;
- drop table student;
- create table student
- ( s_id number(11) ,
- sno number(11) ,
- sname varchar2(50),
- sage number(11),
- ssex varchar2(8) ,
- father_id number(11),
- mather_id number(11),
- note varchar2(500)
- ) nologging;
- truncate table student;
- create or replace procedure insert_student_data
- is
- q number(11);
- begin
- q:=0;
- for i in 1..50 loop
- insert /*+append*/ into student select rownum+q as s_id,rownum+q as sno, concat('sutdent',rownum+q ) as sname,floor(dbms_random.value(1,100)) as sage,'f' as ssex,rownum+q as father_id,rownum+q as mather_id,concat('note',rownum+q ) as note from dual connect by level<=1000000;
- q:=q+1000000;
- commit;
- end loop;
- end insert_student_data;
- /
- call insert_student_data();
- alter table student add constraint pk_student primary key (s_id);
- commit;
- select count(*) from student;
- create table course
- (
- c_id number(11) primary key,
- cname varchar2(50),
- note varchar2(500)
- ) ;
- truncate table course;
- create or replace procedure insert_course_data
- is
- q number(11);
- begin
- for i in 1..1000 loop
- insert /*+append*/ into course values(i , concat('name',i),concat('note',i) );
- end loop;
- end insert_course_data;
- /
- call insert_course_data();
- commit;
- select count(*) from course;
- create table sc
- (
- s_id number(11),
- c_id number(11),
- t_id number(11),
- score number(11)
- ) nologging;
- truncate table sc;
- create or replace procedure insert_sc_data
- is
- q number(11);
- begin
- q:=0;
- for i in 1..50 loop
- insert /*+append*/ into sc select rownum+q as s_id, floor(dbms_random.value(0,1000)) as c_id,floor(dbms_random.value(0,10000000)) t_id,floor(dbms_random.value(0,750)) as score from dual connect by level<=1000000;
- q:=q+1000000;
- commit;
- end loop;
- end insert_sc_data;
- /
- call insert_sc_data();
- create index idx_s_id on sc(s_id) ;
- create index idx_t_id on sc(t_id) ;
- create index idx_c_id on sc(c_id) ;
- select count(*) from sc;
- create table teacher
- (
- t_id number(11) ,
- tname varchar2(50) ,
- note varchar2(500)
- )nologging ;
- truncate table teacher;
- create or replace procedure insert_teacher_data
- is
- q number(11);
- begin
- q:=0;
- for i in 1..10 loop
- insert /*+append*/ into teacher select rownum+q as t_id, concat('teacher',rownum+q ) as tname,concat('note',rownum+q ) as note from dual connect by level<=1000000;
- q:=q+1000000;
- commit;
- end loop;
- end insert_teacher_data;
- /
- call insert_teacher_data();
- alter table teacher add constraint pk_teacher primary key (t_id);
- select count(*) from teacher;
作者:e71hao
編輯:陶家龍
出處:blog.itpub.net/30393770/viewspace-2650450/