阿里規(guī)定超過(guò)3張表禁止JOIN,為啥?
圖片來(lái)自 包圖網(wǎng)
問(wèn)題分析
對(duì)這個(gè)結(jié)論,你是否有懷疑呢?也不知道是哪位先哲說(shuō)的不要人云亦云,今天我設(shè)計(jì) sql,來(lái)驗(yàn)證這個(gè)結(jié)論。(實(shí)驗(yàn)沒(méi)有從代碼角度分析,目前達(dá)不到。可以把 MySQL 當(dāng)一個(gè)黑盒,使用角度來(lái)驗(yàn)證這個(gè)結(jié)論)
驗(yàn)證結(jié)論的時(shí)候,會(huì)有很多發(fā)現(xiàn),各位往后看。
實(shí)驗(yàn)環(huán)境
VMware 10+Centos 7.4+MySQL 5.7.22 ,Centos 7 內(nèi)存 4.5G,4 核,50G 硬盤(pán)。MySQL 配置為 2G,特別說(shuō)明硬盤(pán)是 SSD。
我的實(shí)驗(yàn)
有 4 張表,student 學(xué)生表,teacher 老師表,course 課程表,sc 中間關(guān)系表,記錄了學(xué)生選修課程以及分?jǐn)?shù)。
具體 sql 腳本,看文章結(jié)尾,我附上。中間我自己寫(xiě)了造數(shù)據(jù)的腳本,也在結(jié)尾。
實(shí)驗(yàn)是為解決一個(gè)問(wèn)題的:查詢(xún)選修“tname553”老師所授課程的學(xué)生中,成績(jī)最高的學(xué)生姓名及其成績(jī) 。
查詢(xún) 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);
我來(lái)分析一下這個(gè)語(yǔ)句:4 張表等值 join,還有一個(gè)子查詢(xún)。算是比較簡(jiǎn)單的 sql 語(yǔ)句了(相比 ERP 動(dòng)就 10 張表的哦,已經(jīng)很簡(jiǎn)單了)。
我還會(huì)分解這個(gè)語(yǔ)句成 3 個(gè)簡(jiǎn)單的 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;
我來(lái)分析下:第一句,就是查詢(xún)最高分,得到最高分 590 分。第二句就是查詢(xún)出最高分的學(xué)生 id,得到:
- 20769800,48525000,26280200
第三句就是查詢(xún)出學(xué)生名字和分?jǐn)?shù)。這樣這 3 個(gè)語(yǔ)句的就可以查詢(xún)出來(lái)成績(jī)最高的學(xué)生姓名及其成績(jī)。
接下來(lái)我會(huì)分別造數(shù)據(jù):1 千萬(wàn)選課記錄(一個(gè)學(xué)生選修 2 門(mén)課),造 500 萬(wàn)學(xué)生,100 萬(wàn)老師(一個(gè)老師帶 5 個(gè)學(xué)生,挺高端的吧),1000 門(mén)課。
用上面查詢(xún)語(yǔ)句查詢(xún)。其中 sc 表我測(cè)試了下有索引和沒(méi)有索引情況,具體見(jiàn)下表。
再接下來(lái),我會(huì)造 1 億選課記錄(一個(gè)學(xué)生選修 2 門(mén)課)5000 萬(wàn)學(xué)生,1000 萬(wàn)老師,1000 門(mén)課。然后分別執(zhí)行上述語(yǔ)句。最后我會(huì)在 oracle 數(shù)據(jù)庫(kù)上執(zhí)行上述語(yǔ)句。
下面兩張表是測(cè)試結(jié)果:
仔細(xì)看上表,可以發(fā)現(xiàn)?
①步驟 3.1 沒(méi)有在連接鍵上加索引,查詢(xún)很慢,說(shuō)明:“多表關(guān)聯(lián)查詢(xún)時(shí),保證被關(guān)聯(lián)的字段需要有索引”。
②步驟 6.1,6.2,6.3,換成簡(jiǎn)單 sql,在數(shù)據(jù)量 1 億以上, 查詢(xún)時(shí)間還能勉強(qiáng)接受。此時(shí)說(shuō)明 MySQL 查詢(xún)有些吃力了,但是仍然嫩查詢(xún)出來(lái)。
③步驟 5.1,MySQL 查詢(xún)不出來(lái),4 表連接,對(duì)我本機(jī) MySQL 來(lái)說(shuō),1.5 億數(shù)據(jù)超過(guò)極限了(我調(diào)優(yōu)過(guò)這個(gè) SQL,執(zhí)行計(jì)劃和索引都走了,沒(méi)有問(wèn)題,show profile 顯示在 sending data,這個(gè)問(wèn)題另外文章詳談)。
④對(duì)比 1.1 和 5.1 步驟 sql 查詢(xún),4 表連接,對(duì)我本機(jī) MySQL 來(lái)說(shuō) ,1.5 千萬(wàn)數(shù)據(jù)查詢(xún)很流利,是一個(gè) MySQL 數(shù)據(jù)量流利分水嶺。(這個(gè)只是現(xiàn)象,不太準(zhǔn)確,需要同時(shí)計(jì)算表的容量)。
⑤步驟 5.1 對(duì)比 6.1,6.2,6.3,多表 join 對(duì) MySQL 來(lái)說(shuō),處理有些吃力。
⑥超過(guò)三張表禁止 join,這個(gè)規(guī)則是針對(duì) MySQL 來(lái)說(shuō)的。后續(xù)會(huì)看到我用同樣機(jī)器,同樣數(shù)據(jù)量,同樣內(nèi)存,可以完美計(jì)算 1.5 億數(shù)據(jù)量 join。
針對(duì)這樣一個(gè)規(guī)則,對(duì)開(kāi)發(fā)來(lái)說(shuō) ,需要把一些邏輯放到應(yīng)用層去查詢(xún)。
總結(jié):這個(gè)規(guī)則,超過(guò)三張表禁止 join,由于數(shù)據(jù)量太大的時(shí)候,MySQL 根本查詢(xún)不出來(lái),導(dǎo)致阿里出了這樣一個(gè)規(guī)定。
其實(shí)如果表數(shù)據(jù)量少,10 張表也不成問(wèn)題,你自己可以試試。而我們公司支付系統(tǒng)朝著大規(guī)模高并發(fā)目標(biāo)設(shè)計(jì)的,所以,遵循這個(gè)規(guī)定。
在業(yè)務(wù)層面來(lái)講,寫(xiě)簡(jiǎn)單 sql,把更多邏輯放到應(yīng)用層,我的需求我會(huì)更了解,在應(yīng)用層實(shí)現(xiàn)特定的 join 也容易得多。
讓我們來(lái)看看 oracle 數(shù)據(jù)庫(kù)的優(yōu)秀表現(xiàn):
看步驟 7.1,就是沒(méi)有索引,join 表很多的情況下,oracle 仍然 26 秒查詢(xún)出結(jié)果來(lái)。所以我會(huì)說(shuō) MySQL 的 join 很弱。
那么問(wèn)題來(lái)了,為什么現(xiàn)在使用很多人使用 MySQL 呢?這是另外一個(gè)問(wèn)題,我會(huì)另外說(shuō)下我的思考。
看完本篇文章,另外我還附加贈(zèng)送,所謂摟草打兔子。就是快速造數(shù)據(jù)。你可以自己先寫(xiě)腳本造數(shù)據(jù),看看我是怎么造數(shù)據(jù)的,就知道我的技巧了。
附上部分截圖:
附上 sql 語(yǔ)句和造數(shù)據(jù)腳本:
- 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;
這個(gè)是 oracle 的測(cè)試和造數(shù)據(jù)腳本:
- 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/







































