數據庫和緩存
1.列舉常見的關系型數據庫和非關系型都有那些?
關系型數據庫(需要有表結構) mysql、oracle、splserver、postgresql、db2、sybase 非關系型數據庫(是以key-value存儲的,沒有表結構)(NoSQL) MongoDB MongoDB 是一個高性能,開源,無模式的文檔型數據庫,開發語言是C++。它在許多場景下可用於替代傳統的關系型數據庫或鍵/值存儲方式。 Redis Redis 是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日志型、Key-Value數據庫,並提供多種語言的API。
2.MySQL常見數據庫引擎及比較
InnoDB 支持事務
支持外鍵 支持表鎖、行鎖(for update) 表鎖:select * from tb for update 行鎖:select id,name from tb where id=2 for update myisam 查詢速度快 全文索引 支持表鎖 表鎖:select * from tb for update
3.簡述數據庫三大范式
# 數據庫的三大特性: '實體':表 '屬性':表中的數據(字段) '關系':表與表之間的關系 ---------------------------------------------------- # 數據庫設計三大范式: 1:確保每列保持原子性(即數據庫表中的所有字段值是不可分解的原子值) 2:確保表中的每列都是和主鍵相關(表中只能保存一種數據,不可以把多種數據保存在同一張表中)--->完全屬於當前表的數據 3:確保每列都和主鍵直接相關,而不是間接相關(在一個數據庫表中保存的數據只能與主鍵相關)----> 消除傳遞依賴(間接) 比如在設計一個訂單數據表的時候,可以將客戶編號作為一個外鍵和訂單表建立相應的關系。
而不可以在訂單表中添加關於客戶其它信息(比如姓名、所屬公司等)的字段。
# 數據庫五大約束' 1.primary KEY:設置主鍵約束; 2.UNIQUE:設置唯一性約束,不能有重復值; 3.CHECK:檢查約束 4.NOT NULL:設置非空約束,該字段不能為空; 5.FOREIGN key:設置外鍵約束。
4、什么是事務?MySQL如何支持事務?
事務用於將某些操作的多個SQL作為原子性操作,一旦有某一個出現錯誤,即可回滾到原來的狀態,從而保證數據庫數據完整性。 一般來說,事務是必須滿足4個特性(ACID): Atomicity(原子性)、Consistency(一致性)、Isolation(隔離性)、Durability(持久性)。
1、原子性:事務包含的所有操作要么全部成功,要么全部失敗回滾,因此事務的操作如果成功就必須要完全應用到數據庫,如果操作失敗則不能對數據庫有任何影響。
2、一致性:事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之后都必須處於一致性狀態。拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那么不管A和B之間如何轉賬,轉幾次賬,事務結束后兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。
3、隔離性:當多個用戶並發訪問數據庫時,比如操作同一張表時,數據庫為每一個用戶開啟的事務,不能被其他事務的操作所干擾,多個並發事務之間要相互隔離。即要達到這么一種效果:對於任意兩個並發的事務T1和T2,在事務T1看來,T2要么在T1開始之前就已經結束,要么在T1結束之后才開始,這樣每個事務都感覺不到有其他事務在並發地執行。
4、持久性:持久性是指一個事務一旦被提交了,那么對數據庫中的數據的改變就是永久性的,即便是在數據庫系統遇到故障的情況下也不會丟失提交事務的操作。
最重要的是1,2兩個特性。
關閉自動提交:SET AUTOCOMMIT=0; # 此后需要手動提交事務,應用COMMIT或ROLLBACK提交事務或回滾事務
開啟自動提交:SET AUTOCOMMIT=1;
如果執行語句:START TRANSACTION(開始一個事務); 那么不論有無設置自動提交,均需要手動提交或回滾。
事務的周期由用戶在命令提示符中輸入START TRANSACTION指令開始,直至用戶輸入COMMIT結束。
5.簡述數據庫設計中一對多和多對多的應用場景?
FK(一對多) 下拉框里面的數據就需要用FK關聯另一張表 M2M(多對多) 多選的下拉框,或者checkbox
6.常見SQL
group by 分組對聚合的條件進行篩選需要通過havhing SQL的left join 、right join、inner join之間的區別 left join (左連接) 返回包括左表中的所有記錄和右表中聯結字段相等的記錄 right join(右連接) 返回包括右表中的所有記錄1和左表中聯結字段相等的記錄 inner join(內連接)只返回兩個表中聯結字段相等的行
7.簡述觸發器、函數、視圖、存儲過程
觸發器: 對數據庫某張表的增加、刪除,修改前后定義一些操作。 函數:(觸發函數是通過select) 聚合函數:max/sum/min/avg 時間格式化:date_format 字符串拼接:concat 存儲過程: 將SQL語句保存到數據庫中,並命名,以后在代碼調用時,直接調用名稱即可 參數類型: in 只將參數傳進去 out 只拿結果 inout 既可以傳,可以取 函數與存儲過程區別: 本質上沒區別。只是函數只能返回一個變量的限制,而存儲過程可以返回多個。函數是可以嵌入在sql中使用的,可以在select中調用,而存儲過程不可以,它需要執行語句call來調用。 視圖: 視圖是一個虛擬表,不是真實存在的(一般只能查,不能改)
8.MySQL索引種類
單列 普通索引:加速查找 唯一索引:加速查找 + 約束:不能重復(只能有一個空,不然就重復了) 主鍵(primay key):加速查找 + 約束:不能重復 + 不能為空 多列 聯合索引(多個列創建索引)-----> 相當於單列的普通索引 聯合唯一索引 -----> 相當於單列的唯一索引 ps:聯合索引的特點:遵循最左前綴的規則 其他詞語: ·· - 合並索引,利用多個單列索引查詢;(例如在數據庫查用戶名和密碼,分別給用戶名和密碼建立索引) - 覆蓋索引,查詢和返回列全部利用索引;
9.索引在什么情況下遵循最左前綴的規則?
聯合索引。
10.主鍵和外鍵的區別
主鍵是能確定一條記錄的唯一標示。例如,身份證證號 外鍵:用於與另一張表的關聯,是能確定另一張表記錄的字段,用於保持數據的一致性
主鍵 | 外鍵 | |
定義 | 唯一標識一條記錄,不能有重復的,不允許為空 | 表的外鍵是另一張表的主鍵,外鍵可以有重復的,可以為空 |
作用 | 用來保證數據完整性 | 用來與其他表建立聯系的 |
個數 | 主鍵只能有一個 | 一個表可以有多個外鍵 |
11.MySQL常見的函數
聚合函數 max/min/sum/avg 時間格式化 date_format 字符串拼接 concat(當拼接了null,則返回null) 截取字符串 substring 返回字節個數 length
12.列舉 創建索引但是無法命中索引的8種情況
1. like '%xx' select * from tb1 where name like '%cn'; 2. 使用函數 select * from tb1 where reverse(name) = 'wupeiqi'; 3. or select * from tb1 where nid = 1 or email = 'seven@live.com'; 特別的:當or條件中有未建立索引的列才失效,以下會走索引 select * from tb1 where nid = 1 or name = 'seven'; select * from tb1 where nid = 1 or email = 'seven@live.com' and name = 'alex' 4. 類型不一致 如果列是字符串類型,傳入條件是必須用引號引起來,不然... select * from tb1 where name = 999; 5. != select * from tb1 where name != 'alex' 特別的:如果是主鍵,則還是會走索引 select * from tb1 where nid != 123 6. > select * from tb1 where name > 'alex' 特別的:如果是主鍵或索引是整數類型,則還是會走索引 select * from tb1 where nid > 123 select * from tb1 where num > 123 7. order by select email from tb1 order by name desc; 當根據索引排序時候,選擇的映射如果不是索引,則不走索引 特別的:如果對主鍵排序,則還是走索引: select * from tb1 order by nid desc; 8. 組合索引最左前綴 如果組合索引為:(name,email) name and email -- 使用索引 name -- 使用索引 email -- 不使用索引
13.如何開啟慢日志查詢?
修改配置文件 slow_query_log = OFF 是否開啟慢日志記錄 long_query_time = 2 時間限制,超過此時間,則記錄 slow_query_log_file = /usr/slow.log 日志文件 log_queries_not_using_indexes = OFF 為使用索引的搜索是否記錄 下面是開啟 slow_query_log = ON long_query_time = 2 log_queries_not_using_indexes = OFF log_queries_not_using_indexes = ON 注:查看當前配置信息: show variables like '%query%' 修改當前配置: set global 變量名 = 值
14.數據庫導入導出命令
備份所有數據庫:
mysqldump –u 用戶名 –p –h 主機名 --all-databases > 備份文件名.sql
備份整個或多個數據庫:
mysqldump –u 用戶名 –p –h 主機名 --databases db1 db2 db3 … > 備份文件名.sql
備份某個數據庫的某些表:
mysqldump –u 用戶名 –p –h 主機名 數據庫名 表1 表2 表3… > 備份文件名.sql
通過mysqldump,如果使用了"--all-databases"或"--databases"選項,則在備份文件中包含CREATE DATABASE和USE語句,故並不需要指定一個數據庫名去恢復備份文件。
mysql –u 用戶名 –p < 備份文件.sql
通過mysqldump,如果沒有使用"--databases"選項,則備份文件中不包含CREATE DATABASE和USE語句,那么在恢復的時候必須指定數據庫。
mysql –u 用戶名 –p 數據庫名 < 備份文件.sql
15.數據庫優化方案
可以從數據庫服務器部署、表設計、表查詢等方面考慮。
1、創建數據表時把固定長度的放在前面 2、將固定數據放入內存: 例如:choice字段 (django中有用到,數字1、2、3…… 對應相應內容) 3、char 和 varchar 的區別(char可變, varchar不可變 ) 4、聯合索引遵循最左前綴(從最左側開始檢索) 5、避免使用 select * 6、讀寫分離 - 實現:兩台服務器同步數據 - 利用數據庫的主從分離:主,用於增加、刪除、更新;從,用於查詢; 7、分庫 - 當數據庫中的表太多,將某些表分到不同的數據庫,例如:1W張表時 - 代價:連表查詢 8、分表 - 水平分表:將某些列拆分到另外一張表,例如:博客+博客詳情 - 垂直分表:講些歷史信息分到另外一張表中,例如:支付寶賬單 9、加緩存 - 利用redis、memcache (常用數據放到緩存里,提高取數據速度) 10、如果只想獲取一條數據 - select xxx from tb where name='alex' limit 1;
16.char和varchar的區別
char可變,varchar不可變 。
17.簡述MySQL的執行計划
查看有沒有命中索引,讓數據庫幫看看運行速度快不快 explain select * from table;
當type為all時,是為全表索引。
18.在對name做了唯一索引前提下,簡述以下區別:
select * from tb where name = ‘Oldboy-Wupeiqi’
select * from tb where name = ‘Oldboy-Wupeiqi’ limit 1
沒做唯一索引的話,前者查詢會全表掃描,效率低些;
limit 1,只要找到對應一條數據,就不繼續往下掃描.
然而 name 字段添加唯一索引了,加不加limit 1,意義都不大。
19.1000w條數據,使用limit offset 分頁時,為什么越往后翻越慢?如何解決?
答案一: 先查主鍵,在分頁。 select * from tb where id in ( select id from tb where limit 10 offset 30 ); 答案二: 記錄當前頁ID最大值和最小值 在翻頁時,根據條件先進行篩選(子查詢);篩選完畢之后,再根據limit offset 查詢。 select * from (select * from tb where id > 1000000) limit 10 offset 0; 如果用戶自己修改頁碼,也可能導致慢;此時對url種的頁碼進行加密(rest framework )
20.什么是合並索引?
簡單地說,對同一張表的一條sql可以使用多個索引,對這些索引取交集、並集,從而減少從表中取數據的次數,提高查詢效率。
21.什么是覆蓋索引?
這個概念很重要!
一個索引涵蓋了所有需要查詢和返回字段的值,稱為"覆蓋索引"。
只需掃描索引而無須回表,也就是說只需要通過索引就可以返回查詢數據,而不必先查到索引之后再去查詢數據。性能不言而喻,速度非常快,很強大!!
在 Explain 的時候,輸出的 Extra 信息中如果有 "Using Index" ,就表示這條查詢使用了覆蓋索引。
22.簡述數據庫讀寫分離
- 前提:主備兩台服務器同步數據 - 利用數據庫的主從分離:主,用於增加、刪除、更新;從,用於查詢;
例:Django數據庫讀寫分離 步驟一:寫配置文件 class Router1: # 指定到某個數據庫【讀】數據 def db_for_read(self, model, **hints): """ Attempts to read auth models go to auth_db. """ if model._meta.model_name == 'usertype': return 'db1' else: return 'default' # 指定到某個數據庫【寫】數據 def db_for_write(self, model, **hints): """ Attempts to write auth models go to auth_db. """ return 'default' 步驟二:配置settings DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), }, 'db1': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } DATABASE_ROUTERS = ['db_router.Router1',] 步驟三:視圖里用 using 方式可以指定讀寫數據庫 from django.shortcuts import render,HttpResponse from app01 import models def index(request): models.UserType.objects.using('db1').create(title='普通用戶') # 手動指定去某個數據庫取數據 result = models.UserType.objects.all().using('db1') print(result) return HttpResponse('...')
23.簡述數據庫分庫,分表(水平、垂直)?
1、分庫 當數據庫中的表太多,將某些表分到不同數據庫,例如:1W張表時 代價:連表查詢跨數據庫,代碼變多。 2、分表(提高查詢性能) 水平分表:表的記錄行數龐大,將表按記錄行數分成n份,每張表就小了很多。這些表結構一致。 垂直分表:將表的一些內容很長的列拆出來獨立成另一張表。
24.redis和memcached比較?
區別: 1:redis不僅支持簡單的key_value類型,還支持字符串,HASH,列表,集合。 2:內存使用效率對比:使用簡單的key-value存儲的話,Memcached的內存利用率更高;
而如果Redis采用HASH結構來做key-value存儲,由於其組合式的壓縮,Redis的內存利用率更高。 3:性能對比:由於Redis只使用單核,而Memcached可以使用多核,所以平均每一個核上Redis在存儲小數據時性能更高;
而在100k以上的數據中,Memcached性能更高。 4:Redis雖然是基於內存的存儲系統,但是它本身是支持內存數據的持久化的;而memcached是不支持數據持久化操作的。 5:集群管理不同,Memcached本身並不支持分布式,因此只能在客戶端通過像一致性哈希這樣的分布式算法來實現Memcached的分布式存儲。
25.redis中數據庫默認是多少個db 及作用?
Redis默認支持16個數據庫,可以通過配置databases來修改這一數字。客戶端與Redis建立連接后會自動選擇0號數據庫,不過可以隨時使用SELECT命令更換數據庫。 Redis支持多個數據庫,並且每個數據庫的數據是隔離的不能共享,並且基於單機才有,如果是集群就沒有數據庫的概念。
26.python操作redis的模塊
連接
- 直接連接:
import redis
r = redis.Redis(host='10.211.55.4', port=6379)
r.set('foo', 'Bar') # 這里的方法與Redis命令類似
print r.get('foo')
- 連接池:
import redis
pool = redis.ConnectionPool(host='10.211.55.4', port=6379)
r = redis.Redis(connection_pool=pool)
r.set('foo', 'Bar')
print r.get('foo')
27.如果redis中的某個列表中的數據量非常大,如何實現循環顯示每一個值?
- 如果一個列表在redis中保存了10w個值,我需要將所有值全部循環並顯示,請問如何實現? 一個一個取值,列表沒有iter方法,但能自定義。
寫個生成器: def list_iter(key, count=3): start = 0 while True: result = conn.lrange(key, start, start+count-1) start += count if not result: break for item in result: yield item
# 調用 for val in list_iter('num_list'): print(val)
場景:投票系統
28.redis如何實現主從復制?以及數據同步機制?
優勢: - 高可用 - 分擔主壓力 注意: - slave設置只讀 從的配置文件添加以下記錄,即可: slaveof 10.169.130.11 6379
29.redis中的sentinel (哨兵)的作用?
自動主從之間進行切換,實現熱切。 檢測主是否掛掉,且超過一半的sentinel檢測到掛了之后才進行進行切換。 如果主修復好了,再次啟動時候,會變成從。 啟動主redis: redis-server /etc/redis-6379.conf 啟動主redis redis-server /etc/redis-6380.conf 啟動從redis 在linux中: 找到 /etc/redis-sentinel-8001.conf 配置文件,在內部: - 哨兵的端口 port = 8001 - 主redis的IP,哨兵個數的一半/1 找到 /etc/redis-sentinel-8002.conf 配置文件,在內部: - 哨兵的端口 port = 8002 - 主redis的IP, 1 啟動兩個哨兵
30.如何實現redis集群?
redis集群 分片、分布式redis redis-py-cluster 集群方案: - redis cluster 官方提供的集群方案。 - codis,豌豆莢技術團隊。 - tweproxy,Twiter技術團隊。 redis cluster的原理? - 基於分片來完成。 - redis將所有能放置數據的地方創建了 16384 個哈希槽。 - 如果設置集群的話,就可以為每個實例分配哈希槽: - 192.168.1.20【0-5000】 - 192.168.1.21【5001-10000】 - 192.168.1.22【10001-16384】 - 以后想要在redis中寫值時, set k1 123 將k1通過crc16的算法,將k1轉換成一個數字。然后再將該數字和16384求余,如果得到的余數 3000,那么就將該值寫入到 192.168.1.20 實例中。
31.redis中默認有多少個哈希槽?
16384。
32.簡述redis有哪幾種持久化策略及比較?
RDB:每隔一段時間對redis進行一次持久化。 - 缺點:數據不完整 - 優點:速度快 AOF:把所有命令保存起來,如果想到重新生成到redis,那么就要把命令重新執行一次。 - 缺點:速度慢,文件比較大 - 優點:數據完整
33.列舉redis支持的過期策略。
voltile-lru: 從已設置過有效期的數據集(server.db[i].expires)中挑選最近利用率最低的數據淘汰 volatile-ttl: 從已設置過有效期的數據集(server.db[i].expires)中挑選即將過期的數據淘汰 volatile-random: 從已設置過有效期的數據集(server.db[i].expires)中任意選擇數據淘汰 allkeys-lru: 從數據集(server.db[i].dict)中挑選最近利用率最低的數據淘汰 allkeys-random: 從數據集(server.db[i].dict)中任意選擇數據淘汰 no-enviction(驅逐):禁止驅逐數據
34.MySQL里有2000w數據,redis中只存20w的數據,如何保證redis中都是熱點數據?
相關知識:redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略(回收策略)。redis 提供 6 種數據淘汰策略: volatile-lru:從已設置過有效期的數據集(server.db[i].expires)中挑選最近利用率最低的數據淘汰 volatile-ttl:從已設置過有效期的數據集(server.db[i].expires)中挑選即將過期的數據淘汰 volatile-random:從已設置過有效期的數據集(server.db[i].expires)中任意選擇數據淘汰 allkeys-lru:從數據集(server.db[i].dict)中挑選最近利用率最低的數據淘汰 allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰 no-enviction(驅逐):禁止驅逐數據
35.寫代碼,基於redis的列表實現 先進先出、后進先出隊列、優先級隊列
參看script—redis源碼 from scrapy.utils.reqser import request_to_dict, request_from_dict from . import picklecompat class Base(object): """Per-spider base queue class""" def __init__(self, server, spider, key, serializer=None): """Initialize per-spider redis queue. Parameters ---------- server : StrictRedis Redis client instance. spider : Spider Scrapy spider instance. key: str Redis key where to put and get messages. serializer : object Serializer object with ``loads`` and ``dumps`` methods. """ if serializer is None: # Backward compatibility. # TODO: deprecate pickle. serializer = picklecompat if not hasattr(serializer, 'loads'): raise TypeError("serializer does not implement 'loads' function: %r" % serializer) if not hasattr(serializer, 'dumps'): raise TypeError("serializer '%s' does not implement 'dumps' function: %r" % serializer) self.server = server self.spider = spider self.key = key % {'spider': spider.name} self.serializer = serializer def _encode_request(self, request): """Encode a request object""" obj = request_to_dict(request, self.spider) return self.serializer.dumps(obj) def _decode_request(self, encoded_request): """Decode an request previously encoded""" obj = self.serializer.loads(encoded_request) return request_from_dict(obj, self.spider) def __len__(self): """Return the length of the queue""" raise NotImplementedError def push(self, request): """Push a request""" raise NotImplementedError def pop(self, timeout=0): """Pop a request""" raise NotImplementedError def clear(self): """Clear queue/stack""" self.server.delete(self.key) class FifoQueue(Base): """Per-spider FIFO queue""" def __len__(self): """Return the length of the queue""" return self.server.llen(self.key) def push(self, request): """Push a request""" self.server.lpush(self.key, self._encode_request(request)) def pop(self, timeout=0): """Pop a request""" if timeout > 0: data = self.server.brpop(self.key, timeout) if isinstance(data, tuple): data = data[1] else: data = self.server.rpop(self.key) if data: return self._decode_request(data) class PriorityQueue(Base): """Per-spider priority queue abstraction using redis' sorted set""" def __len__(self): """Return the length of the queue""" return self.server.zcard(self.key) def push(self, request): """Push a request""" data = self._encode_request(request) score = -request.priority # We don't use zadd method as the order of arguments change depending on # whether the class is Redis or StrictRedis, and the option of using # kwargs only accepts strings, not bytes. self.server.execute_command('ZADD', self.key, score, data) def pop(self, timeout=0): """ Pop a request timeout not support in this queue class """ # use atomic range/remove using multi/exec pipe = self.server.pipeline() pipe.multi() pipe.zrange(self.key, 0, 0).zremrangebyrank(self.key, 0, 0) results, count = pipe.execute() if results: return self._decode_request(results[0]) class LifoQueue(Base): """Per-spider LIFO queue.""" def __len__(self): """Return the length of the stack""" return self.server.llen(self.key) def push(self, request): """Push a request""" self.server.lpush(self.key, self._encode_request(request)) def pop(self, timeout=0): """Pop a request""" if timeout > 0: data = self.server.blpop(self.key, timeout) if isinstance(data, tuple): data = data[1] else: data = self.server.lpop(self.key) if data: return self._decode_request(data) # TODO: Deprecate the use of these names. SpiderQueue = FifoQueue SpiderStack = LifoQueue SpiderPriorityQueue = PriorityQueue
36.如何基於redis實現消息隊列?
# 通過"發布訂閱"模式(P-S)實現消息隊列。只要有任務就給所有訂閱者每人一份。 # 發布者發布消息到頻道了,頻道就是一個消息隊列。
# 發布者: import redis conn = redis.Redis(host='192.168.1.99', port=6379) conn.publish('104.9MH', "hahahahahaha")
# 訂閱者: import redis conn = redis.Redis(host='192.168.1.99', port=6379) pub = conn.pubsub() pub.subscribe('104.9MH') while True: msg= pub.parse_response() print(msg)
對了,redis 做消息隊列不合適。 業務上避免過度復用一個redis,用它做緩存、做計算,還做任務隊列,壓力太大,不好。
37.什么是codis及作用?
Codis是一個分布式Redis解決方案, 對於上層的應用來說, 連接到Codis Proxy和連接原生的Redis Server沒有明顯的區別,
上層應用可以像使用單機的Redis一樣使用, Codis底層會處理請求的轉發, 不停機的數據遷移等工作,
所有后邊的一切事情, 對於前面的客戶端來說是透明的, 可以簡單的認為后邊連接的是一個內存無限大的Redis服務。
38.什么是twemproxy及作用?
是 Twtter 開源的一個 Redis 和 Memcache 代理服務器,主要用於管理 Redis 和 Memcached 集群,減少與Cache 服務器直接連接的數量。
39.寫代碼實現redis事務操作
import redis pool = redis.ConnectionPool(host='192.168.1.99', port=6379) conn = redis.Redis(connection_pool=pool)
# 開始事務 pipe = conn.pipeline(transaction=True) pipe.multi() pipe.set('name', 'bendere') pipe.set('role', 'sb')
# 提交 pipe.execute()
40.redis中的watch的命令的作用?
在Redis的事務中,WATCH命令可用於提供CAS(check-and-set)功能。
假設我們通過WATCH命令在事務執行之前【監視】了多個Keys,倘若在WATCH之后有任何Key的值發生了變化,
EXEC命令執行的事務都將被放棄,同時返回Null multi-bulk應答以通知調用者事務執行失敗。 面試題:你如何控制剩余的數量不會出問題? 方式一:- 通過redis的watch實現 import redis conn = redis.Redis(host='192.168.1.99', port=6379) # conn.set('count',1000) val = conn.get('count') print(val) with conn.pipeline(transaction=True) as pipe: # 先監視,自己的值沒有被修改過 conn.watch('count') # 事務開始 pipe.multi() old_count = conn.get('count') count = int(old_count) print('現在剩余的商品有:%s' % count) input("問媳婦讓不讓買?") pipe.set('count', count - 1) # 執行,把所有命令一次性推送過去 pipe.execute()
方式二 - 數據庫的鎖
41.談談數據庫鎖
以MySQL為例。InnoDB 存儲引擎實現了行鎖與表鎖。行鎖可以以行為單位對數據集進行鎖定。表鎖可以以表為單位對數據集進行鎖定。
行鎖、表鎖又可分為兩種鎖:共享鎖與排他鎖。
- 共享鎖:允許一個事務讀取一行,阻止其他事務獲得相同數據集的排他鎖。但允許其他事務獲取共享鎖。
- 排他鎖:允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享與排他鎖。但是可以對獲取了排他鎖的數據集進行單純的查詢訪問。
對於 Update、Delete、insert 語句,InnoDB 會自動給涉及的數據集隱式的加上排他鎖。對於 select 語句 InnoDB 不會加任何鎖。可以通過顯式的方式獲取共享鎖或者排他鎖。
- 共享鎖:
select * from table where ... lock in share mode
- 排他鎖:
select * from table where ... for update
42.基於redis如何實現商城商品數量計數器?
import redis conn = redis.Redis(host='192.168.1.99', port=6379) conn.set('count',1000) with conn.pipeline(transaction=True) as pipe: # 先監視,自己的值沒有被修改過 conn.watch('count') # 事務開始 pipe.multi() old_count = conn.get('count') count = int(old_count) if count > 0: # 有庫存 pipe.set('count', count - 1) # 執行,把所有命令一次性推送過去 pipe.execute()
43.簡述redis分布式鎖(redlock)的實現機制
在不同進程需要互斥地訪問共享資源時,分布式鎖是一種非常有用的技術手段。
用Redis實現分布式鎖管理器的算法,我們把這個算法稱為RedLock。 實現 - 寫值並設置超時時間 - 超過一半的redis實例設置成功,就表示加鎖完成。 - 使用:安裝redlock-py from redlock import Redlock
# redis實例 dbs = Redlock( [ {"host": "localhost", "port": 6379, "db": 0}, {"host": "localhost", "port": 6379, "db": 0}, {"host": "localhost", "port": 6379, "db": 0}, ] ) # 加鎖 my_lock = dbs.lock("unique_key", 10000) # 唯一資源名、有效時間(ms) if my_lock: # 進行操作 ... dbs.unlock(my_lock) # 釋放鎖 else: print('獲取鎖失敗')
redis分布式鎖? # 不是單機操作,又多了一/多台機器 # redis內部是單進程、單線程,是數據安全的(只有自己的線程在操作數據) ---------------------------------------------------------------- A、B、C,三個實例(主) 1、來了一個'隔壁老王'要操作,且不想讓別人操作,加鎖; 加鎖:'隔壁老王'自己生成一個隨機字符串,設置到A、B、C里(xxx=666) 2、來了一個'鄰居老李'要操作A、B、C,一讀發現里面有字符串,擦,被加鎖了,不能操作了,等着吧~ 3、'隔壁老王'解決完問題,不用鎖了,把A、B、C里的key:'xxx'刪掉;完成解鎖 4、'鄰居老李'現在可以訪問,可以加鎖了 # 問題: 1、如果'隔壁老王'加鎖后突然掛了,就沒人解鎖,就死鎖了,其他人干看着沒法用咋辦? 2、如果'隔壁老王'去給A、B、C加鎖的過程中,剛加到A,'鄰居老李'就去操作C了,加鎖成功or失敗? 3、如果'隔壁老王'去給A、B、C加鎖時,C突然掛了,這次加鎖是成功還是失敗? 4、如果'隔壁老王'去給A、B、C加鎖時,超時時間為5秒,加一個鎖耗時3秒,此次加鎖能成功嗎? # 解決 1、安全起見,讓'隔壁老王'加鎖時設置超時時間,超時的話就會自動解鎖(刪除key:'xxx') 2、加鎖程度達到(n/2)+1個(即過半)就表示加鎖成功,即使沒有給全部實例加鎖; 3、加鎖程度達到(n/2)+1個(即過半)就表示加鎖成功,即使沒有給全部實例加鎖; 4、不能成功,鎖還沒加完就過期,沒有意義了,應該合理設置過期時間
44.什么是一致性哈希?Python中是否有相應模塊?
一致性hash算法(DHT)可以通過減少影響范圍的方式,解決增減服務器導致的數據散列問題,從而解決了分布式環境下負載均衡問題; 如果存在熱點數據,可以通過增添節點的方式,對熱點區間進行划分,將壓力分配至其他服務器,重新達到負載均衡的狀態。
Python模塊--hash_ring,即Python中的一致性hash
45.如何高效的找到redis中所有以"w3c"開頭的key?
redis 有一個keys命令。 # 語法:KEYS pattern # 說明:返回與指定模式相匹配的所用的keys。 該命令所支持的匹配模式如下: 1、"?":用於匹配單個字符。例如,h?llo可以匹配hello、hallo和hxllo等; 2、"*":用於匹配零個或者多個字符。例如,h*llo可以匹配hllo和heeeello等; 2、"[]":可以用來指定模式的選擇區間。例如h[ae]llo可以匹配hello和hallo,但是不能匹配hillo。同時,可以使用“/”符號來轉義特殊的字符
# 例子:
redis 127.0.0.1:6379> keys w3c*
1) "w3c1"
2) "w3c123"
3) "w3c12"
# 注意 KEYS 的速度非常快,但如果數據太大,內存可能會崩掉, 如果需要從一個數據集中查找特定的key,最好還是用Redis的集合結構(set)來代替。
