面試官:一千萬(wàn)的數(shù)據(jù)霞幅,你是怎么查詢的漠吻?

面試官:一千萬(wàn)的數(shù)據(jù),你是怎么查詢的司恳?

1 先給結(jié)論

對(duì)于1千萬(wàn)的數(shù)據(jù)查詢途乃,主要關(guān)注分頁(yè)查詢過程中的性能

  • 針對(duì)偏移量大導(dǎo)致查詢速度慢:
  1. 先對(duì)查詢的字段創(chuàng)建唯一索引
  2. 根據(jù)業(yè)務(wù)需求,先定位查詢范圍(對(duì)應(yīng)主鍵id的范圍扔傅,比如大于多少耍共、小于多少、IN)
  3. 查詢時(shí)猎塞,將第2步確定的范圍作為查詢條件
  • 針對(duì)查詢數(shù)據(jù)量大的導(dǎo)致查詢速度慢:
  1. 查詢時(shí)试读,減少不需要的列,查詢效率也可以得到明顯提升
  2. 一次盡可能按需查詢較少的數(shù)據(jù)條數(shù)
  3. 借助nosql緩存數(shù)據(jù)等來(lái)減輕mysql數(shù)據(jù)庫(kù)的壓力

2 準(zhǔn)備數(shù)據(jù)

2.1 創(chuàng)建表

CREATE TABLE `user_operation_log`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `ip` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `op_data` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr3` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr4` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr5` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr6` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr7` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr8` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr9` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr10` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr11` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `attr12` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

2.2 造數(shù)據(jù)腳本

采用批量插入邢享,效率會(huì)快很多鹏往,而且每1000條數(shù)就commit,數(shù)據(jù)量太大骇塘,也會(huì)導(dǎo)致批量插入效率慢

DELIMITER ;;
CREATE DEFINER=`root`@`%` PROCEDURE `batch_insert_log`()
BEGIN
  DECLARE i INT DEFAULT 1;
  DECLARE userId INT DEFAULT 10000000;
 set @execSql = 'INSERT INTO `big_data`.`user_operation_log`(`user_id`, `ip`, `op_data`, `attr1`, `attr2`, `attr3`, `attr4`, `attr5`, `attr6`, `attr7`, `attr8`, `attr9`, `attr10`, `attr11`, `attr12`) VALUES';
 set @execData = '';
  WHILE i<=10000000 DO
   set @attr = "rand_string(50)";
  set @execData = concat(@execData, "(", userId + i, ", '110.20.169.111', '用戶登錄操作'", ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ")");
  if i % 1000 = 0
  then
     set @stmtSql = concat(@execSql, @execData,";");
    prepare stmt from @stmtSql;
    execute stmt;
    DEALLOCATE prepare stmt;
    commit;
    set @execData = "";
   else
     set @execData = concat(@execData, ",");
   end if;
  SET i=i+1;
  END WHILE;
END
DELIMITER ;
delimiter $$
create function rand_string(n INT) 
returns varchar(255) #該函數(shù)會(huì)返回一個(gè)字符串
begin 
#chars_str定義一個(gè)變量 chars_str,類型是 varchar(100),默認(rèn)值'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
 declare chars_str varchar(100) default
   'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
 declare return_str varchar(255) default '';
 declare i int default 0;
 while i < n do 
   set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));
   set i = i + 1;
   end while;
 return return_str;
end $$

2.3 執(zhí)行存儲(chǔ)過程函數(shù)

因?yàn)槟M數(shù)據(jù)流量是1000W伊履,我這電腦配置不高,耗費(fèi)了不少時(shí)間款违,應(yīng)該個(gè)把小時(shí)吧

SELECT count(1) FROM `user_operation_log`;
image-20230331163130669

2.4 普通分頁(yè)查詢

MySQL 支持 LIMIT 語(yǔ)句來(lái)選取指定的條數(shù)數(shù)據(jù)唐瀑, Oracle 可以使用 ROWNUM 來(lái)選取。

MySQL分頁(yè)查詢語(yǔ)法如下:

SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset
  • 第一個(gè)參數(shù)指定第一個(gè)返回記錄行的偏移量
  • 第二個(gè)參數(shù)指定返回記錄行的最大數(shù)目

下面我們開始測(cè)試查詢結(jié)果:

SELECT * FROM `user_operation_log` LIMIT 10000, 10;

查詢3次時(shí)間分別為:

image-20230331164216737
image-20230331164250745
image-20230331164319760

這樣看起來(lái)速度還行插爹,不過是本地?cái)?shù)據(jù)庫(kù)哄辣,速度自然快點(diǎn)。

換個(gè)角度來(lái)測(cè)試

相同偏移量赠尾,不同數(shù)據(jù)量

SELECT * FROM `user_operation_log` LIMIT 10000, 10;
SELECT * FROM `user_operation_log` LIMIT 10000, 100;
SELECT * FROM `user_operation_log` LIMIT 10000, 1000;
SELECT * FROM `user_operation_log` LIMIT 10000, 10000;
SELECT * FROM `user_operation_log` LIMIT 10000, 100000;
SELECT * FROM `user_operation_log` LIMIT 10000, 1000000;
image-20230331163554821

從上面結(jié)果可以得出結(jié)束:數(shù)據(jù)量越大力穗,花費(fèi)時(shí)間越長(zhǎng)(這不是廢話嗎???)

相同數(shù)據(jù)量气嫁,不同偏移量

SELECT * FROM `user_operation_log` LIMIT 100, 100;
SELECT * FROM `user_operation_log` LIMIT 1000, 100;
SELECT * FROM `user_operation_log` LIMIT 10000, 100;
SELECT * FROM `user_operation_log` LIMIT 100000, 100;
SELECT * FROM `user_operation_log` LIMIT 1000000, 100;
image-20230331163739605

從上面結(jié)果可以得出結(jié)束:偏移量越大当窗,花費(fèi)時(shí)間越長(zhǎng)

3 如何優(yōu)化

既然我們經(jīng)過上面一番的折騰,也得出了結(jié)論寸宵,針對(duì)上面兩個(gè)問題:偏移大崖面、數(shù)據(jù)量大元咙,我們分別著手優(yōu)化

3.1 優(yōu)化數(shù)據(jù)量大的問題

SELECT * FROM `user_operation_log` LIMIT 1, 1000000
SELECT id FROM `user_operation_log` LIMIT 1, 1000000
SELECT id, user_id, ip, op_data, attr1, attr2, attr3, attr4, attr5, attr6, attr7, attr8, attr9, attr10, attr11, attr12 FROM `user_operation_log` LIMIT 1, 1000000

查詢結(jié)果如下:

image-20230331204013186

上面模擬的是從1000W條數(shù)據(jù)表中 ,一次查詢出100W條數(shù)據(jù)巫员,看起來(lái)性能不佳庶香,但是我們常規(guī)業(yè)務(wù)中,很少有一次性從mysql中查詢出這么多條數(shù)據(jù)量的場(chǎng)景简识「弦矗可以結(jié)合nosql緩存數(shù)據(jù)等等來(lái)減輕mysql數(shù)據(jù)庫(kù)的壓力。

因此财异,針對(duì)查詢數(shù)據(jù)量大的問題:

  1. 查詢時(shí)倘零,減少不需要的列,查詢效率也可以得到明顯提升
  2. 一次盡可能按需查詢較少的數(shù)據(jù)條數(shù)
  3. 借助nosql緩存數(shù)據(jù)等來(lái)減輕mysql數(shù)據(jù)庫(kù)的壓力

第一條和第三條查詢速度差不多戳寸,這時(shí)候你肯定會(huì)吐槽呈驶,那我還寫那么多字段干啥呢,直接 * 不就完事了

注意本人的 MySQL 服務(wù)器和客戶端是在同一臺(tái)機(jī)器上疫鹊,所以查詢數(shù)據(jù)相差不多袖瞻,有條件的同學(xué)可以測(cè)測(cè)客戶端與MySQL分開

SELECT * 它不香嗎?

在這里順便補(bǔ)充一下為什么要禁止 SELECT *拆吆。難道簡(jiǎn)單無(wú)腦聋迎,它不香嗎?

主要兩點(diǎn):

  1. 用 "SELECT * " 數(shù)據(jù)庫(kù)需要解析更多的對(duì)象枣耀、字段霉晕、權(quán)限、屬性等相關(guān)內(nèi)容捞奕,在 SQL 語(yǔ)句復(fù)雜牺堰,硬解析較多的情況下,會(huì)對(duì)數(shù)據(jù)庫(kù)造成沉重的負(fù)擔(dān)颅围。
  2. 增大網(wǎng)絡(luò)開銷伟葫,* 有時(shí)會(huì)誤帶上如log、IconMD5之類的無(wú)用且大文本字段院促,數(shù)據(jù)傳輸size會(huì)幾何增漲筏养。特別是MySQL和應(yīng)用程序不在同一臺(tái)機(jī)器,這種開銷非常明顯常拓。

3.2 優(yōu)化偏移量大的問題

3.2.1 采用子查詢方式

我們可以先定位偏移位置的 id渐溶,然后再查詢數(shù)據(jù)

SELECT id FROM `user_operation_log` LIMIT 1000000, 1;
SELECT * FROM `user_operation_log` WHERE id >= (SELECT id FROM `user_operation_log` LIMIT 1000000, 1) LIMIT 10;

查詢結(jié)果如下:

image-20230331194706798

這種查詢效率不理想啊EАU泼汀!奇怪眉睹,id是主鍵荔茬,主鍵索引不應(yīng)當(dāng)查詢這么慢啊竹海?慕蔚??

先EXPLAIN分析下sql語(yǔ)句:

EXPLAIN SELECT id FROM `user_operation_log` LIMIT 1000000, 1;
EXPLAIN SELECT * FROM `user_operation_log` WHERE id >= (SELECT id FROM `user_operation_log` LIMIT 1000000, 1) LIMIT 10;

奇怪斋配,走了索引啊孔飒,而且是主鍵索引,如下

image-20230331195704778
image-20230331195904091

帶著十萬(wàn)個(gè)為什么和千萬(wàn)個(gè)不甘心艰争,嘗試給主鍵再加一層唯一索引

ALTER TABLE `big_data`.`user_operation_log` 
ADD UNIQUE INDEX `idx_id`(`id`) USING BTREE;

由于數(shù)據(jù)量有1000W坏瞄,所以,加索引需要等待一會(huì)兒甩卓,畢竟創(chuàng)建1000W條數(shù)據(jù)的索引鸠匀,一般機(jī)器沒那么快。

然后再次執(zhí)行上面的查詢逾柿,結(jié)果如下:

image-20230331195440793

天啊缀棍,這查詢效率的差距不止十倍!;怼爬范!

再次EXPLAIN分析一下:

image-20230331200317237
image-20230331200209341

命中的索引不一樣,命中唯一索引的查詢弱匪,效率高出不止十倍青瀑。

結(jié)論:

對(duì)于大表查詢,不要太相信主鍵索引能夠帶來(lái)多少的性能提升萧诫,老老實(shí)實(shí)根據(jù)查詢字段斥难,添加相應(yīng)索引吧!2聘椤蘸炸!

但是上面的方法只適用于==id是遞增==的情況,如果id不是遞增的尖奔,比如雪花算法生成的id搭儒,得按照下面的方式:

注意:

  1. 某些 mysql 版本不支持在 in 子句中使用 limit,所以采用了多個(gè)嵌套select
  2. 但這種缺點(diǎn)是分頁(yè)查詢只能放在子查詢里面
SELECT * FROM `user_operation_log` WHERE id IN (SELECT t.id FROM (SELECT id FROM `user_operation_log` LIMIT 1000000, 10) AS t);

查詢所花費(fèi)時(shí)間如下:

image-20230331201356087

EXPLAIN一下

EXPLAIN SELECT * FROM `user_operation_log` WHERE id IN (SELECT t.id FROM (SELECT id FROM `user_operation_log` LIMIT 1000000, 10) AS t);
image-20230331201459758

3.2.2 采用 id 限定方式

這種方法要求更高些提茁,==id必須是連續(xù)遞增==(注意是連續(xù)遞增淹禾,不僅僅是遞增哦),而且還得計(jì)算id的范圍茴扁,然后使用 between铃岔,sql如下

SELECT * FROM `user_operation_log` WHERE id between 1000000 AND 1000100 LIMIT 100;
SELECT * FROM `user_operation_log` WHERE id >= 1000000 LIMIT 100;
image-20230331202058138

可以看出,查詢效率是相當(dāng)不錯(cuò)的

注意:這里的 LIMIT 是限制了條數(shù),沒有采用偏移量

還是EXPLAIN分析一下

EXPLAIN SELECT * FROM `user_operation_log` WHERE id between 1000000 AND 1000100 LIMIT 100;
EXPLAIN SELECT * FROM `user_operation_log` WHERE id >= 1000000 LIMIT 100;
image-20230331202314419
image-20230331202334850

因此毁习,針對(duì)分頁(yè)查詢智嚷,偏移量大導(dǎo)致查詢慢的問題:

  1. 先對(duì)查詢的字段創(chuàng)建唯一索引
  2. 根據(jù)業(yè)務(wù)需求,先定位查詢范圍(對(duì)應(yīng)主鍵id的范圍纺且,比如大于多少盏道、小于多少、IN)
  3. 查詢時(shí)载碌,將第2步確定的范圍作為查詢條件

本文由mdnice多平臺(tái)發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猜嘱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嫁艇,更是在濱河造成了極大的恐慌朗伶,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件步咪,死亡現(xiàn)場(chǎng)離奇詭異论皆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)歧斟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門纯丸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人静袖,你說(shuō)我怎么就攤上這事觉鼻。” “怎么了队橙?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵坠陈,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我捐康,道長(zhǎng)仇矾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任解总,我火速辦了婚禮贮匕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘花枫。我一直安慰自己刻盐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布劳翰。 她就那樣靜靜地躺著敦锌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佳簸。 梳的紋絲不亂的頭發(fā)上乙墙,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼听想。 笑死腥刹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哗魂。 我是一名探鬼主播肛走,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼录别!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起邻吞,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤组题,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后抱冷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崔列,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年旺遮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赵讯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耿眉,死狀恐怖边翼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鸣剪,我是刑警寧澤组底,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站筐骇,受9級(jí)特大地震影響债鸡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铛纬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一厌均、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧告唆,春花似錦棺弊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至茄螃,卻和暖如春缝驳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工用狱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留运怖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓夏伊,卻偏偏與公主長(zhǎng)得像摇展,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子溺忧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容