mysql 同一天多条记录只取第一条_MySQL面试高频100问(二)
點(diǎn)擊上方藍(lán)字關(guān)注我們
表結(jié)構(gòu)設(shè)計(jì)
1. 為什么要盡量設(shè)定一個(gè)主鍵?
主鍵是數(shù)據(jù)庫確保數(shù)據(jù)行在整張表唯一性的保障,即使業(yè)務(wù)上本張表沒有主鍵,也建議添加一個(gè)自增長的ID列作為主鍵.設(shè)定了主鍵之后,在后續(xù)的刪改查的時(shí)候可能更加快速以及確保操作數(shù)據(jù)范圍安全.
2. 主鍵使用自增ID還是UUID?
推薦使用自增ID,不要使用UUID.
因?yàn)樵贗nnoDB存儲引擎中,主鍵索引是作為聚簇索引存在的,也就是說,主鍵索引的B+樹葉子節(jié)點(diǎn)上存儲了主鍵索引以及全部的數(shù)據(jù)(按照順序),如果主鍵索引是自增ID,那么只需要不斷向后排列即可,如果是UUID,由于到來的ID與原來的大小不確定,會造成非常多的數(shù)據(jù)插入,數(shù)據(jù)移動,然后導(dǎo)致產(chǎn)生很多的內(nèi)存碎片,進(jìn)而造成插入性能的下降.
總之,在數(shù)據(jù)量大一些的情況下,用自增主鍵性能會好一些.
圖片來源于《高性能MySQL》: 其中默認(rèn)后綴為使用自增ID,_uuid為使用UUID為主鍵的測試,測試了插入100w行和300w行的性能.
關(guān)于主鍵是聚簇索引,如果沒有主鍵,InnoDB會選擇一個(gè)唯一鍵來作為聚簇索引,如果沒有唯一鍵,會生成一個(gè)隱式的主鍵.
If you define a PRIMARY KEY on your table, InnoDB uses it as the clustered index.
If you do not define a PRIMARY KEY for your table, MySQL picks the first UNIQUE index that has only NOT NULL columns as the primary key and InnoDB uses it as the clustered index.
3. 字段為什么要求定義為not null?
MySQL官網(wǎng)這樣介紹:
NULL columns require additional space in the rowto record whether their values are NULL. For MyISAM tables, each NULL columntakes one bit extra, rounded up to the nearest byte.
null值會占用更多的字節(jié),且會在程序中造成很多與預(yù)期不符的情況.
4. 如果要存儲用戶的密碼散列,應(yīng)該使用什么字段進(jìn)行存儲?
密碼散列,鹽,用戶身份證號等固定長度的字符串應(yīng)該使用char而不是varchar來存儲,這樣可以節(jié)省空間且提高檢索效率.
存儲引擎相關(guān)
1. MySQL支持哪些存儲引擎?
MySQL支持多種存儲引擎,比如InnoDB,MyISAM,Memory,Archive等等.在大多數(shù)的情況下,直接選擇使用InnoDB引擎都是最合適的,InnoDB也是MySQL的默認(rèn)存儲引擎.
InnoDB和MyISAM有什么區(qū)別?
InnoDB支持事物,而MyISAM不支持事物
InnoDB支持行級鎖,而MyISAM支持表級鎖
InnoDB支持MVCC, 而MyISAM不支持
InnoDB支持外鍵,而MyISAM不支持
InnoDB不支持全文索引,而MyISAM支持。
零散問題
1. MySQL中的varchar和char有什么區(qū)別.
char是一個(gè)定長字段,假如申請了char(10)的空間,那么無論實(shí)際存儲多少內(nèi)容.該字段都占用10個(gè)字符,而varchar是變長的,也就是說申請的只是最大長度,占用的空間為實(shí)際字符長度+1,最后一個(gè)字符存儲使用了多長的空間.
在檢索效率上來講,char > varchar,因此在使用中,如果確定某個(gè)字段的值的長度,可以使用char,否則應(yīng)該盡量使用varchar.例如存儲用戶MD5加密后的密碼,則應(yīng)該使用char.
2. varchar(10)和int(10)代表什么含義?
varchar的10代表了申請的空間長度,也是可以存儲的數(shù)據(jù)的最大長度,而int的10只是代表了展示的長度,不足10位以0填充.也就是說,int(1)和int(10)所能存儲的數(shù)字大小以及占用的空間都是相同的,只是在展示時(shí)按照長度展示.
3. MySQL的binlog有有幾種錄入格式?分別有什么區(qū)別?
有三種格式,statement,row和mixed.
statement模式下,記錄單元為語句.即每一個(gè)sql造成的影響會記錄.由于sql的執(zhí)行是有上下文的,因此在保存的時(shí)候需要保存相關(guān)的信息,同時(shí)還有一些使用了函數(shù)之類的語句無法被記錄復(fù)制.
row級別下,記錄單元為每一行的改動,基本是可以全部記下來但是由于很多操作,會導(dǎo)致大量行的改動(比如alter table),因此這種模式的文件保存的信息太多,日志量太大.
mixed. 一種折中的方案,普通操作使用statement記錄,當(dāng)無法使用statement的時(shí)候使用row.
此外,新版的MySQL中對row級別也做了一些優(yōu)化,當(dāng)表結(jié)構(gòu)發(fā)生變化的時(shí)候,會記錄語句而不是逐行記錄.
4. 超大分頁怎么處理?
超大的分頁一般從兩個(gè)方向上來解決.
數(shù)據(jù)庫層面,這也是我們主要集中關(guān)注的(雖然收效沒那么大),類似于select * from table where age < 20 limit 1000000,10這種查詢其實(shí)也是有可以優(yōu)化的余地的. 這條語句需要load1000000數(shù)據(jù)然后基本上全部丟棄,只取10條當(dāng)然比較慢. 當(dāng)時(shí)我們可以修改為select * from table where id in (select id from table where age < 20 limit 1000000,10).這樣雖然也load了一百萬的數(shù)據(jù),但是由于索引覆蓋,要查詢的所有字段都在索引中,所以速度會很快. 同時(shí)如果ID連續(xù)的好,我們還可以select * from table where id < 1000000 limit 10,效率也是不錯(cuò)的,優(yōu)化的可能性有許多種,但是核心思想都一樣,就是減少load的數(shù)據(jù).
從需求的角度減少這種請求….主要是不做類似的需求(直接跳轉(zhuǎn)到幾百萬頁之后的具體某一頁.只允許逐頁查看或者按照給定的路線走,這樣可預(yù)測,可緩存)以及防止ID泄漏且連續(xù)被人惡意攻擊.
解決超大分頁,其實(shí)主要是靠緩存,可預(yù)測性的提前查到內(nèi)容,緩存至redis等k-V數(shù)據(jù)庫中,直接返回即可.
在阿里巴巴《Java開發(fā)手冊》中,對超大分頁的解決辦法是類似于上面提到的第一種.
5. 關(guān)心過業(yè)務(wù)系統(tǒng)里面的sql耗時(shí)嗎?統(tǒng)計(jì)過慢查詢嗎?對慢查詢都怎么優(yōu)化過?
在業(yè)務(wù)系統(tǒng)中,除了使用主鍵進(jìn)行的查詢,其他的我都會在測試庫上測試其耗時(shí),慢查詢的統(tǒng)計(jì)主要由運(yùn)維在做,會定期將業(yè)務(wù)中的慢查詢反饋給我們.
慢查詢的優(yōu)化首先要搞明白慢的原因是什么? 是查詢條件沒有命中索引?是load了不需要的數(shù)據(jù)列?還是數(shù)據(jù)量太大?
所以優(yōu)化也是針對這三個(gè)方向來的,
首先分析語句,看看是否load了額外的數(shù)據(jù),可能是查詢了多余的行并且拋棄掉了,可能是加載了許多結(jié)果中并不需要的列,對語句進(jìn)行分析以及重寫.
分析語句的執(zhí)行計(jì)劃,然后獲得其使用索引的情況,之后修改語句或者修改索引,使得語句可以盡可能的命中索引.
如果對語句的優(yōu)化已經(jīng)無法進(jìn)行,可以考慮表中的數(shù)據(jù)量是否太大,如果是的話可以進(jìn)行橫向或者縱向的分表.
6. 上面提到橫向分表和縱向分表,可以分別舉一個(gè)適合他們的例子嗎?
橫向分表是按行分表.假設(shè)我們有一張用戶表,主鍵是自增ID且同時(shí)是用戶的ID.數(shù)據(jù)量較大,有1億多條,那么此時(shí)放在一張表里的查詢效果就不太理想.我們可以根據(jù)主鍵ID進(jìn)行分表,無論是按尾號分,或者按ID的區(qū)間分都是可以的. 假設(shè)按照尾號0-99分為100個(gè)表,那么每張表中的數(shù)據(jù)就僅有100w.這時(shí)的查詢效率無疑是可以滿足要求的.
縱向分表是按列分表.假設(shè)我們現(xiàn)在有一張文章表.包含字段id-摘要-內(nèi)容.而系統(tǒng)中的展示形式是刷新出一個(gè)列表,列表中僅包含標(biāo)題和摘要,當(dāng)用戶點(diǎn)擊某篇文章進(jìn)入詳情時(shí)才需要正文內(nèi)容.此時(shí),如果數(shù)據(jù)量大,將內(nèi)容這個(gè)很大且不經(jīng)常使用的列放在一起會拖慢原表的查詢速度.我們可以將上面的表分為兩張.id-摘要,id-內(nèi)容.當(dāng)用戶點(diǎn)擊詳情,那主鍵再來取一次內(nèi)容即可.而增加的存儲量只是很小的主鍵字段.代價(jià)很小.
當(dāng)然,分表其實(shí)和業(yè)務(wù)的關(guān)聯(lián)度很高,在分表之前一定要做好調(diào)研以及benchmark.不要按照自己的猜想盲目操作.
7. 什么是存儲過程?有哪些優(yōu)缺點(diǎn)?
存儲過程是一些預(yù)編譯的SQL語句。1、更加直白的理解:存儲過程可以說是一個(gè)記錄集,它是由一些T-SQL語句組成的代碼塊,這些T-SQL語句代碼像一個(gè)方法一樣實(shí)現(xiàn)一些功能(對單表或多表的增刪改查),然后再給這個(gè)代碼塊取一個(gè)名字,在用到這個(gè)功能的時(shí)候調(diào)用他就行了。2、存儲過程是一個(gè)預(yù)編譯的代碼塊,執(zhí)行效率比較高,一個(gè)存儲過程替代大量T_SQL語句 ,可以降低網(wǎng)絡(luò)通信量,提高通信速率,可以一定程度上確保數(shù)據(jù)安全
但是,在互聯(lián)網(wǎng)項(xiàng)目中,其實(shí)是不太推薦存儲過程的,比較出名的就是阿里的《Java開發(fā)手冊》中禁止使用存儲過程,我個(gè)人的理解是,在互聯(lián)網(wǎng)項(xiàng)目中,迭代太快,項(xiàng)目的生命周期也比較短,人員流動相比于傳統(tǒng)的項(xiàng)目也更加頻繁,在這樣的情況下,存儲過程的管理確實(shí)是沒有那么方便,同時(shí),復(fù)用性也沒有寫在服務(wù)層那么好.
8. 說一說三個(gè)范式
第一范式: 每個(gè)列都不可以再拆分. 第二范式: 非主鍵列完全依賴于主鍵,而不能是依賴于主鍵的一部分. 第三范式: 非主鍵列只依賴于主鍵,不依賴于其他非主鍵.
在設(shè)計(jì)數(shù)據(jù)庫結(jié)構(gòu)的時(shí)候,要盡量遵守三范式,如果不遵守,必須有足夠的理由.比如性能. 事實(shí)上我們經(jīng)常會為了性能而妥協(xié)數(shù)據(jù)庫的設(shè)計(jì).
9. MyBatis中的#
Mybatis 的Mapper.xml語句中parameterType向SQL語句傳參有兩種方式:#{}和${}
我們經(jīng)常使用的是#{},一般解說是因?yàn)檫@種方式可以防止SQL注入,簡單的說#{}這種方式SQL語句是經(jīng)過預(yù)編譯的,它是把#{}中間的參數(shù)轉(zhuǎn)義成字符串,舉個(gè)例子:
select * from student where student_name = #{name}?
預(yù)編譯后,會動態(tài)解析成一個(gè)參數(shù)標(biāo)記符?:
select * from student where student_name = ?
而使用${}在動態(tài)解析時(shí)候,會傳入?yún)?shù)字符串
select * from student where student_name = 'hr'
#{} 這種取值是編譯好SQL語句再取值
${} 這種是取值以后再去編譯SQL語句
#{}方式能夠很大程度防止sql注入。
$方式無法防止Sql注入。
$方式一般用于傳入數(shù)據(jù)庫對象,例如傳入表名.
一般能用#的就別用$.
總結(jié)
以上是生活随笔為你收集整理的mysql 同一天多条记录只取第一条_MySQL面试高频100问(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在外企和大厂都实习过是一种什么体验?
- 下一篇: linux cmake编译源码,linu