PHP 并发扣款,保证数据一致性(悲观锁和乐观锁)

PHP 并发扣款,保证数据一致性(悲观锁和乐观锁)

业务场景分析

用户购买商品的逻辑中,需要对用户钱包的余额进行查询和扣款

异常:如果同一用户并发执行多个业务进行” 查询 + 扣款” 的业务中有一定概率出现数据不一致

Tips:如果没有做限制单一接口请求频率,用户使用并发请求的手段也有概率出现数据不一致

扣款场景

Step1: 从数据库查询用户钱包余额

SELECT balance FROM user_wallet WHERE uid = $uid;+---------+| balance |+---------+| 100     |+---------+1 row in set (0.02 sec)

Step2: 业务逻辑

Tips: 文章分享处理同一用户并发扣款一致性,检查库存啥的逻辑略过

1. 查询商品价格,比如 70 元
2. 商品价格对比余额是否足够,足够时进行扣款提交订单逻辑

if(goodsPrice <= userBalance) {    $newUserBalance = userBalance - goodsPrice;  }else {    throw new UserWalletException(['msg' => '用户余额不足']);}

Step3: 将数据库的余额进行修改

UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid

在没有并发的情况下,这个流程没有任何问题,原有余额 100,购买 70 元的商品,剩余 30 元

异常场景

Step1: 用户并发购买业务 A 和业务 B(不同实例 / 服务),一定概率并行查询余额是 100

step1

Step2: 业务 A 和业务 B 分别扣款逻辑处理,业务 A 商品 70 结果余额 30,业务 B 商品 80 结果余额 20

step1

Step3:

1 业务 A 先进行修改,修改余额为 30

step1

2 业务 A 后进行修改,修改余额为 20

step1

此时异常出现了,原余额 100 元,业务 A 和业务 B 的商品价格总和 150 元(70+80)都购买成功且余额还剩 20 元。

异常点:业务 A 和业务 B 并行查询余额为 100

解决方案

:lock:

悲观锁

使用 Redis 悲观锁,例如抢到一个 KEY 才能继续操作,否则禁止操作

封装了一个开箱即用的 RedisLock

<?php use Ar414\RedisLock; $redis = new \Redis();$redis->connect('127.0.0.1','6379'); $lockTimeOut = 5;$redisLock = new RedisLock($redis,$lockTimeOut); $lockKey    = 'lock:user:wallet:uid:1001';$lockExpire = $redisLock->getLock($lockKey); if($lockExpire) {    try {        //select user wallet balance for uid        $userBalance = 100;        //select goods price for goods_id        $goodsPrice = 80;         if($userBalance >= $goodsPrice) {            $newUserBalance = $userBalance - $goodsPrice;            //TODO set user balance in db        }else {            throw new Exception('user balance insufficient');        }        $redisLock->releaseLock($lockKey,$lockExpire);    } catch (\Throwable $throwable) {        $redisLock->releaseLock($lockKey,$lockExpire);        throw new Exception('Busy network');    }}

乐观锁

使用 CAS(Compare And Set)

在 set 写回的时候,加上初始状态的条件 compare, 只有初始状态不变的时候才允许 set 写回成功,保证数据一致性的方法

将:

UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid

改为:

UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid AND balance = $oldUserBalance

这样的话并发操作时只有一个是执行成功的,根据 affect rows 是否为 1 判断是否成功

结语

  • 解决方案有很多,这只是其中一种解决方案
  • 使用 Redis 悲观锁的方案会降低吞吐量

php 高并发

https://blog.csdn.net/wx_it/article/details/105827491

php 事务处理transaction

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

  • 在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务
  • 事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行
  • 事务用来管理insert,update,delete语句

一般来说,事务是必须满足4个条件(ACID): Atomicity(原子性)、Consistency(稳定性)、Isolation(隔离性)、Durability(持久性)

  • 1、事务的原子性:一组事务,要么成功;要么撤回。
  • 2、稳定性 : 有非法数据(外键约束之类),事务撤回。
  • 3、隔离性:事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%隔离,需要牺牲速度。
  • 4、持久性:一个事务一旦被提交,它对数据库中数据的改变就是永久的,接下来即使数据库发生故障也不应该对其有任何影响。

执行下面的程序

复制代码
$mysqli = new mysqli('localhost','root','mayi1991','mysqldemo');
if($mysqli->connect_error){
    die('数据库连接错误'.$mysqli->connect_error);
}

$sql1 = "update account set balance = balance - 2 where id = 1";
//这里故意写错指令中的balance1属性
$sql2 = "update account set balance1 = balance + 2 where id = 2";        
$result1 = $mysqli->query($sql1);
$result2 = $mysqli->query($sql2);

if(!$result1 || !$result2){
    die('操作错误'.$mysqli->error);
}else{
    die('操作成功');
}
$mysqli->close();
复制代码

虽然上面的代码有报错,但是在数据库中,id=1的balance已经改变;这样就会出现问题;

我们要的同时改变,如果有一个出错,就不改变;这个时候,我们就需要“事务控制”来保证“一致性”;

我们需要用到的方法autocommit()  commit();看下面的代码

复制代码
$mysqli = new mysqli('localhost','root','mayi1991','mysqldemo');
if($mysqli->connect_error){
    die('数据库连接错误'.$mysqli->connect_error);
}

//关闭数据库自动提交
$mysqli->autocommit(false);

$sql1 = "update account set balance = balance - 2 where id = 1";
//这里故意写错属性balance1
$sql2 = "update account set balance1 = balance + 2 where id = 2";
$result1 = $mysqli->query($sql1);
$result2 = $mysqli->query($sql2);

if(!$result1 || !$result2){
    die('操作错误'.$mysqli->error);
    $mysqli->rollback();    //事务回退
}else{
    //操作全部正确后再提交
    $mysqli->commit();
}
$mysqli->close();
复制代码

首先利用autocommit(false)方法,关闭数据库自动提交,然后当操作语句全部成功后,commit()提交到数据库;

如果操作失败,我们用rollback()方法回退。

PHP进程及进程间通信

一、引言

进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。换句话说就是,在系统调度多个cpu的时候,一个程序的基本单元。进程对于大多数的语言都不是一个陌生的概念,作为”世界上最好的语言PHP”当然也例外。

二、环境

php中的进程是以扩展的形式来完成。通过这些扩展,我们能够很轻松的完成进程的一系列动作。

  • pcntl扩展:主要的进程扩展,完成进程创建于等待操作。
  • posix扩展:完成posix兼容机通用api,如获取进程id,杀死进程等。
  • sysvmsg扩展:实现system v方式的进程间通信之消息队列。
  • sysvsem扩展:实现system v方式的信号量。
  • sysvshm扩展:实现system v方式的共享内存。
  • sockets扩展:实现socket通信。

这些扩展只能在linux/mac中使用,window下是不支持。最后建议php版本为5.5+。

相关代码:进程相关代码

三、简单的例子

一个简单的PHP多进程例子,该例子中,一个子进程,一个父进程。子进程输出5次,退出程序。

$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";
$childList = array();
$pid = pcntl_fork();
if ( $pid == -1) {
    // 创建失败
    exit("fork progress error!\n");
} else if ($pid == 0) {
    // 子进程执行程序
    $pid = posix_getpid();
    $repeatNum = 5;
    for ( $i = 1; $i <= $repeatNum; $i++) {
        echo "({$pid})child progress is running! {$i} \n";
        $rand = rand(1,3);
        sleep($rand);
    }
    exit("({$pid})child progress end!\n");
} else {
    // 父进程执行程序
    $childList[$pid] = 1;
}
// 等待子进程结束
pcntl_wait($status);
echo "({$parentPid})main progress end!";

完美,终于创建了一个子进程,一个父进程。完了么?没有,各个进程之间相互独立的,没有任何交集,使用范围严重受到现在。怎么办,哪就进程间通信(interprogress communication)呗。

四、进程间通信(IPC)

通常linux中的进程通信方式有:消息队列、信号量、共享内存、信号、管道、socket。

1.消息队列

消息队列是存放在内存中的一个队列。如下代码将创建3个生产者子进程,2个消费者子进程。这5个进程将通过消息队列通信。

$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";$childList = array();
// 创建消息队列,以及定义消息类型(类似于数据库中的库)
$id = ftok(__FILE__,'m');
$msgQueue = msg_get_queue($id);
const MSG_TYPE = 1;
// 生产者
function producer(){
    global $msgQueue;
    $pid = posix_getpid();
    $repeatNum = 5;
    for ( $i = 1; $i <= $repeatNum; $i++) {
        $str = "({$pid})progress create! {$i}";
        msg_send($msgQueue,MSG_TYPE,$str);
        $rand = rand(1,3);
        sleep($rand);
    }
}
// 消费者
function consumer(){
    global $msgQueue;
    $pid = posix_getpid();
    $repeatNum = 6;
    for ( $i = 1; $i <= $repeatNum; $i++) {
        $rel = msg_receive($msgQueue,MSG_TYPE,$msgType,1024,$message);
        echo "{$message} | consumer({$pid}) destroy \n";
        $rand = rand(1,3);
        sleep($rand);
    }
}
function createProgress($callback){
    $pid = pcntl_fork();
    if ( $pid == -1) {
        // 创建失败
        exit("fork progress error!\n");
    } else if ($pid == 0) {
        // 子进程执行程序
        $pid = posix_getpid();
        $callback();
        exit("({$pid})child progress end!\n");
    }else{
        // 父进程执行程序
        return $pid;
    }
}
// 3个写进程
for ($i = 0; $i < 3; $i ++ ) {
    $pid = createProgress('producer');
    $childList[$pid] = 1;
    echo "create producer child progress: {$pid} \n";
}
// 2个写进程
for ($i = 0; $i < 2; $i ++ ) {
    $pid = createProgress('consumer');
    $childList[$pid] = 1;
    echo "create consumer child progress: {$pid} \n";
}
// 等待所有子进程结束
while(!empty($childList)){
    $childPid = pcntl_wait($status);
    if ($childPid > 0){
        unset($childList[$childPid]);
    }
}
echo "({$parentPid})main progress end!\n";

由于消息队列去数据是,只有一个进程能去到,所以不需要额外的锁或信号量。

2. 信号量与共享内存

信号量:是系统提供的一种原子操作,一个信号量,同时只有你个进程能操作。一个进程获得了某个信号量,就必须被该进程释放掉。

共享内存:是系统在内存中开辟的一块公共的内存区域,任何一个进程都可以访问,在同一时刻,可以有多个进程访问该区域,为了保证数据的一致性,需要对该内存区域加锁或信号量。

以下,创建多个进程修改内存中的同一个值。

$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";
$childList = array();

// 创建共享内存,创建信号量,定义共享key
$shm_id = ftok(__FILE__,'m');
$sem_id = ftok(__FILE__,'s');
$shareMemory = shm_attach($shm_id);
$signal = sem_get($sem_id);
const SHARE_KEY = 1;
// 生产者
function producer(){
    global $shareMemory;
    global $signal;
    $pid = posix_getpid();
    $repeatNum = 5;
    for ( $i = 1; $i <= $repeatNum; $i++) {
        // 获得信号量
        sem_acquire($signal);
        
        if (shm_has_var($shareMemory,SHARE_KEY)){
            // 有值,加一
            $count = shm_get_var($shareMemory,SHARE_KEY);
            $count ++;
            shm_put_var($shareMemory,SHARE_KEY,$count);
            echo "({$pid}) count: {$count}\n";
        }else{
            // 无值,初始化
            shm_put_var($shareMemory,SHARE_KEY,0);
            echo "({$pid}) count: 0\n";
        }
        // 用完释放
        sem_release($signal);
        
        $rand = rand(1,3);
        sleep($rand);
    }
}
function createProgress($callback){
    $pid = pcntl_fork();
    if ( $pid == -1) {
        // 创建失败
        exit("fork progress error!\n");
    } else if ($pid == 0) {
        // 子进程执行程序
        $pid = posix_getpid();
        $callback();
        exit("({$pid})child progress end!\n");
    }else{
        // 父进程执行程序
        return $pid;
    }
}
// 3个写进程
for ($i = 0; $i < 3; $i ++ ) {
    $pid = createProgress('producer');
    $childList[$pid] = 1;
    echo "create producer child progress: {$pid} \n";
}
// 等待所有子进程结束
while(!empty($childList)){
    $childPid = pcntl_wait($status);
    if ($childPid > 0){
        unset($childList[$childPid]);
    }
}
// 释放共享内存与信号量
shm_remove($shareMemory);
sem_remove($signal);
echo "({$parentPid})main progress end!\n";

3.信号

信号是一种系统调用。通常我们用的kill命令就是发送某个信号给某个进程的。具体有哪些信号可以在liunx/mac中运行kill -l查看。下面这个例子中,父进程等待5秒钟,向子进程发送sigint信号。子进程捕获信号,掉信号处理函数处理。


$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";

// 定义一个信号处理函数
function sighandler($signo) {
    $pid = posix_getpid();
    echo "{$pid} progress,oh no ,I'm killed!\n";
    exit(1);
}

$pid = pcntl_fork();
if ( $pid == -1) {
    // 创建失败
    exit("fork progress error!\n");
} else if ($pid == 0) {
    // 子进程执行程序
    // 注册信号处理函数
    declare(ticks=10);
    pcntl_signal(SIGINT, "sighandler");
    $pid = posix_getpid();
    while(true){
        echo "{$pid} child progress is running!\n";
        sleep(1);
    }
    exit("({$pid})child progress end!\n");
}else{
    // 父进程执行程序
    $childList[$pid] = 1;
    // 5秒后,父进程向子进程发送sigint信号.
    sleep(5);
    posix_kill($pid,SIGINT);
    sleep(5);
}
echo "({$parentPid})main progress end!\n";

4.管道(有名管道)

管道是比较常用的多进程通信手段,管道分为无名管道与有名管道,无名管道只能用于具有亲缘关系的进程间通信,而有名管道可以用于同一主机上任意进程。这里只介绍有名管道。下面的例子,子进程写入数据,父进程读取数据。

// 定义管道路径,与创建管道
$pipe_path = '/data/test.pipe';
if(!file_exists($pipe_path)){
    if(!posix_mkfifo($pipe_path,0664)){
        exit("create pipe error!");
    }
}
$pid = pcntl_fork();
if($pid == 0){
    // 子进程,向管道写数据
    $file = fopen($pipe_path,'w');
    while (true){
        fwrite($file,'hello world');
        $rand = rand(1,3);
        sleep($rand);
    }
    exit('child end!');
}else{
    // 父进程,从管道读数据
    $file = fopen($pipe_path,'r');
    while (true){
        $rel = fread($file,20);
        echo "{$rel}\n";
        $rand = rand(1,2);
        sleep($rand);
    }
}

5.socket

socket即我们常说的套接字编程。这个待补充。原文地址:https://www.jianshu.com/p/08bcf724196b

PHP基于swoole多进程操作示例

https://www.jb51.net/article/167448.htm

这篇文章主要介绍了PHP基于swoole多进程操作,结合实例形式分析了php使用swoole多进程实现多个任务同时执行以及大任务划分成多个小任务相关操作技巧,需要的朋友可以参考下

java

本文实例讲述了PHP基于swoole多进程操作。分享给大家供大家参考,具体如下:

多个任务同时执行

将顺序执行的任务,转化为并行执行(任务在逻辑上可以并行执行)
比如,我们要对已知的用户数据进行判断,是否需要发送邮件和短信,如果需要发送则发送。

不使用多进程时,我们首先判断是否发送邮件,如果需要则发送;然后再判断是否需要发送短信,如果需要则发送。如果发送邮件耗时2s,发送短信耗时2s,那么我们完成任务大概需要4s左右的时间。

如果我们使用多线程的话,可以开两个线程,一个用于处理邮件,一个用于处理短信,则耗时一共需要2s左右,处理时间缩短了一半。

123456789101112131415161718192021222324252627282930313233343536373839<?php/*** Created by PhpStorm.* User: zhezhao* Date: 2016/10/20* Time: 10:37*/$info = array("sendmail"=>1,"mailto"=>"12345@qq.com","sendsms"=>1,"smsto"=>"123456");echo "start:".date("Y-m-d H:i:s").PHP_EOL;$mail_process = new swoole_process('sendMail',true);$mail_process->start();$sms_process = new swoole_process('sendSMS',true);$sms_process->start();//主进程输出子进程范围内容echo $mail_process->read();echo PHP_EOL;echo $sms_process->read();echo PHP_EOL;echo "end:".date("Y-m-d H:i:s").PHP_EOL;//并行函数function sendMail(swoole_process $worker){global $info;if($info['sendmail']==1){sleep(2);$worker->write("send mail to ".$info['mailto']);}}function sendSMS(swoole_process $worker){global $info;if($info['sendmail']==1){sleep(2);$worker->write("send sms to ".$info['smsto']);}}
这里写图片描述

大任务划分成多个小任务

将循环执行的任务,划分为多个进程执行,提高工作效率

假设我们现在有一个通过curl抓取网页内容的需求,需要抓取10个网页,url地址通过数组读取,每个curl耗时2s。如果我们通过for循环来抓取这10个网页,需要耗时20s,使用多进程我们可以将任务划分成5份,分别由5个进程执行,每个进程抓取2个url,并发执行,共耗时4s,效率提高5倍。

123456789101112131415161718192021222324252627282930313233343536<?php/*** Created by PhpStorm.* User: zhezhao* Date: 2016/10/20* Time: 10:51*/$url_arr = array();for ($i=0;$i<10;$i++){$url_arr[] = "www.baidu.com?wd=".$i;}echo "start:".date("Y-m-d H:i:s").PHP_EOL;$workers = array();for ($i=0;$i<5;$i++){$process = new swoole_process('getContents',true);$process->start();$process->write($i);$workers[] = $process;}//主进程数据结果foreach ($workers as $process){echo $process->read();echo PHP_EOL;}echo "end:".date("Y-m-d H:i:s").PHP_EOL;function getContents(swoole_process $worker){$i = $worker->read();global $url_arr;$res1 = execCurl($url_arr[($i*2)]);$res2 = execCurl($url_arr[($i*2+1)]);echo $res1.PHP_EOL.$res2;}function execCurl($url){sleep(2);return "handle ".$url." finished";}
这里写图片描述

总结

以上两种情况,本质上都是将逻辑上没有先后关系的任务,用多个进程程并发执行,提高效率。

php机制本身不提供多线程的操作,ptcl扩展提供了php操作linux多进程的接口。

个人感觉swoole的多进程process方法更加方便一些。

关于两者的比较:http://wiki.swoole.com/wiki/page/214.html

参考文章:
https://segmentfault.com/a/1190000002946586

更多关于PHP相关内容感兴趣的读者可查看本站专题:《PHP进程与线程操作技巧总结》、《PHP网络编程技巧总结》、《PHP基本语法入门教程》、《PHP数组(Array)操作技巧大全》、《php字符串(string)用法总结》、《php+mysql数据库操作入门教程》及《php常见数据库操作技巧汇总

Linux启动/停止重启Mysql数据库的方法+连接MySQL

一、Linux启动/停止/重启Mysql数据库的方法

1、查看mysql版本
方法一:status;
方法二:select version();

2、Mysql启动、停止、重启常用命令
a、启动方式
1、使用 service 启动:

[root@localhost /]

# service mysqld start (5.0版本是mysqld)

[root@szxdb etc]

# service mysql start (5.5.7版本是mysql)

2、使用 mysqld 脚本启动:
/etc/inint.d/mysqld start

3、使用 safe_mysqld 启动:
safe_mysqld&

b、停止
1、使用 service 启动:
service mysqld stop

2、使用 mysqld 脚本启动:
/etc/inint.d/mysqld stop

3、mysqladmin shutdown

c、重启
1、使用 service 启动:
service mysqld restart 
service mysql restart (5.5.7版本命令)

2、使用 mysqld 脚本启动:
/etc/init.d/mysqld restart

二、连接MySQL:

格式: mysql -h主机地址 -u用户名 -p用户密码

1、例1:连接到本机上的MYSQL

找到mysql的安装目录,一般可以直接键入命令mysql -uroot -p,回车后提示你输密码,如果刚安装好MYSQL,超级用户root是没有密码的,故直接回车即可进入到MYSQL中了,MYSQL的提示符是:mysql>

2、连接到远程主机上的MySQL

假设远程主机的IP为:10.0.0.1,用户名为root,密码为123。则键入以下命令:

mysql -h10.0.0.1 -uroot -p123

(注:u与root可以不用加空格,其它也一样)

3、退出MySQL命令

exit (回车)

Cocos Creator存储和读取用户数据–官方文档

存储数据

cc.sys.localStorage.setItem(key, value)

上面的方法需要两个参数,用来索引的字符串键值 key,和要保存的字符串数据 value

假如我们要保存玩家最高分,假设键值为 score:cc.sys.localStorage.setItem('score', 0);

复杂的对象数据,我们可以通过将对象序列化为 JSON 后保存:

Data = {    name: 'Tracer',    level: 1,    gold: 100}; cc.sys.localStorage.setItem('Data', JSON.stringify(Data));

读取数据

cc.sys.localStorage.getItem(key)

和 setItem 相对应,getItem 方法只要一个键值参数就可以取出我们之前保存的值了。对于上文中储存的用户数据:

var userData = JSON.parse(cc.sys.localStorage.getItem('userData'));

移除键值对

移除:cc.sys.localStorage.removeItem(key)

数据加密

对于单机游戏来说,对玩家存档进行加密可以延缓游戏被破解的时间。要加密存储数据,只要在将数据通过JSON.stringify 转化为字符串后调用你选中的加密算法进行处理,再将加密结果传入 setItem 接口即可。

您可以搜索并选择一个适用的加密算法和第三方库,比如 encryptjs,将下载好的库文件放入你的项目,存储时:

var encrypt=require('encryptjs');
var secretkey= 'open_sesame'; // 加密密钥
 
var dataString = JSON.stringify(userData);
var encrypted = encrypt.encrypt(dataString,secretkey,256);
 
cc.sys.localStorage.setItem('userData', encrypted);

读取:

var cipherText = cc.sys.localStorage.getItem('userData');
var userData=JSON.parse(encrypt.decrypt(cipherText,secretkey,256));

注意 数据加密不能保证对用户档案的完全掌控,如果您需要确保游戏存档不被破解,请使用服务器进行数据存取。

转载于:https://www.cnblogs.com/allyh/p/9839762.html

mysql之外键约束

1.什么是外键

  A表的主键,在B表中字段出现,就是外键。

2.什么是约束:约束是一种限制,它通过对表的行货列的数据做出限制,来确保表的数据的完整性、唯一性。比如人员表中有一列是部门id,当新增一个人员的时候,我们不需要手动的在部门id字段给这个人员设置一个部门,而是新增则个新人员记录的时候默认就会有一个部门id给了这个人员,这就是约束。

3.以上1和2结合一起就是外键约束。即:foreign key

4.具体操作

4.1创建表时,同时创建外键约束

4.2已创建表后,追加外键约束

(1)添加外键方法

-- CONSTRAINT 就是创建外键约束 fk_id是外键约束的名字
-- foreign key (dept_id) references dept(did)意思是设置person表中的dept_id字段和dept表中的did字段关联,dept表中的did字段就是person表中的dept_id的外键约束,这个外键约束的名字叫做fk_id,一般潜规则外键约束的名字开头是fk_
alter table person add CONSTRAINT fk_id foreign key (dept_id) REFERENCES dept(did);

此时可以发现在person表中的,点击外键按钮,可以看到创建出来的外键

栏位表示的是person表中的dept_id字段,参考栏位的did就是person表中的dept_id字段的约束,dept_id字段的值被约束为dept字段did字段的值

  主表就是外键约束有约束值的那个表

从表就是被约束的那个表

(2)外键约束的4种类型,RESTRICT、NO ACTION、CASCADE、SET NULL主要针对于外键里的删除时和更新时

RESTRICT(约束):如果出现在删除时,意思是约束外键主键did记录(主表中的记录)不能直接删除,必须先删除被约束的表(从表)字段中dept_id所有这个外键主键值对应的记录,才能删除外键约束(主表中的记录)

NO ACTION:

CASCADE:删除选择这个时,删除主表中的记录时,主表中的这个主键id关联的从表的这个id值所在的记录也会被删除。建议不选。

SET NULL :删除选择这个时,如果从表(被约束的字段所在的表中)被约束的字段的值设置为可以为空时,那么当删除主表的记录时,主表中被删除的这个记录对应的主键值(约束从表字段的那个值)在从表中对应的字段中出现的那个记录的被约束字段的值就会变为NULL。

最常用的是选择RESTRICT不让删的这个约束、或者选择SET NULL删除后值表为空。

目前公司都不太喜欢使用这种真实的外键约束,而是使用虚拟的外键约束。虚拟外键约束:就是人员表中的部门id字段中的id值是部门表中的主键id的值,这就是虚拟外键约束,也是目前来说比较流行使用的。

(3)删除外键

-- 删除外键约束
alter table person DROP foreign key fk_id;

5.其他约束类型

-- 向t5表中插入两条记录,第二值用的都是默认值,如果是默认值则可以不填或填default
insert into t5 values(3, DEFAULT), (4, DEFAULT);

如何使用myisamchk和mysqlcheck工具快速修复损坏的MySQL数据库文件

有时候数据库突然就坏了很郁闷,用mysqlcheck.exe可以修复受损数据库

java

由于服务器的数据库硬盘空间满了,由于大量写入数据失败导致了出现“Duplicate entry ” for key ‘username’”的错误。

如果,出现这样的mysql数据库错误很可能是mysql数据库索引出了问题。那么,什么是mysql数据库索引?

分析:索引如果是primary unique这两两种,那么数据表的数据对应的这个字段就必须保证其每条记录的唯一性。否则就会产生这个错误。

一般发生在对数据库写操作的时候,例如Discuz!4.1论坛程序要求所有会员的用户名username必须唯一,即username 的索引是unique,这时如果强行往cdb_members表里插入一个已有的username的记录就会发上这个错误,或者将一条记录的username更新为已有的一个username。

比如某网友的dedecms网站出问题了,访问一看,果然全屏报错,检查mysql日志,错误信息为:

Table .dedecmsv4dede_archives is marked as crashed and should be repaired

提示说cms的文章表dede_archives被标记有问题,需要修复。

于是赶快恢复历史数据,上网查找原因。最终将问题解决。

解决方法如下:

找到mysql的安装目录的bin/myisamchk工具,在命令行中输入:

myisamchk -c -r ../data/dedecmsv4/dede_archives.MYI

然后myisamchk 工具会帮助你恢复数据表的索引。重新启动mysql,问题解决。

那么,修复mysql数据库一般可以myisamchk工具或者mysqlcheck工具用这二种方法:

1、myisamchk工具

使用 myisamchk 必须暂时停止 MySQL 服务器。例如,我们要检修 discuz 数据库。执行以下操作:

# service mysql stop (停止 MySQL );

# myisamchk -r /数据库文件的绝对路径/*MYI

# service mysql start

myisamchk 会自动检查并修复数据表中的索引错误。

2、mysqlcheck工具

使用 mysqlcheck 无需停止 MySQL ,可以进行热修复。操作步骤如下:

# mysqlcheck -r discuz.*

# service mysql stop (停止 MySQL );

# myisamchk -r /数据库文件的绝对路径/*MYI

# service mysql start

myisamchk 会自动检查并修复数据表中的索引错误。

注意:无论是 myisamchk 还是 mysqlcheck ,一般情况下不要使用 -f 强制修复,-f 参数会在遇到一般修复无法成功的时候删除部分出错数据以尝试修复。所以,不到万不得已不要使用 -f。

下面是其它网友的补充

检查修复所有数据库:

# mysqlcheck -A -o -r -p 
Enter password: 
db1  OK 
db2  OK  
db3  OK 
db4  OK 
…… 
…… 
…… 

修复指定的数据库用 

# mysqlcheck  -o -r Database_NAME -p 

即可

命令详解:

mysqlcheck客户端可以检查和修复MyISAM表。它还可以优化和分析表。
mysqlcheck的功能类似myisamchk,但其工作不同。主要差别是当mysqld服务器在运行时必须使用mysqlcheck,而myisamchk应用于服务器没有运行时。使用mysqlcheck的好处是不需要停止服务器来检查或修复表。
Mysqlcheck为用户提供了一种方便的使用SQL语句CHECK TABLE、REPAIR TABLE、ANALYZE TABLE和OPTIMIZE TABLE的方式。它确定在要执行的操作中使用使用哪个语句,然后将语句发送到要执行的服务器上。
有3种方式来调用mysqlcheck:

shell> mysqlcheck[options] db_name [tables]
shell> mysqlcheck[options] —database DB1 [DB2 DB3…]
shell> mysqlcheck[options] –all–database

如果没有指定任何表或使用—database或–all–database选项,则检查整个数据库。
同其它客户端比较,mysqlcheck有一个特殊特性。重新命名二进制可以更改检查表的默认行为(–check)。如果你想要一个工具默认可以修复表的工具,只需要将mysqlcheck重新复制为mysqlrepair,或者使用一个符号链接mysqlrepair链接mysqlcheck。如果调用mysqlrepair,可按照命令修复表。

下面的名可用来更改mysqlcheck的默认行为:

mysqlrepair

 默认选项为–repair
 
mysqlanalyze
 默认选项为–analyze
 
mysqloptimize
 默认选项为–optimize
 
mysqlcheck支持下面的选项:
·         —help,-?
显示帮助消息并退出。
·         –all–database,-A
检查所有数据库中的所有表。与使用—database选项相同,在命令行中命名所有数据库。
·         –all-in-1,-1
不是为每个表发出一个语句,而是为命名数据库中待处理的所有表的每个数据库执行一个语句。
·         –analyze,-a
分析表。
·         –auto-repair
如果某个被检查的表破坏了,自动修复它。检查完所有表后自动进行所有需要的修复。
·         –character-sets-dir=path
字符集的安装目录。参见5.10.1节,“数据和排序用字符集”。
·         –check,-c
检查表的错误。
·         –check-only-changed,-C
只检查上次检查以来已经更改的或没有正确关闭的表。
·         –compress
压缩在客户端和服务器之间发送的所有信息(如果二者均支持压缩)。
·         —database,-B
处理数据库中命名的所有表。使用该选项,所有字名参量被看作数据库名,而不是表名。
·         —debug[=debug_options],-# [debug_options]
写调试日志。debug_options字符串通常为’d:t:o,file_name’。
·         –default-character-set=charset
使用charsetas默认字符集。参见5.10.1节,“数据和排序用字符集”。
·         –extended,-e
如果你正使用该选项来检查表,可以确保它们100%地一致,但需要很长的时间。
如果你正使用该选项来修复表,则运行扩展修复,不但执行的时间很长,而且还会产生大量的垃圾行! 
·         –fast,-F
只检查没有正确关闭的表。
·         –force,-f
即使出现SQL错误也继续。
·         –host=host_name,-h host_name
连接给定主机上的MySQL服务器。
·         –medium-check,-m
执行比–extended操作更快的检查。只能发现99.99%的错误,在大多数情况下这已经足够了。
·         –optimize,-o
优化表。
·         –password[=password],-p[password]
当连接服务器时使用的密码。如果使用短选项形式(-p),选项和 密码之间不能有空格。如果在命令行中–password或-p选项后面没有 密码值,则提示输入一个密码。
·         –port=port_num,-P port_num
用于连接的TCP/IP端口号。
·         –protocol={TCP | SOCKET | PIPE | MEMORY} 
使用的连接协议。
·         –quick,-q
如果你正使用该选项在检查表,它防止扫描行以检查错误链接的检查。这是最快的检查方法。
如果你正使用该选项在修复表,它尝试只修复索引树。这是最快的修复方法。
·         –repair,-r
执行可以修复大部分问题的修复,只是唯一值不唯一时不能修复。
·         –silent,-s
沉默模式。只打印错误消息。
·         –socket=path,-S path
用于连接的套接字文件。
·         –tables
覆盖—database或-B选项。选项后面的所有参量被视为表名。
·         –user=user_name,-u user_name
当连接服务器时使用的MySQL用户名。
·         –verbose,-v
冗长模式。打印关于各阶段程序操作的信息。
·         –version,-V
显示版本信息并退出。

修复Mysql表时提示:myisamchk: error: 140 when opening MyISAM-table

这是Mysql5.6以后出现的一个bug,解决方法时修复表时不要带上MYI的后缀。
如原来使用

myisamchk -rf /data/mysql/table/table1.MYI

改为

myisamchk -rf /data/mysql/table/table1

即可解决。

解决:Reading table information for completion of table and column names

mysql -A不预读数据库信息(use dbname 更快)—Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A

这里写图片描述

mysql> use dbname
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
让后就卡在这里。

上面卡住的原因::
是由于数据库太大,即数据库中表非常多,所以如果预读数据库信息,将非常慢,所以就卡住了,如果数据库中表非常少,将不会出现问题。

出现问题的原因是::
我们进入mysql 时,没有使用-A参数;
即我们使用
mysql -hhostname -uusername -ppassword -Pport 的方式进入数据,
而没有使用
mysql -hhostname -uusername -ppassword -Pport -A的方式进入数据库。

       当我们打开数据库,即use   dbname时,要预读数据库信息,当使用-A参数时,就不预读数据库信息。