應朋友之邀刺桃,今天下午去字節(jié)送了顆人頭,最后不負眾望,被面試官攆出來了……
一面
談一下之前重構百度賬號中心的方案
吹了一波之前在百度改造restful接口的方案,但面試官并不感冒敢靡,提了一個顯示文章的列表的場景,但感覺沒有理解面試官的意思苦银,沒有提出面試官滿意的restful解決方案啸胧,剛開始就得了個負分,這塊得抽空找大佬再探討探討幔虏,等后面有什么心得再補充吧
mysql索引快的原理
回答這個問題需要先看一下數(shù)據(jù)庫的存儲結構
頁和頁之間的關系
有個知識纺念,之前不知道的
聚集索引:以主鍵創(chuàng)建的索引,葉子節(jié)點存儲的是表中的數(shù)據(jù)
非聚集索引:非主鍵創(chuàng)建的索引想括,葉子節(jié)點中存儲的是主鍵和索引列陷谱,使用非聚集索引查詢數(shù)據(jù),會查詢到葉子上的主鍵主胧,再根據(jù)主鍵查到數(shù)據(jù)(這個過程叫做回表)
沒有用索引的時候叭首,需要遍歷雙向鏈表來定位對應的頁,有了索引踪栋,可以用二分查找焙格,這么弱智的答案,我當時居然沒想到夷都,這也是后面面試官問為什么主鍵建議用自增字段的答案
頁碼跳頁性能(即sql offset會不會影響性能)
mysql查詢時眷唉,offset過大影響性能的原因是多次通過主鍵索引訪問數(shù)據(jù)塊的I/O操作
InnoDB會,MyISAM不會囤官,如圖所示
InnoDB的二級索引對應的是主鍵冬阳,mysql查詢的時候會根據(jù)主鍵將數(shù)據(jù)塊查出來,然后執(zhí)行offset丟棄党饮,如果只查主鍵就不會有性能問題肝陪。MyISAM的主鍵索引和二級索引都指向數(shù)據(jù)塊,因此沒有這方面的問題
優(yōu)化措施
先查詢偏移后的主鍵刑顺,再查詢數(shù)據(jù)塊
select a.* from member as a inner join (select id from member where gender=1 limit 300000,1) as b on a.id=b.id
golang中new和make的區(qū)別
- make 僅用來分配及初始化類型為 slice氯窍、map、chan 的數(shù)據(jù)蹲堂。new 可分配任意類型的數(shù)據(jù).
- new 分配返回的是指針狼讨,即類型 *Type。make 返回引用柒竞,即 Type.
- new 分配的空間被清零, make 分配空間后政供,會進行初始化.
有一個字母翻譯對照表,1代表A朽基,2代表B布隔,以此類推至26代表Z,現(xiàn)給一個整形數(shù)組踩晶,例如[1,2,3,4,5,6,7,8,9],求共有多少種翻譯方式
package main
import (
"fmt"
)
func main() {
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
dict := make(map[int]string)
for i := 1; i < 27; i++ { //初始化字典
dict[i] = string('A' + (i - 1))
}
count := 0
for i := 0; i < len(s); i++ {
if i == 0 {
count++
} else if s[i-1]*10+s[i] < 27 && s[i-1]*10+s[i] > 0 {
count++
}
}
fmt.Println(count)
}
redis zset的數(shù)據(jù)結構
如圖所示执泰,L0層存儲所有的數(shù)據(jù),L1層隨機抽取幾個組成一個系數(shù)索引渡蜻,L2層進一步抽取L1層术吝,從而組成一個多層稀疏索引,這樣就可以用二分法快速的找出所需要的數(shù)據(jù)了
利用redis做一個延時事件執(zhí)行系統(tǒng)(設計)
當時想的設計跟盒子科技的這張PPT高度類似茸苇,以時間為score排苍,以事件數(shù)組為value,存儲成一個zset学密,然后定期去取一定數(shù)量的事件進行執(zhí)行淘衙。如果延時執(zhí)行事件比較稀疏,就設定一個值腻暮,比如每次取的事件必須是一分鐘內(nèi)的彤守,一分鐘內(nèi)沒有時間則等待下次再取毯侦。
二面
PHP代碼sleep的時候,進程狀態(tài)是怎樣的具垫,進程都有哪幾種狀態(tài)
- 創(chuàng)建狀態(tài)
- 就緒狀態(tài)
- 運行狀態(tài)
- 阻塞狀態(tài)
- 終止狀態(tài)
當代碼執(zhí)行sleep的時候進程處于阻塞狀態(tài)
linux系統(tǒng)中如何查看進程狀態(tài)
使用命令 ps -aux
侈离,STAT列即為進程狀態(tài)
linux上進程有五種狀態(tài)
- R——Runnable(運行):正在運行或在運行隊列中等待
- S——sleeping(中斷):休眠中,受阻筝蚕,在等待某個條件的形成或接收到信號
- D——uninterruptible sleep(不可中斷):收到信號不喚醒和不可運行卦碾,進程必須等待直到有中斷發(fā)生
- Z——zombie(僵死):進程已終止,但進程描述還在起宽,直到父進程調(diào)用wait4()系統(tǒng)調(diào)用后釋放
- T——traced or stoppd(停止):進程收到SiGSTOP,SIGSTP,SIGTOU信號后停止運行
狀態(tài)后綴表示:
<:優(yōu)先級高的進程
N:優(yōu)先級低的進程
L:有些頁被鎖進內(nèi)存
s:進程的領導者(在它之下有子進程)
l:ismulti-threaded (using CLONE_THREAD, like NPTL pthreads do)
+:位于后臺的進程組
數(shù)據(jù)庫事務的實現(xiàn)原理
數(shù)據(jù)庫不同的存儲引擎可能會有一些區(qū)別洲胖。這里拿常用的InnerDB存儲引擎舉例:
原子性實現(xiàn)原理:
通過數(shù)據(jù)庫Undo Log實現(xiàn)的。事務中在操作任何數(shù)據(jù)之前坯沪,首先將原數(shù)據(jù)備份到Undo Log然后進行數(shù)據(jù)的修改绿映。如果事務中有任意操作發(fā)生異常或用戶執(zhí)行了 Rollback 語句屏箍,那么數(shù)據(jù)庫就會使用Undo Log中的備份將數(shù)據(jù)恢復到事務開始之前的狀態(tài)绘梦。
一致性實現(xiàn)原理:
與原子性實現(xiàn)原理一樣也是利用Undo Log
持久性實現(xiàn)原理:
通過數(shù)據(jù)庫Redo Log實現(xiàn)的,Redo Log與Undo Log 相反赴魁,Redo Log 記錄的是新數(shù)據(jù)的備份卸奉,事務提交之前,會把數(shù)據(jù)備份到Redo Log中并持久化颖御。當系統(tǒng)崩潰時榄棵,雖然數(shù)據(jù)沒有持久化到數(shù)據(jù)庫中,但是 Redo Log 已經(jīng)持久化潘拱。系統(tǒng)可以根據(jù) Redo Log 的內(nèi)容疹鳄,將所有數(shù)據(jù)恢復到最新的
隔離性實現(xiàn)原理:
隔離性的實現(xiàn)原理比較特殊,是通過數(shù)據(jù)庫鎖的機制實現(xiàn)的芦岂。
隔離性分四個級別:讀未提交(Read uncommitted)瘪弓、讀已提交(Read committed)、可重復讀(Repeatable reads)禽最、可序列化(Serializable)
MySQL的默認隔離級別就是Repeatable,Oracle默認Read committed
讀未提交:一個事務可以讀到另外一個事務未提交的數(shù)據(jù)腺怯。臟讀
實現(xiàn):事務在讀數(shù)據(jù)的時候并未對數(shù)據(jù)進行加鎖。
事務在發(fā)生更新數(shù)據(jù)的瞬間川无,必須先對其加 行級共享鎖呛占,直到事務結束才釋放。
舉例:事務A讀取某行記錄時(沒有加鎖)懦趋,事務2也能對這行記錄進行讀取晾虑、更新。當事務B對該記錄進行更新時,事務A讀取該記錄帜篇,能讀到事務B對該記錄的修改版本糙捺,即使該修改尚未被提交。
事務A更新某行記錄時笙隙,事務B不能對這行記錄做更新继找,直到事務A結束。
讀已提交:一個事務可以讀到另外一個事務提交的數(shù)據(jù)逃沿。不可重復讀
實現(xiàn):事務對當前被讀取的數(shù)據(jù)加 行級共享鎖(當讀到時才加鎖),一旦讀完該行幻锁,立即釋放該行級共享鎖凯亮;
事務在更新某數(shù)據(jù)的瞬間(就是發(fā)生更新的瞬間),必須先對其加 行級排他鎖哄尔,直到事務結束才釋放假消。
原理:事務A讀取某行記錄時,事務B也能對這行記錄進行讀取岭接、更新富拗;當事務B對該記錄進行更新時,事務A再次讀取該記錄鸣戴,讀到的只能是事務B對其更新前的版本啃沪,或者事務B提交后的版本。
事務A更新某行記錄時窄锅,事務B不能對這行記錄做更新创千,直到事務1結束。
流程描述:事務A讀操作會加上共享鎖入偷,事務B寫操作時會加上排他鎖追驴,當事務B正在寫操作時,事務A要讀操作疏之,發(fā)現(xiàn)有排他鎖殿雪,事務A就會阻塞,等待排他鎖釋放(事務B寫操作提交才會釋放)锋爪,才能進行讀操作丙曙。
可重復讀
實現(xiàn):事務在讀取某數(shù)據(jù)的瞬間(就是開始讀取的瞬間),必須先對其加 行級共享鎖几缭,直到事務結束才釋放河泳;
事務在更新某數(shù)據(jù)的瞬間(就是發(fā)生更新的瞬間),必須先對其加 行級排他鎖年栓,直到事務結束才釋放拆挥。
舉例:事務A讀取某行記錄時,事務B也能對這行記錄進行讀取、更新纸兔;當事務B對該記錄進行更新時惰瓜,事務A再次讀取該記錄,讀到的仍然是第一次讀取的那個版本汉矿。
事務A更新某行記錄時崎坊,事務B不能對這行記錄做更新,直到事務1結束洲拇。
可序列化(Serializable) 寫操作串聯(lián)執(zhí)行
實現(xiàn):事務在讀取數(shù)據(jù)時奈揍,必須先對其加 表級共享鎖 ,直到事務結束才釋放赋续;
事務在更新數(shù)據(jù)時男翰,必須先對其加 表級排他鎖 ,直到事務結束才釋放纽乱。
舉例:事務A正在讀取A表中的記錄時蛾绎,則事務B也能讀取A表,但不能對A表做更新鸦列、新增租冠、刪除,直到事務A結束薯嗤。
事務A正在更新A表中的記錄時顽爹,則事務B不能讀取A表的任意記錄,更不可能對A表做更新骆姐、新增话原、刪除,直到事務A結束诲锹。
原理:在讀操作時繁仁,加表級共享鎖,事務結束時釋放归园;寫操作時候黄虱,加表級獨占鎖,事務結束時釋放庸诱。
聚簇索引
跟聚集索引是一個東西捻浦,參見上面的聚集索引
反轉一個鏈表,如1->2->3->4->5->6桥爽,轉為1->5->4->3->2->6
package main
import "fmt"
// ListNode 鏈表節(jié)點
type ListNode struct {
Val int
Next *ListNode
}
//反轉鏈表的實現(xiàn)
func reversrList(head *ListNode) *ListNode {
cur := head
var pre *ListNode = nil
for cur != nil {
pre, cur, cur.Next = cur, cur.Next, pre //這句話最重要
}
return pre
}
// Print 打印鏈表
func (l *ListNode) Print() {
for l != nil {
if l.Val > 0 {
fmt.Println(l.Val)
}
l = l.Next
}
}
func main() {
m := 1
n := 4
root := new(ListNode)//頭結點
p := root
for i := 1; i < 7; i++ {//初始化鏈表
node := new(ListNode)
node.Val = i
p.Next = node
p = p.Next
}
p.Next = new(ListNode)//尾節(jié)點
l1 := root
var l2, l3 *ListNode
p = root
index := 0
for l3 == nil {//截成三段
if index == m {
l2 = p.Next
p.Next = nil
p = l2
}
if index == n {
l3 = p.Next
p.Next = nil
}
p = p.Next
index++
}
l2 = reversrList(l2)// 反轉l2
list := []*ListNode{l2, l3}
p = l1
index = 0
for index < len(list) {//拼接起來
for p.Next != nil {
p = p.Next
}
p.Next = list[index]
index++
}
l1.Print()
}