一、来源

  之所以要写这篇文章是因为想对自己当前的分布式知识做一个归纳。今天就先推出一篇MySQL实现的分布式锁,后续会继续推出其他版本的分布式锁,比如通过Zookeeper、Redis实现等。

二、正题

  要想通过MySQL来实现分布式锁,那么必定是需要一个唯一的特性才可以实现,比如主键、唯一索引这类。因为锁是为了限制资源的同步访问,也就是一个瞬间只能有一个线程去访问该资源。分布式锁就是为了解决这个资源的访问竞争问题。

  那么,主键这个方式是不建议使用的,因为我们的锁有可能是各种字符串,虽然字符串也可以当作主键来使用,但是这样会让插入变得完全随机,进而触发页分裂。所以站在性能角度,MySQL分布式锁这一块不使用主键来实现,而采用唯一索引的方式来实现。

  直接上数据库脚本:

DROP TABLE IF EXISTS `distribute_lock_info`;

CREATE TABLE `distribute_lock_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`lock_key` varchar(100) NOT NULL COMMENT '加锁Key',
`lock_value` varchar(100) NOT NULL COMMENT '加锁Value',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`expire_time` datetime DEFAULT NULL COMMENT '过期时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_lock_key` (`lock_key`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=21884 DEFAULT CHARSET=utf8mb4;

  这里主要的是3个字段:加锁key(唯一索引),加锁value,过期时间。id采用自增,保证顺序性。两个时间主要是作为一个补充信息,非必需字段。

  ok,那么到这里,一张基本的分布式锁的表设计就已经完成了。这里的唯一索引是实现互斥的一个重点。

  接着就开始代码的编写了,先来一份依赖文件。

    <dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>false</optional>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mybatis-generator-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>

  分布式锁有个过期时间,那么就需要定时清除这些过期的锁信息,这也是预防死锁的一个手段。所以,我们可以编写一个清除的定时任务,来帮助我们清除过期的锁信息。代码如下:

package cn.lxw.task;

import cn.lxw.configdb.DistributeLockInfoMapper;
import cn.lxw.entity.DistributeLockInfo;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled; import javax.annotation.Resource;
import java.time.LocalDateTime; @Configuration
@EnableScheduling
public class LockCleanTask { @Resource
private DistributeLockInfoMapper lockInfoMapper; /**
* 功能描述: <br>
* 〈Clean the lock which is expired.〉
* @Param: []
* @Return: {@link Void}
* @Author: luoxw
* @Date: 2021/7/26 20:13
*/
@Scheduled(cron = "0/6 * * * * *")
public void cleanExpireLock() {
int deleteResult = lockInfoMapper.delete(new UpdateWrapper<DistributeLockInfo>() {
{
le("expire_time", LocalDateTime.now());
}
});
System.out.println("[LockCleanTask]The count of expired lock is " + deleteResult + "!");
}
}

  清除任务搞定,那么我们可以开始建立数据库的分布式锁那张表对应的实体类,方便后面操作表数据。

package cn.lxw.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor; import java.io.Serializable;
import java.time.LocalDateTime; /**
* 功能描述: <br>
* 〈The entity of ditribute_lock_info table in database.〉
* @Param:
* @Return: {@link }
* @Author: luoxw
* @Date: 2021/7/26 20:19
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("distribute_lock_info")
public class DistributeLockInfo implements Serializable { @TableId(value = "id", type = IdType.AUTO)
private Long id; @TableField("lock_key")
private String lockKey; @TableField("lock_value")
private String lockValue; @TableField("create_time")
private LocalDateTime createTime; @TableField("update_time")
private LocalDateTime updateTime; @TableField("expire_time")
private LocalDateTime expireTime; @Override
public String toString() {
return "DistributeLockInfo{" +
"id=" + id +
", lockKey='" + lockKey + '\'' +
", lockValue='" + lockValue + '\'' +
", createTime=" + createTime +
", updateTime=" + updateTime +
", expireTime=" + expireTime +
'}';
}
}

  接着,就是编写这张表对应的增删改查操作了。这里我采用的是Mybatis-Plus,这样比较快捷一些。

package cn.lxw.configdb;

import cn.lxw.entity.DistributeLockInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; /**
* 功能描述: <br>
* 〈Judge by whether the record insert success or not to prove that lock-operation is whether success or not.So I need to define a method which can ignore insert when failed.〉
* @Param:
* @Return: {@link }
* @Author: luoxw
* @Date: 2021/7/26 20:19
*/
public interface DistributeLockInfoMapper extends BaseMapper<DistributeLockInfo> { int insertIgnore(DistributeLockInfo entity); }

  这时,应该声明一个分布式锁相关的API操作,比如这些,加锁,解锁,获取锁信息等。

package cn.lxw.service;

import cn.lxw.entity.DistributeLockInfo;

import java.util.concurrent.TimeUnit;

public interface ILockService {

    /**
* 功能描述: <br>
* 〈Lock until success!〉
* @Param: [lockKey, lockValue]
* @Return: {@link Void}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
void lock(String lockKey,String lockValue); /**
* 功能描述: <br>
* 〈Lock method, return the result immediately if failed .〉
* @Param: [lockKey, lockValue]
* @Return: {@link boolean}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
boolean tryLock(String lockKey,String lockValue); /**
* 功能描述: <br>
* 〈Lock with a timeout param, return the result immediately if failed.If lock success and it is expired,will be freed by LockCleanTask {@link cn.lxw.task.LockCleanTask}〉
* @Param: [lockKey, lockValue, expireTime, unit]
* @Return: {@link boolean}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
boolean tryLock(String lockKey,String lockValue,long expireTime, TimeUnit unit); /**
* 功能描述: <br>
* 〈Unlock with lockKey & lockValue.If doesn't matched,will be lock failed.〉
* @Param: [lockKey, lockValue]
* @Return: {@link boolean}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
boolean unlock(String lockKey,String lockValue); /**
* 功能描述: <br>
* 〈Get lock info by lockKey!〉
* @Param: [lockKey]
* @Return: {@link DistributeLockInfo}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
DistributeLockInfo getLock(String lockKey);
}

   接着就是通过数据库的增删改查操作去实现这些API。

package cn.lxw.service.impl;

import cn.lxw.configdb.DistributeLockInfoMapper;
import cn.lxw.entity.DistributeLockInfo;
import cn.lxw.service.ILockService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service; import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit; @Service
@Primary
public class MysqlDistributeLockServiceImpl implements ILockService { @Resource
private DistributeLockInfoMapper lockInfoMapper; /**
* 功能描述: <br>
* 〈Lock until success!〉
* @Param: [lockKey, lockValue]
* @Return: {@link Void}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
@Override
public void lock(String lockKey, String lockValue) {
int insertResult = 0;
// trying until success
while(insertResult < 1) {
insertResult = lockInfoMapper.insertIgnore(new DistributeLockInfo() {
{
setLockKey(lockKey);
setLockValue(lockValue);
}
});
}
} /**
* 功能描述: <br>
* 〈Lock method, return the result immediately if failed .〉
* @Param: [lockKey, lockValue]
* @Return: {@link boolean}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
@Override
public boolean tryLock(String lockKey, String lockValue) {
int insertResult = lockInfoMapper.insertIgnore(new DistributeLockInfo() {
{
setLockKey(lockKey);
setLockValue(lockValue);
}
});
return insertResult == 1;
} /**
* 功能描述: <br>
* 〈Lock with a timeout param, return the result immediately if failed.If lock success and it is expired,will be freed by LockCleanTask {@link cn.lxw.task.LockCleanTask}〉
* @Param: [lockKey, lockValue, expireTime, unit]
* @Return: {@link boolean}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
@Override
public boolean tryLock(String lockKey, String lockValue, long expireTime, TimeUnit unit) {
long expireNanos = unit.toNanos(expireTime);
LocalDateTime expireDateTime = LocalDateTime.now().plusNanos(expireNanos);
int insertResult = lockInfoMapper.insertIgnore(new DistributeLockInfo() {
{
setLockKey(lockKey);
setLockValue(lockValue);
setExpireTime(expireDateTime);
}
});
return insertResult == 1;
} /**
* 功能描述: <br>
* 〈Unlock with lockKey & lockValue.If doesn't matched,will be lock failed.〉
* @Param: [lockKey, lockValue]
* @Return: {@link boolean}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
@Override
public boolean unlock(String lockKey, String lockValue) {
int deleteResult = lockInfoMapper.delete(new UpdateWrapper<DistributeLockInfo>() {
{
eq("lock_key", lockKey);
eq("lock_value", lockValue); }
});
return deleteResult == 1;
} /**
* 功能描述: <br>
* 〈Get lock info by lockKey!〉
* @Param: [lockKey]
* @Return: {@link DistributeLockInfo}
* @Author: luoxw
* @Date: 2021/7/26 20:14
*/
@Override
public DistributeLockInfo getLock(String lockKey) {
return lockInfoMapper.selectOne(new QueryWrapper<DistributeLockInfo>(){
{
eq("lock_key",lockKey);
}
});
}
}

  理解起来没有那么困难,【加锁】实际就是添加一条记录,【解锁】就是删除这条记录,【获取锁信息】就是查询出这条记录,【加过期时间的锁】就是添加记录的时候多设置一个过期时间。

  这样的话,就可以进行测试工作了。测试之前,需要先准备一个锁信息的生成工具类,帮助我们生成统一格式的锁信息。主要的锁信息有:IP+节点ID+线程ID+线程名称+时间戳。这个锁信息一方面是为了解锁的时候是唯一值,不会误解掉别人的锁,还有一方面是可以提供有效的信息帮助你排查问题。

package cn.lxw.util;

/**
* 功能描述: <br>
* 〈A string util of lock.〉
* @Param:
* @Return: {@link }
* @Author: luoxw
* @Date: 2021/7/26 20:09
*/
public class LockInfoUtil { private static final String LOCAL_IP = "192.168.8.8";
private static final String NODE_ID = "node1";
private static final String STR_SPILT = "-";
private static final String STR_LEFT = "[";
private static final String STR_RIGHT = "]"; /**
* 功能描述: <br>
* 〈Return the unique String value of lock info.〉
* "[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627301265325]"
* @Param: []
* @Return: {@link String}
* @Author: luoxw
* @Date: 2021/7/26 20:08
*/
public static String createLockValue(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder
.append(STR_LEFT)
.append(LOCAL_IP)
.append(STR_RIGHT)
.append(STR_SPILT)
.append(STR_LEFT)
.append(NODE_ID)
.append(STR_RIGHT)
.append(STR_SPILT)
.append(STR_LEFT)
.append(Thread.currentThread().getId())
.append(STR_RIGHT)
.append(STR_SPILT)
.append(STR_LEFT)
.append(Thread.currentThread().getName())
.append(STR_RIGHT)
.append(STR_SPILT)
.append(STR_LEFT)
.append(System.currentTimeMillis())
.append(STR_RIGHT);
return stringBuilder.toString();
}
}

  测试开始,我这边直接通过main函数进行测试工作。大家有觉得不妥的,可以写个test类,效果是一样的。

package cn.lxw;

import cn.lxw.entity.DistributeLockInfo;
import cn.lxw.service.ILockService;
import cn.lxw.util.LockInfoUtil;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@SpringBootApplication
@EnableTransactionManagement
@MapperScan("cn.lxw")
@EnableScheduling
public class MainApp {

/**
* 功能描述: <br>
* 〈DistributeLock testing start here!〉
*
* @Param: [args]
* @Return: {@link Void}
* @Author: luoxw
* @Date: 2021/7/26 18:20
*/
public static void main(String[] args) {
// run the springboot app
ConfigurableApplicationContext context = SpringApplication.run(MainApp.class, args);
// define some lock infomation
final String lockKey = "lock_test";
ILockService lockService = context.getBean(ILockService.class);
// create a ThreadPoolExecutor
ThreadPoolExecutor tpe = new ThreadPoolExecutor(5,
5,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10));
// execute the simulator
for (int i = 0; i < 3; i++) {
tpe.execute(() -> {
while (true) {
// get the unique lock value of current thread
String lockValue = LockInfoUtil.createLockValue();
// start lock the lockKey
boolean tryLockResult = lockService.tryLock(lockKey, lockValue, 10L, TimeUnit.SECONDS);
// get the most new lock info with lockKey
DistributeLockInfo currentLockInfo = lockService.getLock(lockKey);
System.out.println("[LockThread]Thread[" + Thread.currentThread().getId() + "] lock result:" + tryLockResult + ",current lock info:" + (currentLockInfo==null?"null":currentLockInfo.toString()));
// here do some business opearation
try {
TimeUnit.SECONDS.sleep((int) (Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
// continue to fight for lock if failed
if(!tryLockResult){
continue;
}
// start unlock the lockKey with lockKey & lockValue
lockService.unlock(lockKey, lockValue);
}
});
}
}
}

  查看日志,是否满足分布式锁的要求:同一个瞬间,必然只有一个线程可以争抢到对应资源的锁。

2021-07-27 20:33:40.764  INFO 14128 --- [pool-1-thread-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-07-27 20:33:40.972 INFO 14128 --- [pool-1-thread-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
[LockThread]Thread[39] lock result:false,current lock info:DistributeLockInfo{id=22354, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[38]-[pool-1-thread-2]-[1627389195666]', createTime=2021-07-27T20:33:15, updateTime=2021-07-27T20:33:15, expireTime=2021-07-27T20:33:26}
[LockThread]Thread[37] lock result:false,current lock info:DistributeLockInfo{id=22354, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[38]-[pool-1-thread-2]-[1627389195666]', createTime=2021-07-27T20:33:15, updateTime=2021-07-27T20:33:15, expireTime=2021-07-27T20:33:26}
[LockThread]Thread[38] lock result:false,current lock info:DistributeLockInfo{id=22354, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[38]-[pool-1-thread-2]-[1627389195666]', createTime=2021-07-27T20:33:15, updateTime=2021-07-27T20:33:15, expireTime=2021-07-27T20:33:26}
[LockThread]Thread[38] lock result:false,current lock info:DistributeLockInfo{id=22354, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[38]-[pool-1-thread-2]-[1627389195666]', createTime=2021-07-27T20:33:15, updateTime=2021-07-27T20:33:15, expireTime=2021-07-27T20:33:26}
[LockCleanTask]The count of expired lock is 1!
[LockThread]Thread[37] lock result:true,current lock info:DistributeLockInfo{id=22362, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389224033]', createTime=2021-07-27T20:33:44, updateTime=2021-07-27T20:33:44, expireTime=2021-07-27T20:33:54}
[LockThread]Thread[38] lock result:false,current lock info:DistributeLockInfo{id=22362, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389224033]', createTime=2021-07-27T20:33:44, updateTime=2021-07-27T20:33:44, expireTime=2021-07-27T20:33:54}
[LockThread]Thread[37] lock result:true,current lock info:DistributeLockInfo{id=22364, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389224067]', createTime=2021-07-27T20:33:44, updateTime=2021-07-27T20:33:44, expireTime=2021-07-27T20:33:54}
[LockThread]Thread[37] lock result:true,current lock info:DistributeLockInfo{id=22365, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389225085]', createTime=2021-07-27T20:33:45, updateTime=2021-07-27T20:33:45, expireTime=2021-07-27T20:33:55}
[LockThread]Thread[39] lock result:false,current lock info:DistributeLockInfo{id=22365, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389225085]', createTime=2021-07-27T20:33:45, updateTime=2021-07-27T20:33:45, expireTime=2021-07-27T20:33:55}
[LockCleanTask]The count of expired lock is 0!
[LockThread]Thread[38] lock result:false,current lock info:DistributeLockInfo{id=22365, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389225085]', createTime=2021-07-27T20:33:45, updateTime=2021-07-27T20:33:45, expireTime=2021-07-27T20:33:55}
[LockThread]Thread[38] lock result:false,current lock info:DistributeLockInfo{id=22365, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389225085]', createTime=2021-07-27T20:33:45, updateTime=2021-07-27T20:33:45, expireTime=2021-07-27T20:33:55}
[LockThread]Thread[39] lock result:false,current lock info:DistributeLockInfo{id=22365, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389225085]', createTime=2021-07-27T20:33:45, updateTime=2021-07-27T20:33:45, expireTime=2021-07-27T20:33:55}
[LockThread]Thread[37] lock result:true,current lock info:DistributeLockInfo{id=22370, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389232106]', createTime=2021-07-27T20:33:52, updateTime=2021-07-27T20:33:52, expireTime=2021-07-27T20:34:02}
[LockCleanTask]The count of expired lock is 0!
[LockThread]Thread[38] lock result:false,current lock info:DistributeLockInfo{id=22370, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389232106]', createTime=2021-07-27T20:33:52, updateTime=2021-07-27T20:33:52, expireTime=2021-07-27T20:34:02}
[LockThread]Thread[38] lock result:false,current lock info:DistributeLockInfo{id=22370, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389232106]', createTime=2021-07-27T20:33:52, updateTime=2021-07-27T20:33:52, expireTime=2021-07-27T20:34:02}
[LockThread]Thread[38] lock result:false,current lock info:DistributeLockInfo{id=22370, lockKey='lock_test', lockValue='[192.168.8.8]-[node1]-[37]-[pool-1-thread-1]-[1627389232106]', createTime=2021-07-27T20:33:52, updateTime=2021-07-27T20:33:52, expireTime=2021-07-27T20:34:02}

  大家可以自己按照这个思路去调整一下代码验证一下。

三、结论

  结论是通过MySQL我们是可以实现分布式锁的,而且十分简单,一张表,一点代码就可以搞定。但是,它的本质是通过数据库的锁来实现的,所以这么做会增加数据库的负担。而且数据库实现的锁性能是有瓶颈的,不能满足性能高的业务场景。所以,性能低的业务下玩玩是可以的。

  

  OK,本篇MySQL实现分布式锁介绍结束,欢迎关注下一篇分布式锁V2的实现。

 

【分布式锁】通过MySQL数据库的表来实现-V1的更多相关文章

  1. Mariadb/MySQL数据库单表查询基本操作及DML语句

    Mariadb/MySQL数据库单表查询基本操作及DML语句 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一数据库及表相关概述 1>.数据库操作 创建数据库: CREATE ...

  2. MySQL数据库分表的3种方法

    原文地址:MySQL数据库分表的3种方法作者:dreamboycx 一,先说一下为什么要分表 当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了.分表的目 ...

  3. mysql数据库单表只有一个主键自增id字段,ibatis实现id自增

    mysql数据库单表只有一个主键自增id字段,ibatis实现id自增 <insert id="autoid">        insert into user_id ...

  4. Vc数据库编程基础MySql数据库的表查询功能

    Vc数据库编程基础MySql数据库的表查询功能 一丶简介 不管是任何数据库.都会有查询功能.而且是很重要的功能.上一讲知识简单的讲解了表的查询所有. 那么这次我们需要掌握的则是. 1.使用select ...

  5. Vc数据库编程基础MySql数据库的表增删改查数据

    Vc数据库编程基础MySql数据库的表增删改查数据 一丶表操作命令 1.查看表中所有数据 select * from 表名 2.为表中所有的字段添加数据 insert into 表名( 字段1,字段2 ...

  6. mysql数据库user表host字段的%问题

    搜索: mysql数据库user表host字段的%问题 连接:http://blog.csdn.net/xiaomengh/article/details/48706149 在mysql数据库中,使用 ...

  7. MySQL数据库以及表的管理

    MySQL数据库以及表的管理 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 今天我们探讨的话题就是如何使用MySQL做开发,我们运维的主要工作不是去开发SQL的,但尽管如此,我们有 ...

  8. 第二百七十七节,MySQL数据库-数据表、以及列的增删改查

    MySQL数据库-数据表.以及列的增删改查 1.创建一个表 CREATE(创建) TABLE(表) ENGINE(引擎) ENGINE=INNODB(引擎)还有很多类引擎,这里只是简单的提一下INNO ...

  9. MySql数据库创建表

    3.3.MySql数据库创建表 创建5个表: UserInfo用户基础表 Role 角色表 MenuInfo 菜单即控制表 Relation_Role_Menu 角色对应菜单关系表 RelaTion_ ...

随机推荐

  1. ng : 无法加载文件 C:\Users\szz\AppData\Roaming\npm\ng.ps1,因为在此系统上禁止运行脚本的解决方案

    当安装好Angular CLI后想要查看该版本时在终端键入: ng version 后出现下图的错误提示 解决方案: 在win10 系统中有一个搜索框 输入 Windos PowerShell (一定 ...

  2. python+selenium基础篇,三种等待方式,显示、隐式、强制等待

    1.显示等待: from selenium import webdriver from time import sleep from selenium.webdriver.support.ui imp ...

  3. 对标 Spring Boot & Cloud ,轻量框架 Solon 1.5.2 重要发布

    Solon 是一个轻量的Java基础开发框架.强调,克制 + 简洁 + 开放的原则:力求,更小.更快.更自由的体验.支持:RPC.REST API.MVC.Job.Micro service.WebS ...

  4. 【工具解析】瑞士军刀bettercap2.X_解析_第二期_内网钓鱼(嗅探)工具编写

    /文章作者:Kali_MG1937 CNBLOG博客:ALDYS4 QQ:3496925334/ 第一期: https://www.cnblogs.com/aldys4/p/14877783.html ...

  5. Django-Auth模块之auth_user表

    一.Auth模块之auth_user表 在创建Django项目之后直接执行数据迁移命令会自动生成许多表. Django在启动之后就可以直接访问admin路由,需要输入用户名和密码,数据参考的就是aut ...

  6. 《手把手教你》系列基础篇之(三)-java+ selenium自动化测试- 启动三大浏览器(上)(详细教程)

    1.简介 前边宏哥已经将环境搭建好了,今天就在Java项目搭建环境中简单地实践一下: 启动三大浏览器.按市场份额来说,全球前三大浏览器是:IE.Firefox.Chrome.因此宏哥这里主要介绍一下如 ...

  7. ES6学习笔记之数组

    1.拓展运算符 含义 拓展运算符是三个点(...),功能是将数组或类数组对象展开成一系列用逗号隔开的值.如下面代码: console.log(...[1, 2, 3]); //1 2 3 consol ...

  8. 阿里云中quick bi用地图分析数据时维度需转换为地理区域类型

    1.到数据集里面点击编辑要做地图分析的数据集 2.找到要分析的地理维度字段,选择转换为对应的类型,这里为市级,所以选择转换为市,其它类似,然后点击右上角保存即可. 3.返回数据集,点击新建仪表板 4. ...

  9. Kubernetes之deployment

    Kubernetes实现了零停机的升级过程.升级操作可以通过使用ReplicationController或者ReplicaSet实现,但是Kubernetes提供了另一种基于ReplicaSet的资 ...

  10. 乘风破浪,Windows11预览版升级和安装,积极准备中的大跃进

    安装Windows11 暂时官方还没出可靠的ISO 升级到Windows11 预览版 关于一些限制 目前DEV预览通道对从老系统升级到Windows11暂时没有什么限制,只是会提示你可能不太好,但是安 ...