MySQL數(shù)據(jù)權(quán)限的實現(xiàn)詳情
數(shù)據(jù)權(quán)限模型
上篇文章的數(shù)據(jù)模型是基于傳統(tǒng)的RBAC模型來設(shè)計的,由于我們這里的應(yīng)用場景不一樣,所以這里的數(shù)據(jù)權(quán)限模型并沒有嚴(yán)格按照上篇文章的方案來設(shè)計,但是萬變不離其宗,核心原理還是相同的。
首先我來介紹一下我們最終實現(xiàn)的效果
實現(xiàn)效果

一個組件(可以理解成菜單)可以綁定多個授權(quán)維度,當(dāng)給角色授權(quán)組件時可以給這個授權(quán)組件賦予不同維度的權(quán)限。
關(guān)于數(shù)據(jù)權(quán)限的授權(quán)維度有以下幾個關(guān)鍵點需要仔細(xì)體會:
- 給一個角色勾選授權(quán)維度實際上是在限制這個角色所能看到的數(shù)據(jù)范圍
- 任何一個授權(quán)維度勾選了"全部",相當(dāng)于不限制此維度的權(quán)限。
- 如果一個角色勾選了客戶群的全部 + A產(chǎn)品線,那么最終生成的sql 會是
where 產(chǎn)品線 in ('A產(chǎn)品線')
- 如果一個角色勾選了客戶群的全部 + A產(chǎn)品線,那么最終生成的sql 會是
- 如果一個角色勾選了多個維度,維度之間用 AND 拼接
- ? 如果一個角色勾選了A客戶群 + B產(chǎn)品線,那么最終生成的sql 會是
where 客戶群 in('A客戶群')AND 產(chǎn)品線 in ('B產(chǎn)品線')
- ? 如果一個角色勾選了A客戶群 + B產(chǎn)品線,那么最終生成的sql 會是
- 一個用戶可能有多個角色,角色之間用 OR 拼接
- ? 一個用戶有兩個角色:客戶群總監(jiān),產(chǎn)品線經(jīng)理。其中客戶群總監(jiān)角色擁有A客戶群和B客戶群的權(quán)限,產(chǎn)品線經(jīng)理角色擁有A產(chǎn)品線權(quán)限,那么最終生成的sql會是
where 客戶群 in ('A客戶群','B客戶群') OR 產(chǎn)品線 in ('A產(chǎn)品線')
- ? 一個用戶有兩個角色:客戶群總監(jiān),產(chǎn)品線經(jīng)理。其中客戶群總監(jiān)角色擁有A客戶群和B客戶群的權(quán)限,產(chǎn)品線經(jīng)理角色擁有A產(chǎn)品線權(quán)限,那么最終生成的sql會是
當(dāng)然我們業(yè)務(wù)場景中數(shù)據(jù)規(guī)則比較單一,全部使用
in作為sql條件連接符,你們可以根據(jù)實際業(yè)務(wù)場景進(jìn)行補充。
數(shù)據(jù)模型
最終的數(shù)據(jù)模型如下所示:

這里的組件大家完全可以理解成RBAC模型中的資源、菜單,只不過叫法不同而已。
數(shù)據(jù)權(quán)限表結(jié)構(gòu)
下面是具體的表結(jié)構(gòu)設(shè)計
授權(quán)維度表
CREATE TABLE `wb_dimension` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵', `DIMENSION_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '維度編碼', `DIMENSION_NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '維度名稱', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='授權(quán)維度'
具體授權(quán)維度表(產(chǎn)品線)
CREATE TABLE `wb_dimension_proc_line` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵', `DIMENSION_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '維度編碼', `PROC_LINE_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '產(chǎn)品線編碼', `PROC_LINE_NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '產(chǎn)品線名稱', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='授權(quán)維度-產(chǎn)品線'
跟授權(quán)維度表實際是一個表繼承的關(guān)系,由于每個授權(quán)維度的屬性不一樣,展現(xiàn)形式也不一樣,所以分表存儲。
組件路由表
CREATE TABLE `wb_route` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵ID', `COMPONENT_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '組件ID', `ROUTE_URL` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '路由地址', `AUTHORIZATION_TYPE` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '授權(quán)方式:1 自定義,2 上下級授權(quán), 3 范圍授權(quán)', `AUTHORIZATION_DIMENSION` json DEFAULT NULL COMMENT '授權(quán)維度', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='組件路由' 復(fù)制代碼
當(dāng)組件屬性授權(quán)方式為范圍授權(quán)時在應(yīng)用側(cè)會強制要求選擇具體的授權(quán)維度,如 產(chǎn)品線、客戶群。
角色表
CREATE TABLE `wb_role` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵ID', `ROLE_CODE` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色CODE', `ROLE_NAME` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色名稱', `IDENTITY_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '身份ID' PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色表'
角色上有一個身份屬性,多個角色可以歸屬同一個身份,方便對角色進(jìn)行分類管理。
角色組件綁定表
CREATE TABLE `role_component_relation` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵ID', `ROLE_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色I(xiàn)D', `COMPONENT_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '組件ID', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色授權(quán)組件'
角色組件授權(quán)規(guī)則表(核心)
CREATE TABLE `wb_role_component_rule` ( `ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主鍵', `ROLE_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '角色I(xiàn)D', `COMPONENT_ID` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '組件ID', `RULE_CODE` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '規(guī)則編碼', `RULE_NAME` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '規(guī)則名稱', `RULE_CONDITION` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '規(guī)則條件', `RULE_VALUE` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '規(guī)則值', PRIMARY KEY (`ID`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='角色組件維度規(guī)則表'
數(shù)據(jù)權(quán)限的核心表,規(guī)則條件的取值為IN,規(guī)則值存儲具體的維度編碼,當(dāng)在數(shù)據(jù)維度中選擇 全部 時我們將規(guī)則值存儲為ALL這個特殊值,方便后續(xù)生成SQL語句。

實現(xiàn)過程
- 自定義一個數(shù)據(jù)權(quán)限的注解,比如叫
DataPermission - 在對應(yīng)的資源請求方法,比如商機列表上添加自定義注解
@DataPermission - 利用AOP抓取到用戶對應(yīng)角色的所有數(shù)據(jù)規(guī)則并進(jìn)行SQL拼接,最終在SQL層面實現(xiàn)數(shù)據(jù)過濾。
代碼實現(xiàn)
自定義數(shù)據(jù)權(quán)限注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
public @interface DataPermission {
/**
* 數(shù)據(jù)權(quán)限類型
* 1 上下級授權(quán) 2 數(shù)據(jù)范圍授權(quán)
*/
String permissionType() default "2";
/**
* 配置菜單的組件路徑,用于數(shù)據(jù)權(quán)限
*/
String componentRoute() default "";
}定義數(shù)據(jù)權(quán)限處理切面
@Aspect
@Slf4j
public class DataPermissionAspect {
@Autowired
private RoleComponentRuleService roleComponentRuleService;
@Pointcut("@annotation(com.ifly.workbench.security.annotation.DataPermission)")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable{
HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
//獲取請求token
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
String userName = JwtUtil.getUsername(token);
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataPermission permissionData = method.getAnnotation(DataPermission.class);
//獲取授權(quán)方式
String permissionType = permissionData.permissionType();
//獲取組件路由
String componentRoute = permissionData.componentRoute();
if (StringUtils.isNotEmpty(componentRoute)){
// 查找當(dāng)前用戶此組件下的所有規(guī)則
List<RoleComponentRuleDTO> componentRules = roleComponentRuleService.getRoleComponentRule(userName, componentRoute);
if(CollectionUtils.isNotEmpty(componentRules)){
DataPermissionUtils.installDataSearchConditon(request, componentRules);
SysUserCacheInfo userInfo = buildCacheUser(userName);
DataPermissionUtils.installUserInfo(request, userInfo);
}
}
return point.proceed();
}
private SysUserCacheInfo buildCacheUser(String userName) {
SysUserCacheInfo info = new SysUserCacheInfo();
info.setSysUserName(userName);
info.setOneDepart(true);
return info;
}
}在AOP中獲取當(dāng)前用戶、需要訪問的組件中所有的數(shù)據(jù)規(guī)則,參考wb_role_component_rule表設(shè)計,并將其放到Request作用域中。
數(shù)據(jù)權(quán)限工具類
public class DataPermissionUtils {
public static final String COMPONENT_DATA_RULES = "COMPONENT_DATA_RULES";
public static final String SYS_USER_INFO = "SYS_USER_INFO";
/**
* 往鏈接請求里面,傳入數(shù)據(jù)查詢條件
* @param request
* @param componentRules
*/
public static void installDataSearchConditon(HttpServletRequest request, List<RoleComponentRuleDTO> componentRules) {
// 1.先從request獲取MENU_DATA_AUTHOR_RULES,如果存則獲取到LIST
List<RoleComponentRuleDTO> list = loadDataSearchCondition();
if (list==null) {
// 2.如果不存在,則new一個list
list = Lists.newArrayList();
}
list.addAll(componentRules);
// 3.往list里面增量存指
request.setAttribute(COMPONENT_DATA_RULES, list);
}
/**
* 獲取請求對應(yīng)的數(shù)據(jù)權(quán)限規(guī)則
*
*/
@SuppressWarnings("unchecked")
public synchronized List<RoleComponentRuleDTO> loadDataSearchCondition() {
return (List<RoleComponentRuleDTO>) SpringContextUtils.getHttpServletRequest().getAttribute(COMPONENT_DATA_RULES);
}
public synchronized void installUserInfo(HttpServletRequest request, SysUserCacheInfo userinfo) {
request.setAttribute(SYS_USER_INFO, userinfo);
}
}在Request中存儲數(shù)據(jù)規(guī)則。
查詢組件規(guī)則
public interface RoleComponentRuleService extends IService<RoleComponentRule> {
/**
* 根據(jù) 用戶域賬戶和組件編碼 獲取組件對應(yīng)的關(guān)系
*
* @param userName 域賬號
* @param componentCode 組件編碼
* @return 用戶的所有規(guī)則
*/
List<RoleComponentRuleDTO> getRoleComponentRule(String userName, String componentCode);
}@Service
public class RoleComponentRuleServiceImpl extends ServiceImpl<RoleComponentRuleMapper, RoleComponentRule> implements RoleComponentRuleService {
@Resource
private RoleComponentRuleMapper roleComponentRuleMapper;
/**
* 根據(jù) 用戶域賬戶和組件編碼 獲取組件對應(yīng)的關(guān)系
* @param userName 域賬號
* @param componentCode 組件編碼
* @return 用戶的所有規(guī)則
*/
@Override
public List<RoleComponentRuleDTO> getRoleComponentRule(String userName, String componentCode) {
return roleComponentRuleMapper.getRoleComponentRule(userName,componentCode);
}
}<select id="getRoleComponentRule" resultType="com.ifly.vo.RoleComponentRuleDTO">
SELECT
tab1.id,
tab1.role_id,
tab4.role_code,
tab1.component_id,
tab1.rule_code,
tab1.rule_name,
tab1.rule_condition,
tab1.rule_value,
tab4.identity_id
FROM
wb_role_component_rule tab1
LEFT JOIN user_role_relation tab2 ON tab2.role_id = tab1.role_id
LEFT JOIN wb_component tab3 ON tab3.id = tab1.component_id
LEFT JOIN wb_role tab4 ON tab4.id = tab1.role_id
JOIN role_component_relation tab5 ON tab5.role_id = tab1.role_id
AND tab5.component_id = tab1.component_id
where
tab2.user_account = #{userName}
and tab3.component_code = #{componentCode}
</select>Controller調(diào)用
@ApiOperation(value = "服務(wù)BU-領(lǐng)導(dǎo)-總覽")
@GetMapping("opp/getLeaderOverviewSve")
@DataPermission(componentRoute = "020202")
public Result<SalesProjOverviewSve> getLeaderOverviewSve(@RequestParam(name = "identityId") String identityId) {
String permissionSql = RuleQueryGenerator.getPermissionSql(identityId);
log.info("查服務(wù)BU-領(lǐng)導(dǎo)-總覽-permissionSQL==" + permissionSql);
return Result.OK(overviewSveService.getLeaderOverviewSve(permissionSql));
}在controller的請求方法上加上自定義注解@DataPermission并指定組件編碼,然后通過工具類生成SQL條件,最后將SQL條件傳入service層進(jìn)行處理。
構(gòu)建數(shù)據(jù)權(quán)限SQL
@Slf4j
@UtilityClass
public class RuleQueryGenerator {
private static final String SQL_AND = " and ";
private static final String SQL_OR = " or ";
private static final String SQL_JOINT = " (%s) ";
/**
* 獲取帶有數(shù)據(jù)權(quán)限的SQL
* @param identityId 身份ID
*/
public String getPermissionSql(String identityId) {
//------------------------獲取當(dāng)前身份的數(shù)據(jù)規(guī)則------------------------------------
List<RoleComponentRuleDTO> conditionList = getCurrentIdentyPermission(identityId);
if (CollectionUtils.isEmpty(conditionList)) {
//沒有權(quán)限
return "1 = 0";
}
//存在權(quán)限
//對當(dāng)前身份根據(jù)規(guī)則編碼分組-去除不同角色中相同編碼且規(guī)則值為ALL的規(guī)則 并根據(jù)角色id分組
Map<String, List<RoleComponentRuleDTO>> ruleMap = getRuleMapByRoleId(conditionList);
StringBuilder sb = new StringBuilder();
String roleSql;
if (MapUtils.isNotEmpty(ruleMap)) {
//按角色拼接SQL
for (Map.Entry<String, List<RoleComponentRuleDTO>> entry : ruleMap.entrySet()) {
List<RoleComponentRuleDTO> lists = entry.getValue();
// 同角色之間使用 AND
roleSql = buildRoleSql(lists);
//角色之間使用 OR
if (StringUtils.isNotEmpty(roleSql)) {
jointSqlByRoles(sb, roleSql);
}
}
}
return sb.toString();
}
private static List<RoleComponentRuleDTO> getCurrentIdentyPermission(String identityId) {
//----------------------------獲取所有數(shù)據(jù)規(guī)則-----------------------------
List<RoleComponentRuleDTO> roleRuleList = DataPermissionUtils.loadDataSearchCondition();
if(CollectionUtils.isEmpty(roleRuleList)){
return null;
}
//-----------------------------過濾掉不屬于當(dāng)前身份的規(guī)則-----------------------------------
return roleRuleList.stream()
.filter(item -> item.getIdentityId().equals(identityId))
.collect(Collectors.toList());
}
/**
* 構(gòu)建單角色SQL
*/
private static String buildRoleSql(List<RoleComponentRuleDTO> lists) {
StringBuilder roleSql = new StringBuilder();
for (RoleComponentRuleDTO item : lists) {
//如果出現(xiàn)全選 則 代表全部,不需要限定范圍
if ("ALL".equals(item.getRuleValue())) {
continue;
}
//將規(guī)則轉(zhuǎn)換成SQL
String filedSql = convertRuleToSql(item);
roleSql.append(SQL_AND).append(filedSql);
}
return roleSql.toString();
}
/**
* 將單一規(guī)則轉(zhuǎn)化成SQL,默認(rèn)全部使用 In
* ruleCode : area_test
* ruleValue : 區(qū)域1,區(qū)域2,區(qū)域3
* @param rule 規(guī)則值
*/
private static String convertRuleToSql(RoleComponentRuleDTO rule) {
String whereCondition = " in ";
String ruleValueConvert = getInConditionValue(rule.getRuleValue());
return rule.getRuleCode() + whereCondition + ruleValueConvert;
}
/**
* IN字符串轉(zhuǎn)換
* 區(qū)域1, 區(qū)域2, 區(qū)域3 --> ("區(qū)域1","區(qū)域2","區(qū)域3")
* 江西大區(qū) --> ("江西大區(qū)")
*/
private static String getInConditionValue(String ruleValue) {
String[] temp = ruleValue.split(",");
StringBuilder res = new StringBuilder();
for (String string : temp) {
res.append(",'").append(string).append("'");
}
return "(" + res.substring(1) + ")";
}
/**
* 拼接單角色的SQL
* @param sqlBuilder 總的SQL
* @param roleSql 單角色SQL
*/
private static void jointSqlByRoles(StringBuilder sqlBuilder, String roleSql) {
roleSql = roleSql.replaceFirst(SQL_AND, "");
if (StringUtils.isEmpty(sqlBuilder.toString())) {
sqlBuilder.append(String.format(SQL_JOINT, roleSql));
} else {
sqlBuilder.append(SQL_OR).append(String.format(SQL_JOINT, roleSql));
}
}
/**
*
* 1. 對當(dāng)前身份根據(jù)規(guī)則編碼分組-去除不同角色中相同編碼且規(guī)則值為ALL的規(guī)則
* 2. 對角色進(jìn)行分組
* @param conditionList 數(shù)據(jù)規(guī)則
* @return 分組后的規(guī)則list
*/
private static Map<String, List<RoleComponentRuleDTO>> getRuleMapByRoleId(List<RoleComponentRuleDTO> conditionList) {
//--------過濾掉不屬于當(dāng)前身份的規(guī)則,并對條件編碼進(jìn)行分組-----------------------------------
Map<String, List<RoleComponentRuleDTO>> conditionMap = conditionList.stream().collect(Collectors.groupingBy(RoleComponentRuleDTO::getRuleCode));
//--------相同編碼分組中存在ALL的排除掉-----------------------------------------------
List<RoleComponentRuleDTO> newRoleRuleList = new ArrayList<>();
if (MapUtils.isNotEmpty(conditionMap)) {
for (Map.Entry<String, List<RoleComponentRuleDTO>> entry : conditionMap.entrySet()) {
boolean flag = true;
List<RoleComponentRuleDTO> lists = entry.getValue();
for (RoleComponentRuleDTO item : lists) {
if ("ALL".equals(item.getRuleValue())) {
flag = false;
break;
}
}
if (flag) {
newRoleRuleList.addAll(lists);
}
}
}
if (CollectionUtils.isNotEmpty(newRoleRuleList)) {
return newRoleRuleList.stream().collect(Collectors.groupingBy(RoleComponentRuleDTO::getRoleId));
}
return Maps.newHashMap();
}
}核心類,用于生成數(shù)據(jù)權(quán)限查詢的SQL腳本。
Dao層實現(xiàn)
<select id="getLeaderOverviewSve" resultType="com.ifly.center.entity.SalesProjOverviewSve">
SELECT <include refid="column_list"/> FROM U_STD_ADS.LTC_SALES_PROJ_OVERVIEW_SVE
<where>
<if test="permissionSql != null and permissionSql != ''">
${permissionSql}
</if>
</where>
</select>Dao層接受service層傳入已經(jīng)生成好的sql語句,作為查詢條件直接拼接在業(yè)務(wù)語句之后。
小結(jié)
以上,就是數(shù)據(jù)權(quán)限的實現(xiàn)過程,其實代碼實現(xiàn)并不復(fù)雜,主要還是得理解其中的實現(xiàn)原理。如果你也有數(shù)據(jù)權(quán)限的需求,不妨參考一下。
到此這篇關(guān)于MySQL數(shù)據(jù)權(quán)限的實現(xiàn)詳情的文章就介紹到這了,更多相關(guān)SQL數(shù)據(jù)權(quán)限內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Linux下二進(jìn)制方式安裝mysql5.7版本和系統(tǒng)優(yōu)化的步驟
這篇文章主要介紹了Linux下二進(jìn)制方式安裝mysql5.7版本和系統(tǒng)優(yōu)化的步驟,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-01
MySQL中獲取最大值MAX()函數(shù)和ORDER BY … LIMIT 1比較
mysql取最大值的的是max 和order by兩種方式,同時也大多數(shù)人人為max的效率更高,在本文中,我們將介紹MySQL中MAX()和ORDER BY … LIMIT 1兩種獲取最大值的方法以及它們性能上的差異,同時我們將探討這種性能差異的原因,并提供一些優(yōu)化建議2024-03-03
mysql 5.7.20常用下載、安裝和配置方法及簡單操作技巧(解壓版免安裝)
這篇文章主要介紹了mysql 5.7.20常用下載、安裝和配置方法及簡單操作技巧(解壓版免安裝)的相關(guān)資料,需要的朋友可以參考下2017-11-11

