我叫補三補四,很高興見到大家,歡迎一起學習交流和進步
今天來講一講alpha策略制定后的測試問題
mysql配置
?
Django模型體現了面向對象的編程技術,是一種面向對象的編程語言和不兼容類型能相互轉化的編程技術,這種技術也叫ORM(Object Relation Mapping,ORM)對象關系映射,Django的ORM功能十分強大,極大提高了開發效率
在Django當中配置數據庫的連接非常簡單
settings.py文件當中會提前為你創建好一個DATABASE(類型為字典):
# 這是settings.py的內容DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql', ?# 配置引擎'OPTIONS': {'read_default_file': '/path/to/my.cnf', ?# 配置文件路徑},'USER': 'yonghu', ?# 數據庫用戶名'PASSWORD': 'mima', ?# 數據庫密碼'HOST': '127.0.0.1', ?# 數據庫服務監聽IP'PORT': '3306', ?# 數據庫服務監聽端口'NAME': 'data', ?# 數據庫名字}}
配置連接有一定順序
如果配置中定義了OPTIONS,其中定義的連接信息則會優先被使用,如果沒有定義OPTIONS,則配置當中的USER、NAME、PASS WORD、HOST、PORT等信息會依次被使用到
DATABASE定義的數據庫數量不受限制,但是必須定義一個名為default的數據庫,數據庫支持如下配置:
ENGINE:配置定義數據庫后端:Django自帶支持的后端有:PostgreSQL、MYSQL、SQLite、Oracle
HOST:指定連接數據庫的主機地址,空字符串則輸出默認的地址localhost,也可以使用指定用于連接的套字路徑
NAME:數據庫名
CONN_MAX_AGE:一個連接的周期,比如設置為5,則會在請求結束5秒后段開連接
OPTIONS:默認為空,用于連接字典的額外參數
PASSWORD:數據庫密碼,默認為空(用環境變量)
PORT:數據庫的端口號
USER:連接數據庫的用戶名
連接池
?
在客戶端向服務器發出訪問請求以后,需要建立一個到數據端的連接,每進行一次訪問操作,就會創建一個連接,在mysql當中,這樣的連接由max_connection來配置。大部分情況下,這種連接是網絡連接,鏈接過程當中會使用網絡套接字,這是一個耗時的操作,大大增加了服務器的壓力,為了緩解這種問題,我們可以使用連接池,也就是將數據庫連接放到應用程序的緩存當中,在應用程序需要多數據庫發出請求時,先從連接池獲取連接,再使用這個連接請求數據庫
?
簡單來講,連接池就是通過預先創建一些保持長期為打開狀態的接口,從而減少頻繁開關連接的開銷,Django在首次進行數據庫連接后,這個連接會長期在打開狀態,只有當超過生命時間周期后才會被關閉,Django本身也并不具備連接池功能,但我們可以通過第三方庫來實現,例如django-db-connection-pool 、psycopg2-binary、django_db_polling
實操
不同的庫對于連接池的配置有不同的要求,這里用django-db-connection-pool
首先先下載:
pip install django-db-connection-pool
然后在settings.py當中添加相應的配置:
# settings.pyINSTALLED_APPS = [...'django_db_connection_pool',...]DATABASES = {'default': {'ENGINE': 'django_db_connection_pool.backends.mysql', ?# 使用連接池的數據庫引擎'NAME': 'your_database_name','USER': 'your_database_user','PASSWORD': 'your_database_password','HOST': 'localhost','PORT': '3306','POOL_OPTIONS': {'POOL_SIZE': 10, ?# 連接池大小'MAX_OVERFLOW': 20, ?# 最大溢出連接數},}}
Django遷移工具
Django提供的遷移工具可以將對模型所做的更改應用到數據庫,從而修改對應的表單和數據庫,Django當中提供了三個常用命令,分別是migrate、makemigration、sqlmigrate
其中,migrate命令負責將遷移應用到數據庫;makemigration負責將模型變動轉換成遷移;sqlmigrate會輸出應用變動時實際執行的SQL語句。
具體使用:
1.`makemigrations`命令
`makemigrations`用于生成遷移文件,將模型的變更轉換為遷移腳本。
基本用法
python manage.py makemigrations
此命令會檢查所有已安裝應用的模型定義,與數據庫當前狀態進行比較,并生成必要的遷移文件。
指定應用
如果只想為特定應用生成遷移文件,可以指定應用名稱:
python manage.py makemigrations your_app_name
?
這會為`myapp`應用生成遷移文件。
查看遷移計劃
在生成遷移文件之前,可以使用`--plan`選項查看即將執行的遷移操作:
python manage.py makemigrations --plan
這有助于了解遷移的具體內容。
2.`migrate`命令
`migrate`用于將遷移文件應用到數據庫,更新數據庫結構以匹配模型。
基本用法
python manage.py migrate
此命令會應用所有未應用的遷移文件。
指定應用
如果只想應用特定應用的遷移,可以指定應用名稱:
python manage.py migrate your_app_name
這會只應用`myapp`應用的遷移。
3.`sqlmigrate`命令
`sqlmigrate`用于查看特定遷移文件對應的 SQL 語句,但不會實際執行這些語句。
基本用法
python manage.py sqlmigrate app_name migration_name
?
例如,查看`myapp`應用中名為`0001_initial`的遷移文件的 SQL 語句:
?
?
python manage.py sqlmigrate myapp 0001
?
這會輸出該遷移文件對應的 SQL 語句。
進階用法
?
? 指定數據庫:
python manage.py sqlmigrate myapp 0001 --database=secondary_db
?
這會在多數據庫環境中指定數據庫。
? 列出遷移文件:
??python manage.py sqlmigrate myapp --list
?
這會列出所有可用的遷移文件及其狀態。
通過以上命令,你可以有效地管理 Django 項目的數據庫遷移,確保模型與數據庫結構始終保持一致。
數據庫創建完成,Django會自動為模型提供一個數據庫抽象API,允許創建,檢索,更新和刪除對象,因此,我們不需要學習mysql語言,僅僅靠Django當中提供的函數就能實現對數據庫的增刪改查操作,不管是增刪還是改查,首先要對數據庫進行保存,所以我們要學習Django當中的save()方法
簡單案例:
python manage.py shellfrom myapp.models import Personperson = Person(name="Alice", age=30)person.save()
在該案例中在數據庫當中新寫入了一個person對象,同時,會自動獲取一個自增的主鍵
獲取數據表當中的對象:
?all_products =Product.objects.all()
通過過濾器獲取符合條件的子集:
?Product.objects.filter(date_created__year=2018)
多個過濾器連接:
Product.objects.filter(date_created__year=2018).exclude(date_created__month=12)
這在sql里面是AND的語句表達,如果想用OR語句查詢可以使用Q對象:
Product.objects.filter(Q(title__startswith="Women") | (Q(date_created=date(2005, 5, 2)) | Q(date_created=date(2005, 5, 6))))
這段代碼的目的是從一個名為 Product 的模型中檢索一個對象,該對象滿足以下條件之一:? 標題以"Women"開頭。? 創建日期為2005年5月2日或2005年5月6日。
懶加載和緩存
QuerySet采用懶加載的機制(只有要用到結果時才會訪問數據庫),因此,訪問數據庫時,要采用遍歷、切片、len()、list()、bool()等方式進行訪問,為了降低數據庫的負荷,每個QuerySet都會保留一份緩存
>>> print([p.date_created for p in Product.objects.all()]) ???>>> print([p.title for p in Product.objects.all()])
因此,兩次輸入 print([p.date_created for p in Product.objects.all()]) 的結果很可能不同
也可以只是對數據集做一些簡單的運算并返回結果(可以用count()查詢數量,用Avg查詢平均值等等)
數據庫事務
將數據庫上的許多操作,例如讀取數據庫、對象、寫入、獲取鎖封裝成一個工作單元,整個單元內的操作要么都成功,要么都失敗,這就叫事務,在Django當中,提供了控制數據庫事務管理的方法,要啟用其中將每一個請求包裝在事務中的功能,可以在數據庫的配置中設置:ATOMIC_REQUESTS為TRUE來開啟此功能
此功能的大概工作流程如下:調用視圖函數之前,Django會啟動一個事務,如果生成的響應沒有問題,Django就提交事務,否則就會回滾事務
(也可以使用atomic()上下文管理器):
from django.db import transactiondef my_view(request):try:with transaction.atomic():# 在這里執行數據庫操作...except Exception as e:# 處理異常,事務會自動回滾...return HttpResponse('Success or Error')from django.db import transaction@transaction.atomic ?# 作為裝飾器使用def viewfunc(request):do_stuff()def anotherviewfunc(request):with transaction.atomic(): ?# 作為上下文管理器使用do_more_stuff()
需要注意:視圖函數的執行包含在事務當中,但中間件和模板的渲染是在事務之外運行的
自動提交
?自動提交(Autocommit)模式是指數據庫在執行每個單獨的 SQL 語句后立即將其提交到數據庫。這意味著每個操作都是一個獨立的事務,沒有事務的開始和結束控制。這種模式通常用于簡單的數據庫操作,其中不需要復雜的事務控制。在 Django 中,默認情況下,數據庫操作是自動提交的。這意味著如果你不顯式地使用事務管理,每個數據庫操作(如 ?.save() , ?.delete() , ?.update() ?等)都會立即被提交。
?
可以在配置文件當中設置禁止
AUTOCOMMIT = False
提交后執行操作
在數據庫操作中,"提交后執行操作"通常指的是在事務提交之后執行某些特定的操作。在 Django 中,這可以通過幾種方式實現:
?
?
1.使用信號(Signals)
?
Django 的信號機制允許你在執行特定操作后發送信號,其他接收者可以監聽這些信號并執行相應的操作。例如,你可以在事務提交后發送一個信號。
from django.db import transactionfrom django.db.models.signals import post_savefrom django.dispatch import receiver@receiver(post_save, sender=MyModel)def my_model_saved(sender, instance, **kwargs):# 這個函數會在 MyModel 實例保存后執行# 確保在事務提交后執行if kwargs.get('created', False):# 實例創建后執行的操作pass# 在視圖中使用事務def my_view(request):try:with transaction.atomic():my_model_instance = MyModel.objects.create(...)# 其他數據庫操作...except Exception as e:# 處理異常pass
2.使用`transaction.on_commit`回調
?
Django 3.6+引入了`transaction.on_commit`回調,允許你在事務提交時執行某些操作。
from django.db import transactiondef my_view(request):def callback():# 事務提交后執行的操作passtry:with transaction.atomic():my_model_instance = MyModel.objects.create(...)# 其他數據庫操作...transaction.on_commit(callback)except Exception as e:# 處理異常pass
在這個例子中,`callback`函數將在事務成功提交后執行。
?
?
3.使用數據庫觸發器(Triggers)
?
對于更復雜的場景,你可以在數據庫層面使用觸發器(Triggers)。觸發器是數據庫對象,它們在特定事件發生時自動執行。例如,你可以創建一個觸發器,在插入或更新記錄后執行某些操作。
CREATE TRIGGER after_insert_my_modelAFTER INSERT ON my_modelFOR EACH ROWBEGIN-- 執行操作END;
請注意,使用觸發器需要對數據庫有更深入的了解,并且不同數據庫的觸發器語法可能不同。
?
?
注意事項
?
?
? 確保在事務提交后執行的操作不會違反事務的隔離性或一致性。
?
? 使用信號或回調時,確保它們不會引入死鎖或循環依賴。
?
? 在使用觸發器時,確保它們不會與應用邏輯沖突。
?
通過這些方法,你可以在 Django 中實現在事務提交后執行特定操作的需求。選擇哪種方法取決于你的具體場景和偏好。
?
數據庫并發控制
多用戶同時訪問和更改數據庫可能會引發沖突,因此,要協調同步事務,在兩個活動嘗試修改同一個數據庫的對象時,有三種情況,兩個活動會相互干擾:
- 臟讀:事務A讀取到了事務B的修改后尚未提交的數據,但事務B如果最后沒有選擇提交,那這種讀取方式就叫臟讀
?
- 不可重復讀:多次讀取同一個數據,兩次讀取結果不一致,這種情況一般發生在其他事務對該數據進行修改并提交
?
- 幻讀:A查詢后的下一刻數據庫被更改,導致查詢結果不準確
越是允許緩存當中的過時數據存在,則并發的線程/問題就越多,可能出現的問題也就越大
這會導致什么問題?具體舉個例子,A訪問數據庫時余額為10,然后B也訪問了數據庫,A從中取出3,也就是原數據庫改為7,但B如果進行存5的操作,此時由于數據庫并發的問題,B修改數據庫為10+5=15。
為了杜絕此類問題出現,需要確保正在處理的資源在工作時不會發生改變
悲觀鎖
是指實體在應用中存儲的整個周期,數據庫都處于鎖定狀態,以此來限制其他用戶使用這個數據庫當中的實體
可以是修改數據庫的人不希望其他人再次過程當中讀取實體,也可以是讀取數據庫的人不希望其他人在此過程當中修改實體
鎖的范圍可能是整個數據庫、表、多行或單行。這些鎖分別稱為數據庫鎖、表鎖、頁鎖和行鎖。
優點是簡單易用,缺點是當用戶數量過多時該方法限制了可同時操作的用戶數量
在Django中實現悲觀鎖可以通過`select_for_update()`方法來完成,它利用數據庫的行級鎖機制,確保在事務提交或回滾之前,其他事務無法修改被鎖定的行。
?
?
實現步驟
?
1. 使用`transaction.atomic()`裝飾器:確保操作在一個事務中完成,如果操作失敗,事務會自動回滾。
?
2. 調用`select_for_update()`方法:對查詢結果加鎖,鎖定特定的行。
?
3. 執行業務邏輯:在鎖定期間對數據進行修改。
?
4. 提交或回滾事務:如果操作成功,事務提交;如果失敗,事務回滾。
?
示例代碼
以下是一個使用悲觀鎖的Django視圖函數示例,模擬秒殺場景:
?
?
from django.db import transactionfrom django.http import HttpResponsefrom .models import Book, Orderimport timeimport random@transaction.atomicdef seckill(request):sid = transaction.savepoint() ?# 設置保存點book = Book.objects.select_for_update().filter(pk=1).first() ?# 加行鎖if book.count > 0:print('庫存可以,下單')Order.objects.create(order_id=str(time.time()), order_name='測試訂單')time.sleep(random.randint(1, 4)) ?# 模擬延遲book.count -= 1book.save()transaction.savepoint_commit(sid) ?# 提交事務return HttpResponse('秒殺成功')else:transaction.savepoint_rollback(sid) ?# 回滾事務return HttpResponse('庫存不足,秒殺失敗')
?
?
?
?
注意事項
?
1. 鎖的粒度:`select_for_update()`默認加行級鎖,但如果查詢條件未指定主鍵或唯一索引,可能會升級為表鎖。
?
2. 死鎖風險:在高并發場景下,悲觀鎖可能導致死鎖。需要合理設計事務邏輯,避免長時間持有鎖。
?
3. 數據庫支持:`select_for_update()`需要數據庫支持事務和行級鎖,如MySQL的InnoDB引擎。
?
通過這種方式,Django可以利用數據庫的鎖機制實現悲觀鎖,確保在并發場景下數據的一致性和完整性。
樂觀鎖
在沖突發生不頻繁時,可以考不阻止并發控制,而是選擇檢測沖突,并且在沖突發生時去解決他
updated = Account.objects.filter(id=self.id,version=self.version,).update(balance=F('balance') + amount,version=F('version') + 1,)
在原本定義的模型上多加上一個version,update語句其實暗含了檢查版本的功能(因為會先檢查version字段與傳入的version字段是否匹配,只有匹配才會執行更改)
解決沖突的基本途徑有:放棄、展示問題讓客戶決定、合并改動、記錄沖突讓后來的人決定、無視沖突直接覆蓋,在不同的情況下,我們可以選擇不同的鎖和不同的策略
數據庫擴展
縱向擴展
簡單來說,數據庫的擴展就是讓數據庫能夠處理更多流量和更多讀寫查詢,主流的擴展方法有縱向擴展和橫向擴展,縱向擴展采用的是增強單個數據庫能力的方法(增強cpu、內存、存儲空間等等)
優點:應用層不需要適配;缺點:硬件擴容有上限,成本高
橫向擴展
?
橫向擴展采用的是將多個數據庫組合起來的方法,和縱向擴展相比,此發可用性高,易于升級且成本低廉,缺點是對技術要求高并且要求對應用層做出更改
幾種橫向擴展的方法
讀寫分離
讀寫分離主要用到了MYSQL的復制功能
允許將一個來自MYSQL數據庫服務器的數據自動復制到一個或多個MYSQL數據庫服務器,該功能需要在修改Django配置的同時對于MYSQL也做出小的修改,效果是當一臺數據庫服務宕機后,能通過調整另外一臺庫以最快的速度恢復服務,由于主從復制是單向的,因此只有主數據庫用于寫操作,而讀操作則可以在多個數據庫上進行,因此,我們至少需要兩個數據庫,一個用于讀操作,另一個用于寫操作,要使復制功能正常工作,首先要把復制事件寫入日志,一般稱這個日志為Binlog,每當從數據庫連接到主數據庫,主數據庫就會創建新的線程,然后執行服務器對他的請求,大多請求會是將Binlog傳給從服務器并且通知從服務器有新的Binlog寫入
從服務器會起兩個線程來處理復制,一個稱I/O線程,負責連接到主服務器從中讀取二進制日志事件(中繼日志),另一個稱SQL線程,負責在本地的中繼日志當中讀取事件,然后本地執行
在Django當中提供了多數據庫請求路由來實現讀寫分離
使用步驟如下:
- 修改settings.py當中的DATABASES列表
DATABASES = {'default': { ?# 默認數據庫,主數據庫'NAME': 'multidb','ENGINE': 'django.db.backends.mysql','USER': 'some_user', ?# 數據庫賬號'PASSWORD': 'some_password', ?# 數據庫密碼'HOST': 'master_host_ip', ?# 數據庫IP},'slave': { ?# 從數據庫'NAME': 'multidb','ENGINE': 'django.db.backends.mysql','USER': 'some_user', ?# 數據庫賬號'PASSWORD': 'some_password', ?# 數據庫密碼'HOST': 'slave_host_ip' ?# 數據庫IP}}
- 將寫操作應用到主數據庫,讀操作應用到從數據庫
import randomclass RandomRouter:def db_for_read(self, model, **hints):return random.choice(['replica1', 'replica2']) ?# 隨機選一個從數據庫
- 在settings.py當中加上
DATABASE_ROUTERS = ['path.to.DefaultRouter']
垂直分庫
比較常見的一種做法是將不同的模塊放在不同的數據庫中。
?
?
?
通過路由,將不同模塊的請求轉到不同的數據庫當中,首先在settings.py當中配置多個數據庫:
DATABASES = {'default': {},'auth_db': {'NAME': 'auth_db','ENGINE': 'django.db.backends.mysql',# 其他配置...},'product_db': {'NAME': 'product_db','ENGINE': 'django.db.backends.mysql',# 其他配置...},'order_db': {'NAME': 'order_db','ENGINE': 'django.db.backends.mysql',# 其他配置...},}
然后根據模型的不同路由請求到不同的數據庫
class MultiDatabaseRouter:def db_for_read(self, model, **hints):if model == User: ?# 請求用戶數據庫return 'auth_db'elif model == Product: ?# 請求商品數據庫return 'product_db'elif model == Order: ?# 請求訂單數據庫return 'order_db'else:return 'default'
在settings.py當中加上
DATABASE_ROUTERS = ['path.to.MultiDatabaseRouter']
?
水平擴展
當用戶數量達到一定規模,單個數據庫或數據表無法容納用戶的全部數據,常見的做法就是對數據庫中的數據進行水平分區,每個單獨的分區稱為分片
每個分片承載單獨的數據庫,以分散負載
?
分片操作雖然提高了查詢性能,單本身也使應用層變得極其復雜,采用該策略需要慎重考慮
常見的分片方式有算法分片和動態分片——算法分片是讓數據通過算法寫入不同分片,動態分片則需要客戶端讀取其他存儲,確認分片信息,最簡單的算法分片是取模算法,因為筆者做不到每天300萬的用戶訪問量所以這部分咱就不討論了...
?