经典永不过时数据分析网红级别的项目案例分享超详细

Posted 报告,今天也有好好学习

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了经典永不过时数据分析网红级别的项目案例分享超详细相关的知识,希望对你有一定的参考价值。

大家好,你可以叫俺老吴,本周给大家分享的数据分析案例是泰坦尼克号幸存者预测的项目,没记错的话,这应该是很多朋友写在简历上的项目经历。不过这里我先提一嘴,如果你目前正在找工作,想要充实项目经历的话,建议还是不要把它写到你的简历上了,原因就是刚刚说的——实在太多人写在简历上了,没有特色。

但是,虽然不建议你写到简历上,却强烈建议你亲身体验一遍,毕竟这么多人都喜欢的项目还是有它的道理在的。

详细的项目背景介绍可以参考这里,多的不说,本篇博客将详细地把整个项目流程分享给你,欢迎收藏

泰坦尼克号幸存者预测

import numpy as np  # 科学计算工具包
import pandas as pd  # 数据分析工具包
import matplotlib.pyplot as plt # 图表绘制工具包
import seaborn as sns # 基于 matplot, 导入 seaborn 会修改默认的 matplotlib 配色方案和绘图样式,这会提高图表的可读性和美观性

# 算法库
from sklearn import linear_model
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import Perceptron
from sklearn.linear_model import SGDClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC, LinearSVC
from sklearn.naive_bayes import GaussianNB

# 在 jupyter notebook 里面显示图表
%matplotlib inline 

1 获取数据集

读取参数指定的文件,返回一个DataFrame类型的对象。

有两个数据集:训练集和测试集。 我们将使用训练集来构建我们的预测模型,用测试集来对其进行评分并生成输出文件以在Kaggle评估系统上提交。

test_df = pd.read_csv("./test.csv")
train_df = pd.read_csv("./train.csv")

1.1 探索性数据分析

开始探索数据,带着问题去理解数据。

通过以下的属性和方法了解数据

  • 属性
    • df.columns.value 数据集中所有列名(特征),numpy.ndarray 类型
  • 方法
    • df.head() 预览数据集前5行
    • df.tail() 预览数据集后5行
    • df.info() 显示大致数据信息,包括每列名称,非空值数量,每列的数据类型,内存占用等信息。
    • df.describe() 展示数据的一些描述性统计信息,但会过滤掉缺失值。默认只统计数值类型的字段内容。
      • describe(include=‘ALL‘),统计所有类型的数据
      • describe(include=[np.number]) 只统计数值类型的字段内容:count计数,mean平均数,std方差,min最小值,四分位数,max 最大值
      • describe(include=[np.object]) 只统计object类型的字段内容
      • `describe(include=‘O’) 只统计字符串类型的字段内容:count计数,unique唯一值数量,top出现频率最高的内容,freq最高出现频率
      • describe(percentiles=[]):设置输出的百分位数,默认为[.25,.5,.75],返回第25,第50和第75百分位数。

1.1.1 预览数据

  • df.head() 预览数据集前5行
train_df.head()
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
0103Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNS
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123S
4503Allen, Mr. William Henrymale35.0003734508.0500NaNS

Survived 列是目标变量,这是我们要预测的变量。 如果 Survived 为 1,乘客幸免于难,为 0,表示未存活。

Survived是否存活
0死亡
1存活

其他描述乘客的变量,是特征

数据特征意思
PassengerId乘客ID
Pclass乘客等级(1,2,3)
Name乘客姓名
Sex乘客性别 (Female,Male)
Age乘客年龄
SibSp与乘客同行的兄弟姐妹和配偶的数量
Parch与乘客同行的父母和孩子的数量
Ticket船票号码
Fare票价
Cabin船舱号码
Embarked乘客登船港口(C = Cherbourg, Q = Queenstown, S = Southampton)

1.1.2 数据集的大致信息

df.info() 显示大致数据信息,包括每列名称,非空值数量,每列的数据类型,内存占用

train_df.info()
print('_'*40)
test_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
________________________________________
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  418 non-null    int64  
 1   Pclass       418 non-null    int64  
 2   Name         418 non-null    object 
 3   Sex          418 non-null    object 
 4   Age          332 non-null    float64
 5   SibSp        418 non-null    int64  
 6   Parch        418 non-null    int64  
 7   Ticket       418 non-null    object 
 8   Fare         417 non-null    float64
 9   Cabin        91 non-null     object 
 10  Embarked     418 non-null    object 
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB
  • 训练数据集

    • 行数 : 891个样本大小
    • 列数 :12(11个特征+1个目标变量 Survived
    • 数据类型 : 7个特征是整数或浮点数,5个特征是字符串
    • 缺失值情况 :Cabin > Age > Embarked (数量从大到小排序)
    • 数值型数据 :PassengerId,Age,Fare,SibSp,Parch
    • 类别数据 :Survived,Sex,Embarked,Pclass
    • 混合型数据:Ticket,Cabin
  • 测试数据集

    • 列数 :11个特征
    • 数据类型 : 6个特征是整数或浮点数,5个特征是字符串
    • 缺失值情况 :Cabin > Age > Fare (数量从大到小排序)

知识点

  • 类别数据(定性数据)

    数据被划分为各种类别,用以描述某类得性质或特征。因此,类别数据也称为定性数据。游戏种类就是定性数据的一个实例 —— 每种游戏种类形成一个独立的类别。关于定性数据,请记住一个重点:不能将数据值理解为数字。

  • 数值型数据(定量数据)

    数值型数据不同,它所涉及的是数字。数值型数据中的数值具有数字的意义,但还涉及计量或计数。由于数值型数据描述的是数量,所以也称为定量数据。


1.1.3 数据集的描述性统计信息

df.describe() 返回数据数值情况,数值数据的字段内容:count计数,mean平均数,std方差,min最小值,四分位数,max 最大值

train_df.describe()
PassengerIdSurvivedPclassAgeSibSpParchFare
count891.000000891.000000891.000000714.000000891.000000891.000000891.000000
mean446.0000000.3838382.30864229.6991180.5230080.38159432.204208
std257.3538420.4865920.83607114.5264971.1027430.80605749.693429
min1.0000000.0000001.0000000.4200000.0000000.0000000.000000
25%223.5000000.0000002.00000020.1250000.0000000.0000007.910400
50%446.0000000.0000003.00000028.0000000.0000000.00000014.454200
75%668.5000001.0000003.00000038.0000001.0000000.00000031.000000
max891.0000001.0000003.00000080.0000008.0000006.000000512.329200
  • Survied 的均值为 0.383838,而 survied 值只有0或1,说明这个均值反应了存活率。
    • 样本存活率约38%,实际存活率为32%(2224-1502/2224=32.4%)

从目前来看,要注意几点。

  • 需要将非数值型特征转换为数值型特征,以便机器学习算法后期处理。
  • 这些特征的范围有很大的差异,需要将其转换为大致相同的尺度。
  • 一些特征包含缺失值(NaN = 不是数字),们需要处理。

1.1.4 数据集缺失值详情

  • df.isnull() 返回表明哪些值是缺失值的布尔值
  • df.notnull() 返回表明哪些值不是缺失值的布尔值
  • df.dropna() 根据每个标签的值是否是缺失数据来筛选轴标签,并根据允许丢失的数据量来确定阀值
  • df.sort_values() 排序,默认升序,ascending = False表示降序
  • pd.concat([]) 使对象在轴向上进行粘合或“堆叠“,默认是沿着 axis=0(行) 的轴向。axis=1 表示(列)的轴向。
  • round(x[, n]) 方法返回浮点数x的四舍五入值,n小数点位数
train_total = train_df.isnull().sum().sort_values(ascending=False)
percent_1 = train_df.isnull().sum()/train_df.isnull().count()*100
percent_2 = round(percent_1,1).sort_values(ascending=False)
train_miss_data = pd.concat([train_total,percent_2],axis=1,keys=['total','%'])
train_miss_data.head()
total%
Cabin68777.1
Age17719.9
Embarked20.2
Fare00.0
Ticket00.0
test_total = test_df.isnull().sum().sort_values(ascending=False)
percent_1 = test_df.isnull().sum()/test_df.isnull().count()*100
percent_2 = round(percent_1,1).sort_values(ascending=False)
test_miss_data = pd.concat([test_total,percent_2],axis=1,keys=['total','%'])
test_miss_data.head()
total%
Cabin32778.2
Age8620.6
Fare10.2
Embarked00.0
Ticket00.0
  • 训练数据集
    • Embarked 特征只有2个缺失的值,可以很容易地填补。
    • Age 特征就比较麻烦了,因为它有177个缺失值。
    • Cabin 特征需要进一步调查,但看起来可能要从数据集中删除它,因为缺失值比例高达 77%。
  • 测试数据集
    • Fare 特征只有1个缺失的值,可以很容易地填补。
    • Age 特征有86个缺失值。
    • Cabin 特征需要进一步调查,但看起来可能要从数据集中删除它,因为缺失值比例高达 78.2%。

isnull用法

  • df.isnull() #元素为空或者NA就显示True,否则就是False
  • df.isnull().any() #判断哪些列包含缺失值,该列存在缺失值则返回True,反之False。

对比count() 、isnull().count()和isnull().sum()

  • df.count() #每一列中非缺失值的个数
  • df.isnull().count() #每一列总元素个数
  • df.isnull().sum() #每列缺失数据的个数

1.1.5 假设

哪些特征可能和存活有关?

train_df.columns.values
array(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
       'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype=object)

初步判断,除了PassengerId、Ticket和 Name之外,其他特征都可能与存活有关。

1.1.5.1 数值型 Age、类别型 Sex、类别型 Survived 三者之间的关系:

  • 首先将数据按照性别(Female, Male)划分
  • 基于性别,再根据存活状态(Survived,Not_Survived)划分
  • 就会有四种搭配
    - Female & Survived
    - Female & Not_Survived
    - Male & Survived
    - Male & Not_Survived
  • 鉴于 Age 特征有缺失值,在此处可视化分析时,需要去除 Age 的缺失值
# 按性别筛选出数据
women = train_df[train_df['Sex']=='female']
men = train_df[train_df['Sex']=='male']

# 在性别的基础上筛选出存活和未存活的数据

# 选出存活的数据
F_survived = women[women['Survived']==1]
M_survived = men[men['Survived']==1]

# 选出未存活的数据
F_not_surv = women[women['Survived']==0]
M_not_surv = men[men['Survived']==0]

F_survived.head()
PassengerIdSurvivedPclassNameSexAgeSibSpParchTicketFareCabinEmbarked
1211Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85C
2313Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNS
3411Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123S
8913Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)female27.00234774211.1333NaNS
91012Nasser, Mrs. Nicholas (Adele Achem)female14.01023773630.0708NaNC

求某一列的缺失值情况

由于 Dataframe 数据中选择某一列的方式有 (按照字典型标记或属性那样检索为 Series)
- df.A 的属性方式
- df[‘A’] 的方式
所以求某一列的缺失值情况也有两种
- df.A.isnull().sum()
- df[‘A’].isnull().sum()

根据某一列去除缺失值
- df.A.isnull().dropna()
- df[‘A’].isnull().dropna()

# 每种数据去除 Age 缺失值
print('去除前,Female survived null', F_survived.Age.isnull().sum())

# 去除 Age 缺失值
F_survived.Age.dropna()
M_survived.Age.dropna()
F_not_surv.Age.dropna()
M_not_surv.Age.dropna()

print('取出后,Female survived null',F_survived.Age.dropna().isnull().sum())
去除前,Female survived null 36
取出后,Female survived null 0

知识点

displot()集合了matplotlib的hist()与核函数估计kdeplot的功能,增加了rugplot分布观测条显示与利用scipy库fit拟合参数分布的新颖用途。具体用法如下::

seaborn.distplot(a, bins=None, hist=True, kde=True, rug=False, fit=None, hist_kws=None, kde_kws=None, rug_kws=None, fit_kws=None, color=None, vertical=False, norm_hist=False, axlabel=None, label=None, ax=None)
- 参数 ax:选择位置
- 参数 hist、kde,调节是否显示直方图及核密度估计(默认hist,kde均为True)

问题:直方图中参数 bins,这里是根据什么设置的?

  • 极端情况, bins = 1
    上图展现了极端情况, 当 bins = 1,表示所有的数据都在一个组内。分组太粗糙影响数据分组规律的明显性,什么都看不出。

  • 极端情况, bins = 数据大小
    下图进行极端情况的对比, 当 bins = len(data),表示如果所有的数据都不同,将出现和数据长度一样的组。分组太细致会引起较大的误差,因此组数的确定要适当。

组距的宽度:用数据中(最大值-最小值)/组数


sns.set() # 声明使用 Seaborn 样式

fig,axes = plt.subplots(nrows=1,ncols=2,figsize=(16,8)) # 创建一个 Figure, 子图为1行,2列
survived = 'survived' # 图例 label 
not_survived = 'not survived' # 图例 label

ax = sns.distplot(F_survived.Age.dropna(),bins=18,ax=axes[0],kde=False)
ax = sns.distplot(F_not_surv.Age.dropna(),bins=40,ax=axes[0],kde=False)
ax.legend([survived,not_survived]) # 图例 label 放置位置1
ax.set_title('Female')

ax = sns.distplot(M_survived.Age.dropna(),bins=18,ax=axes[1],label=survived,kde=False) # 图例 label 放置位置2
ax = sns.distplot(M_not_surv.Age.dropna(),bins=40,ax=axes[1],label=not_survived,kde=False)
ax.legend()
ax.set_title('Male')
Text(0.5, 1.0, 'Male')

可以看出

  • 对于男性来说,大概20岁到35岁之间的存活率较高的;5岁到18岁之间的存活概率很低,
  • 对于女性来说,大概15岁到40岁之间的存活概率更高
  • 无论男女,婴儿的存活概率会高一点。

似乎有一些特定的年龄段,存活几率会增加。下面来观察 Age 和 Survived 的关系

1.1.5.1.1 数值型 Age、 类别型 Survived 两者之间的关系

FacetGrid当您想要在数据集的子集中分别可视化变量的分布或多个变量之间的关系时,该类非常有用。一个FacetGrid可以与多达三个维度可以得出:row,col,和hue。前两个与得到的轴阵列有明显的对应关系; 将色调变量视为沿深度轴的第三个维度,其中不同的级别用不同的颜色绘制。

FacetGrid(data, row=None, col=None, hue=None, col_wrap=None, sharex=True, sharey=True, height=3, aspect=1, palette=None, row_order=None, col_order=None, hue_order=None, hue_kws=None, dropna=True, legend_out=True, despine=True, margin_titles=False, xlim=None, ylim=None, subplot_kws=None, gridspec_kws=None, size=None)
  • col 列上的变量(左右)
  • row 行上的变量(上下)

这里将 FacetGrid 函数用于不同存活率的值,独立分成两个直方图。

import matplotlib.pyplot as plt #导入 matplotlib.pyplot,并简写成plt
import seaborn as sns
import numpy as np  #导入numpy包,用于生成数组
import pandas as pd #导入pandas包,用于数据分析
#IPython notebook中的魔法方法,这样每次运行后可以直接得到图像,不再需要使用plt.show()
%matplotlib inline  

g = sns.FacetGrid(train_df, col='Survived')
g.map(plt.hist, 'Age', bins=20)

可以看出

  • 婴儿(Age<=4)有较高的生存率
  • 老人(Age=80)全部生还
  • 大量的15-25年纪的乘客没有生还
  • 乘客主要在15-35的年纪范围内
1.1.5.1.2 类别型 Sex、 数值型 Survived 两者之间的关系 (这里用的是数值型 Survived,可能需要放到其他位置)
import matplotlib.pyplot as plt #导入 matplotlib.pyplot,并简写成plt
import seaborn as sns
import numpy as np  #导入numpy包,用于生成数组
import pandas as pd #导入pandas包,用于数据分析
#IPython notebook中的魔法方法,这样每次运行后可以直接得到图像,不再需要使用plt.show()
%matplotlib inline  

sns.barplot(x='Sex', y='Survived', data=train_df)

可以看出,女性乘客的幸存率比男性高。

1.1.5.2 类别型 Embarked、类别型 Pclass、类别型 Sex、数值型 Survived 4者之间的关系

grid = sns.FacetGrid(train_df, row='Embarked',height=2.2, aspect=1.6) 
grid.map(sns.pointplot, 'Pclass', 'Survived','Sex',palette='deep',hue_order=['female','male'],order=[1,2,3]) 
grid.add_legend()

按照性别分类,Embarked 似乎与存活率有关。

  • 不论从哪个港口登船,Pclass 一样的前提下,女性乘客的存活率都高于男性。
  • 对于从S或C港口登船的男性乘客中,Pclass=1的存活率比 Pclass =2, Pclass=3 都高。
  • 对于从Q港口登船的男性乘客中,Pclass=3的存活率比 Pclass =1, Pclass=2 高。

Pclass 似乎也与存活率有关。下面来观察 Pclass 和 Survived 的关系

1.1.5.3 类别型 Pclass、 数值型 Survived 两者之间的关系

sns.barplot(x='Pclass', y='Survived', data=train_df)

明显可以看出 Pclass=1的乘客存活率更高

1.1.5.4 类别型 Pclass、数值型 Age、 类别型 Survived 三者之间的关系

aspect:每个小图表的横轴长度和纵轴的比,默认为1; height:每个小图表的高度设定,默认为3

grid = sns.FacetGrid(train_df, col='Survived', row='Pclass', height=2.2, aspect=1.6)
grid.map(plt.hist, 'Age', alpha=.5, bins=20)
grid.add_legend();

# 或者用这个
grid = sns.FacetGrid(train_df,hue='Survived',row='Pclass')
grid.map(plt.hist,"Age",bins=20)
grid.add_legend()

1.1.5.5 数值型 SibSp、 数值型 Parch、 数值型 Survived 3者之间的关系

SibSp 和 Parch 组合在一起使用才更有意义,组合起来表示与乘客同行的亲属人数。并计算出单独出行和非单独出行的人数

data = [train_df, test_df]  # 训练集和测试集
for dataset in data:
    dataset['relatives'] = dataset['SibSp'] + dataset['Parch']
    dataset.loc[dataset['relatives'] > 0, 'not_alone'] = 0
    dataset.loc[dataset['relatives'] == 0, 'not_alone'] = 1
    dataset['not_alone'] = dataset['not_alone'].astype(int)
train_df['not_alone'].value_counts()

1    537
0    354
Name: not_alone, dtype: int64

知识点:loc 根据条件,对新增列赋值

df.loc[条件,新增列] = 赋初始值

如果新增列名为已有列名,则在原来的数据列上改变

df1 = pd.DataFrame(np.random.rand(8,4),index=list('abcdefgh'),columns=['A','B','C','D'])
print(df1)

df1.loc[df1['A']<0.5,'小于0.5'] = 1
df1.loc[df1['A']>0.5,'小于0.5'] = 0
print(df1)
          A         B         C         D
a  0.075956  0.513582  0.090774  0.542909
b  0.156261  0.873195  0.974398  0.920191
c  0.015779  0.786579  0.356560  0.604733
d  0.574546  0.742924  0.243504  0.751018
e  0.814584  0.196367  0.067841  0.232922
f  0.480406  0.103638  0.301940  0.153113
g  0.863849  0.479492  0.110800  0.322068
h  0.395352  0.851746  0.858481  0.225225
          A         B         C         D  小于0.5
a  0.075956  0.513582  0.090774  0.542909    1.0
b  0.156261  0.873195  0.974398  0.920191    1.0
c  0.015779  0.786579  0.356560  0.604733    1.0
d  0.574546  0.742924  0.243504  0.751018    0.0
e  0.814584  0.196367  0.067841  0.232922    0.0
f  0.480406  0.103638  0.301940  0.153113    1.0
g  0.863849  0.479492  0.110800  0.322068    0.0
h  0.395352  0.851746  0.858481  0.225225    1.0

知识点:value_counts() 方法

返回一个序列 Series,该序列包含每个值的数量。也就是说,对于数据框中的任何列,value-counts () 方法会返回该列每个项的计数。

  • 对该列中出现的每个值进行计数(无效值会被排除) 默认降序排序
    • value_counts(ascending=True) 升序
  • 求各个值的相对频率
    • value_counts(normalize=True)
df1 = pd.DataFrame(np.random.rand(8,4),index=list('abcdefgh'),columns=['A','B','C','D'])
print(df1)

df1.loc[df1['A']<0.5,'小于0.5'] = 1
print(df1)
print(df1['小于0.5'].value_counts())
df1.loc[df1['A']>0.5,'小于0.5'] = 0
print(df1['小于0.5'].value_counts(normalize=True))
          A         B         C         D
a  0.387505  0.040494  0.105987  0.320365
b  0.990164  0.930623  0.644406  0.467170
c  0.932130  0.681749  0.080384  0.409407
d  0.985110  0.305801  0.690751  0.145207
e  0.247883  0.645068  0.049671  0.899674
f  0.654758  0.152449  0.750448  0.716139
g  0.753139  0.387617  0.299998  0.236939
h  0.937349  0.227210  0.722307  0.555785
          A         B         C         D  小于0.5
a  0.387505  0.040494  0.105987  0.320365    1.0
b  0.990164  0.930623  0.644406  0.467170    NaN
c  0.932130  0.681749  0.080384  0.409407    NaN
d  0.985110  0.305801  0.690751  0.145207    NaN
e  0.247883  0.645068  0.049671  0.899674    1.0
f  0.654758  0.152449  0.750448  0.716139    NaN
g  0.753139  0.387617  0.299998  0.236939    NaN
h  0.937349  0.227210  0.722307  0.555785    NaN
1.0    2
Name: 小于0.5, dtype: int64
0.0    0.75
1.0    0.25
Name: 小于0.5, dtype: float64

知识点 astype(int)

用于转化dateframe某一列的数据类型

如下将dateframe某列的str类型转为int,注意astype()没有replace=True的用法,想要在原数据上修改,要写成如下形式。

app_train[['uid','index']] = app_train[['uid','index']].astype(int)

注意只有当该列的字符串全是由纯数字构成时才可以这样写,如果混有字母,会报错:ValueError: invalid literal for int() with base 10

利用int()函数转字符串也类似

isdigit()用于判断一个字符串是否由纯数字构成,如果是返回True,否则False

df1['小于0.5'].astype以上是关于经典永不过时数据分析网红级别的项目案例分享超详细的主要内容,如果未能解决你的问题,请参考以下文章

Android App实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)

如何保障缓存和数据库的一致性(超详细案例)

如何保障缓存和数据库的一致性(超详细案例)

如何保障缓存和数据库的一致性(超详细案例)

如何保障缓存和数据库的一致性(超详细案例)

经典10046剖析案例-2