区块链交易入门远离区块链骗局,与其亏钱伤心,不如打钱给我, 至少我还会说一声谢谢交易所1. 中心化交易所交易所 a. 币安 (交易人数最多的交易所) b. okx (目前可以直接入金的交易所) c. coinbase (美国人玩的交易所) d. ftx (手续费最低的交易所) 2. 去中心化交易所 a. uniswap(以太链) b. pancakeswap(币安链) Gas1. Gas(汽油费) Gas最初是在以太坊区块链上实现的，用于衡量消息消耗的计算和存储资源。也就是钱包与钱包交互时产生的计算费用 2. 用户可以用更多的Gas来让自己的交易可以更快的被矿工打包 3. 由此可以使用出三明治攻击的玩法, 即在链上的交易对中监控大单以比大单更高的Gas提前完成交易, 之后再卖出来赚取差价 入金入金是将法币在中心化交易所转换为链上资产的行为 入金方法分为线上和线下两种 1. 中心化交易所入金 a. KYC (中国大陆清退政策) b. 提币手续费 (按链划分 以太链最贵) 2. 线下入金 (不安全)目前可以直接入金的交易所只有OKX (支持支付宝, 微信,银行卡 )ERC-201. 支付的手续费种类为ETH 2. ERC-USDT与其他以太坊上的Token一样，存储在以太坊地址上，因此每次转账的时候，需要支付的手续费种类为ETH。地址示例：0xdac17f958d2ee523a2206206994597c13d831ec7 (ERC-20合约) 3. Gas费用高,当消息出现拥堵的时候Gas费用会水涨船高 BSC1.BEP-20是ERC-20的扩展，ERC-20是以太坊区块链上最流行的令牌标准。 同样，BEP-20是Binance平台智能链上的令牌标准。 2. Gas费用低 TRC-201. TRC-20是用于TRON区块链上的智能合约的技术标准，用于使用TRON虚拟机实施代币。 它与 ERC-20完全兼容。 2. 在波场上转账USDT是不收手续费的，但在交易所提现TRC-USDT还是会收提现手续费的(由交易所收取)。地址示例：TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t (TRC-20合约) 如何避免交易所高昂提币手续费方法1:1. 确认FTX交易所是否有你想提现的交易对(没有的话就放弃吧) 2. OKX交易所入金USDT 3. 将USDT提币到 FTX 交易所 4. 在FTX交易所将USDT兑换为你想要的币种 5. 提币到钱包 方法2:1. 交易所入金 2. 提币到FTX交易所 3. 在交易所将USDT兑换为ETH 4. 提币到钱包 5. 在Uniswap上去兑换你想要的币 注意GAS费用
iTerm2+omz+p10k打造炫酷的终端前言代码可以写的烂, 工具必须要好看!!效果图准备工作iTerm2 Mac上最强的终端工具 官网oh my zsh 用于管理Zsh配置的框架 官网powerlevel10k 是Zsh的一款主题 官网开始1.下载iTerm2 前往官网下载2. 安装Oh my zsh 通过官网给 curl 或 wget 下载安装sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"3. 安装powerlevel10k克隆项目到omz的外置主题目录下git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k在 ~/.zshrc 中设置主题ZSH_THEME="powerlevel10k/powerlevel10k" 4. 安装语法高亮插件和自动补全插件语法高亮插件git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting自动补全插件git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions在 ~/.zshrc 中添加插件plugins=( [plugins...] 其他插件 zsh-syntax-highlighting zsh-syntax-highlighting )4.使配置生效 source ~/.zshrc5. 安装字体git clone https://hub.fastgit.org/possible318/p10k-font.git6. 设置iTerm2的字体7. 运行p10k configure 按照提示选择想要的效果enjoy !!!
挖掘以太坊Ethereum(中文名 以太坊, 缩写 ETH)使用 Ethash 算法来挖掘, 这种算法是一种内存密集型算法, 内存吞吐量越高计算越快. 显卡所用的显存比需要通过数据总线来传输数据的主板内存快得多, 因此以太坊主要使用显卡来挖掘, CPU 挖掘的速度低至忽略不计.Ethash 算法会把已有的区块的加密信息放在显存中继而计算下一区块. 由于以太坊支持 uncle block, 因此这些数据甚至不是链而是有向无环图(DAG). 这个 DAG 会以每年大约 520MiB 的速度增大, 在目前(2021 年 3 月)已经达到了 4.2GiB, 如果你的显卡显存没有超过 4.2GiB 将挖不了以太坊.DAG 占用的内存每年逐渐增大, 因此 ASIC 矿机挖掘以太坊的效益并不是那么高. 通常 ASIC 的内存都不是那么快(相比显卡所用的 DDR6), 并且没有那么大, 所以矿老板并不是非常乐意挖以太坊, 这也就意味着业余加密货币爱好者用自己的显卡挖以太坊也可以有相对比较好的收益(相较于比特币).有不少 ETH 挖矿程序并不开源, 无法得知其内部是否有恶意代码窃取用户电脑上的数据, 业余加密货币爱好者一般不会有多台高性能电脑, 挖矿用的电脑就是平时所用的电脑, 因此使用一款安全的挖矿程序格外重要. 甚至有很多闭源程序或补丁会标榜自己能够 "优化" 算法, 使相同的硬件运行得到更高的 HashRate, 实际上 Ethash 核心算法是开源的, 而且早已优化到不能再优化, 那些程序只不过是给使用者的显存超频而已. 显存超频之后会提高硬件出错率, 加速元件老化, 也会导致原本风道设计合理的机箱变得无法及时散热进一步损害其他硬件的健康(矿老板正是因为超频挖矿所以矿卡故障率很高).对于业余加密货币爱好者来说, 为了体验挖矿的乐趣而损害计算机健康得不偿失.挖矿程序挖掘以太坊推荐使用 ethminer 挖矿程序 https://github.com/ethereum-mining/ethminer在 release 页面下载适合自己所用的操作系统的最新版程序, 例如 "ethminer-0.19.0-alpha.0-cuda-9-linux-x86_64.tar.gz" 表示适用于 Linux 操作系统的用 CUDA9 编译的 0.19.0 版本.下载完挖矿程序之后, 还需要安装对应版本的 CUDA. 不过安装显卡驱动时可能已经自带了 CUDA, 使用以下命令来查看显卡和显卡驱动器状态nvidia-smi如果显示已经安装了 CUDA 就不需要另外安装了. 值得一提的是, 软件是面向旧版本 CUDA 编译的, 因为作者称新版本 CUDA 的挖矿效率没有那么高, 但是安装了新版本 CUDA(例如 CUDA11) 也是能运行软件(CUDA 9/10)的, 如果追求最高的性能可以删除安装驱动时自动安装的 CUDA 再手动安装旧版本.Windows 系统也是用这个命令来查看, 但是通过 Windows 更新安装的驱动有可能没有这个命令, 可以先尝试运行一下挖矿程序, 运行报错则再手动安装 CUDA.如果安装驱动时没有自动安装 CUDA, 请到 NVIDIA 官网下载 https://developer.nvidia.com/zh-cn/cuda-downloads以太坊钱包在开始挖掘之前要先拥有一个自己的以太坊钱包.以太坊钱包软件有很多, 与其他加密货币钱包一样, 不开源===送钱给别人. 在这个页面可以找到可靠的钱包软件 https://ethereum.org/en/wallets/find-wallet/ 挑选其中一款你觉得好看的钱包软件就可以开始了. 或者直接使用交易所提供的充值地址(那是交易所为用户自动生成的钱包), 这样就不需要额外使用钱包软件, 挖矿所得也可以直接在交易所卖出, 十分方便. 注册交易所详见另一篇文章 加密货币交易.选择矿池以太坊矿池也有很多, 在一些矿池列表网站上可以找到它们 https://investoon.com/mining_pools/eth目前第一名的矿池(按算力排序)是 SparkPool, 这个矿池的网站很多页面不登录不给看, 对于想要匿名挖矿的用户请直接跳过这一矿池. 第二名的矿池是 Ethermine, 与其他国外矿池一样, 它支持匿名挖矿, 并不需要任何个人资料也不需要任何形式的注册, 这是本文推荐的矿池.如果你已经注册了加密货币交易所, 并且交易所自己开设了矿池, 可以直接用交易所的矿池, 比如币安矿池. 交易所提供的矿池一般没有最小提现额度限制, 挖出多少就可以立即在市场上交易多少.Ethermine打开矿池的网站查看矿池提供的服务器 https://ethermine.org/start然后用以下命令连接适合的服务器开始挖矿Linux./ethminer -P stratum1+ssl://0xd39eecf6fd2d47a2955dd50befb1dbd7e457e9dd.home@asia1.ethermine.org:5555Windowsethminer.exe -P stratum1+ssl://0xd39eecf6fd2d47a2955dd50befb1dbd7e457e9dd.home@asia1.ethermine.org:5555(更多命令行样例详见 https://github.com/ethereum-mining/ethminer/blob/master/docs/POOL_EXAMPLES_ETH.md)"0x" 开头的字符串是以太坊钱包地址, 钱包地址后方的 . 后面的 "home" 表示矿机名, 可以是任何字符串, 仅用于统计算力时区分算力来源, 没有实际作用.钱包地址请换成自己的, 当然我不介意大家帮我挖矿.请不要使用非 SSL 协议和端口连接矿池服务器, 在国内有几率连不上.挖矿开始后, 会花一段时间来生成 DAG, 通常二十秒内可以完成. 当 HashRate 不为 0 时说明开始挖矿了, 例如m 12:02:38 ethminer 12:35 A248:R1 35.12 Mh - cu0 35.12在矿池的网站的首页(https://ethermine.org)输入自己的钱包地址就可以查看自己的挖矿状态. 网站数据更新有延迟, 建议挖了一个小时再去看. 在页面上也可以更改提现阈值(0.1 ETH 到 10 ETH), 旁边有详细的支付说明.币安矿池首先创建自己的挖矿账户, 如果还没有注册过币安请先注册.在币安矿池挖矿不使用以太坊钱包地址, 而使用币安挖矿账户名.币安矿池参数示例ethminer.exe -P stratum+tcp://czp3009.home@ethash.poolbinance.com:443挖矿账户名请换成自己的.币安矿池的收益每天发放一次, 请第二天再看. 矿池收益无论多少都可以转到现货账户.币安现货交易最低金额 10 USDT, 相当于"提现"金额为 10 美元, 这远低于各大矿池的提现额度, 可以很快把挖矿所得变现.FAQethminer 这个软件有一个毛病, 在网络不幸断开时不会自动重连而是结束运行, 如果你的挖矿电脑无人值守, 请在命令行写一个死循环来执行它.总结稍微好一些的显卡一天能挖四五个美元, 收益超过电费, 所以业余数字货币爱好者挖以太坊是合算的. Ethash 是内存密集型算法, 并不需要特别高的核心频率, 在机箱风道合理的情况下显卡温度(以 GTX 1080/1080 TI作为例子)也不会很高(低于 65°), 这甚至远远低于玩大型游戏时的温度, 只要不超频就不会对显卡带来损害, 甚至还可以一边挖矿一边打游戏.祝大家早日发家致富, 然后分我一点.作者：czp链接：https://www.hiczp.com/jia-mi-huo-bi/wa-jue-yi-tai-fang.html来源：博客著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处
加密货币交易致命警告: 加密货币有风险, 与其亏钱不如直接送我很多业余加密货币爱好者接触加密货币并不是出于对区块链技术的兴趣, 而是想通过加密货币交易来赚钱, 俗称炒币.选择交易所加密货币交易除了面对面交易, 主要发生在加密货币交易所(Cryptocurrency Exchange), 这些交易所提供了交易平台, 让用户可以在平台托管自己的数字资产, 并与他人进行交易. 选择一个靠谱的交易所也就成为了交(亏)易(钱)的第一步.直接在 Google 搜索 world's biggest bitcoin exchange 可以找到很多交易所排名网站, 例如 statista 或者 coinmarketcap 在绝大多数榜单上, Binance 都是世界第一交易所, 这也是本文推荐的交易所.很多国内用户会被人推荐火币交易所, 但是本文并不推荐使用火币, 主要原因有几个:首先, 火币是中国人创办的交易所, 创办比特币交易网站在国内是非法的 , 而且未来政策可能会进一步缩紧. 虽然目前火币网站不在国内(大部分交易所都在塞舌尔或者马耳他), 但是火币的老板和大部分员工都在中国, 不排除未来政策改变后因为某些众所周知的原因 无 了.其次, 火币的交易手续费高达 0.2% , 而主流交易所例如币安只有 0.1% (BNB 手续费燃烧, 邀请人返利, 高等级 VIP 还会有进一步交易手续费折扣, 最低可达 0.012%).再次, 火币的数字资产提现手续费非常高昂(提现到自己的钱包或者其他交易所), 以比特币为例, 火币的比特币提现费用高达 0.001 , 而币安仅有 0.0005 . 这意味着想把资产从火币转移出去比其他交易所更困难.获得 USDT开始炒币的第一步是首先获得计价币种, 计价币种是价值长期稳定的加密货币, 最常用的是 USDT(泰达币), 每个 USDT 等于一美元. 交易的关键就是在价值波动大的加密货币例如比特币(BTC)涨到高点时卖出得到 USDT, 通过 USDT 这样的价值稳定的加密货币维持自己的数字资产价值, 继而在比特币跌到低谷时用 USDT 换取更多的 BTC, 循环往复从而赚钱.获得 USDT 主要有两个方式, 一是从其他交易所或者 USDT 钱包转入币安为用户提供的托管账户内. 在交易所的充值页面可以充值各种加密货币, 每一种加密货币的充值页面上给出的钱包地址是交易所为每个用户自动生成的托管式的钱包. 这个钱包的密钥用户无法取得, 把资产转入这一钱包地址就可以在交易所进行交易. 由于托管式钱包的安全性完全依赖于交易所会不会突然 无 了, 因此选择靠谱的交易所尤为重要. 对于其他数字资产也是同样的操作过程, 转到交易所提供的对应种类的加密货币的充值地址就可以开始交易了.二是通过 C2C(customer to customer) 交易获得. C2C 交易就是从别人手里购买数字资产(不仅限于 USDT), 交易所会提供广告位挂出用法币买/卖数字资产的其他用户. 目前各大交易所在进行 C2C 交易之前都要求用户实名认证(人脸, 身份证), 来确保交易双方互相可信. 如果你不想透露个人信息给交易所, 请通过第一种方式来获得可供交易的数字资产.由于 USDT 的价值保持长期稳定, 所以 C2C 交易中的买家和卖家给出的 USDT 价格接近美元真实汇率, 而其他数字资产的 C2C 交易价格都与实际价值偏差较大.现货交易现货交易就是最简单的一手交钱一手交货, 双方在某一转换率交换了两种加密货币(比如用 USDT 买 BTC), 通过高抛低吸操作继而赚钱.交易对是指买卖双方交换的数字资产种类, 例如 BTC/USDT 或者缩写为 BTCUSDT 的意思是买家用 USDT 买入 BTC 而卖家卖出 BTC 获得 USDT. 买入消耗的币种为 USDT 是最简单的交易对, 可以很直观的反映 BTC 的价格波动不需要换算. 其他的交易对例如 BTC/ETH 是用 ETH 买 BTC, 由于 ETH 本身价值也在大幅度波动, 这样的交易对的价格(转换率)十分大脑升级, 不建议新手尝试. 在最简单的情况下, 只交易以 USDT 做计价的交易对.现货交易常用概念在现货交易中会碰到一些基本概念委托委托就是已经挂在市场上的订单. 在深度图中可以看到买卖双方的委托价格和资金容量.限价委托限价委托是以固定价格发出的委托, 这是最普通的委托订单. 这些订单会写明以什么价格买入或卖出货物(加密货币). 如果有买家/卖家挂出更好价格的对应的买/卖委托, 这两份委托就会互相成交.市价现货交易的市价就是最新价格, 最新价格是当前市场上最新一份被成交的委托的价格, 如果有人以比正常价格高的多/低得多的价格买/卖, 市价就会猛烈波动. 如果一直没有任何委托被成交, 价格曲线就不会波动而持续保持在某个值. 冷门加密货币经常出现猛烈波动或持续不动的情况.市价委托市价委托就是以市价买/卖的订单. 交易所的市价委托中所用的市价并不是用当前市价赋值的固定值, 而是一个变量. 两个对应的市价买/卖总是能立即成交. 如果在某一时刻因为买家/卖家订单的资金容量差距过大而使一方的订单无法被全部成交完(单个订单中也可以出现部分成交的情况), 这部分订单在下一时刻会以下一时刻的市价买/卖. 对于热门加密货币而言, 由于交易流水很大, 市价单无法被立即成交的情况极其罕见, 成交速度超过肉眼观察速度. 只有冷门加密货币才会出现市价单很久(超过一秒)无法成交的情况.触发器触发器是交易所服务器上运行的程序, 当达到目标价格时就会执行其中的委托(进行挂单)(限价/市价委托). 举个例子, 当交易者认为一旦突破 50000 USDT 阻力位时, 就有可能涨到 51000 USDT, 那么交易者就可以设定一个 50000 USDT 的触发器, 此触发器触发一个 51000 USDT 的限价委托挂单操作.所有的高级挂单方式都是触发器 + 限价委托/市价委托的排列组合. 当某种挂单方式所需填写的参数中包含"触发价"时就是指触发器.比如 "止盈止损" = 触发器 + 限价委托(买入/卖出); "OCO" = 限价委托(买入) + 触发器 + 限价委托(卖出);杠杆简单地说, 杠杆就是借钱炒股(币). 10 倍杠杆的意思是本金只有 1 块钱, 借了 9 块钱, 总共 10 块钱. 这十块钱投入市场后, 如果市场上涨 10%, 那么加上借来的钱, 资金将变为 11 块. 盈利后归还借来的 9 块钱, 净赚 1 块钱, 利润率 100%.杠杆可以将收益放大, 杠杆倍率就是收益放大倍率, 但与此同时, 亏损也会被放大. 如果市场下跌 10%, 那么本金就会被亏完. 交易所为了不亏掉借给用户的那部分钱, 会通过强制平仓机制在本金亏完时抛出用户的仓, 这种情况称为爆仓. 一旦爆仓发生, 用户的本金分文不剩, 不会有东山再起之日, 杠杆是一种风险很高的金融工具.通过杠杆借来的钱要支付利息, 大部分交易所的杠杆利息按小时计算(比较高), 一旦长时间无法盈利, 即使没有爆仓, 也会因为利息而亏损很大.交易所要借钱给用户, 因此交易所还会有理财产品, 理财用户存入的钱就会变成借给另一些用户的钱. 如果最近行情比较差, 可以选择将闲置的数字资产存入理财产品以赚取稳定的利息.合约交易合约全称期货合约, 是一种金融衍生品. 期货是一种在未来取货(标的物, 例如比特币)的交易方式. 举个例子, 面粉厂每年都需要大量面粉, 所以和小麦农场主签订了合约, 在年初时面粉厂付全款购买了今年的面粉, 年底时面粉厂会来取货. 而根据合约的内容, 无论年底时小麦价格是否浮动, 农场主都必须把约定好数量的货物交给面粉厂. 而面粉厂手里的用于取货的那张纸是可以被交易的, 假如今年遇到大旱, 面粉厂在年中时将这张纸卖给了别人, 面粉厂就赚了钱.传统的期货有交割日, 而且无论是卖家交不出货还是买家不来收货, 都要赔偿违约金. 股票中的合约被进一步发展, 成为一种不交割货物的期货, 买卖双方在交割日直接进行金钱交换, 一方向另一方支付差价. 这种现金交割不需要实物参与, 成为单纯的零和游戏.现金交割期货虽然不需要交割实物, 但依然有比较大的缺点, 当交割日很远时人们对期货的价值期望就越不确定, 越容易出现价格离谱的期货收购/卖出委托. 对于加密货币这样本来就不是"实物"的合约, 更是发展出了没有交割日的合约, 这种合约被称为永续合约.由于没有交割日, 永续合约的价格通过另一个机制来保持合约价格趋向现货价格. 交易所会在每天确定的几个固定时间点, 例如每隔八小时进行一次判定, 如果合约标记价格大于现货价格则看涨用户要补一定资金给看跌用户(从看涨方的合约账户里划转一定量计价币到看跌方的合约账户里), 如果合约标记价格小于现货价格则看跌用户要补一定资金给看涨用户. 这样就可以对偏离现货价格的一方实施惩罚迫使合约价格逼近现货价格.换句话说, 永续合约实际上是在与另一方对赌市场行情走向. 当用户在赌市场涨的时候, 系统就会匹配另一个资金容量一样看跌的用户来配对, 此时两张相对应的看涨/看空合约委托就被成功交易了. 如果赌注金额较大/小, 就会出现一个人与好几个人对赌, 或者一个人跟零点几个人对赌.这种纯粹的赌博游戏自然也可以使用杠杆, 永续合约是双方对赌而没有真实买入标的物, 因此合约的杠杆也没有真实的借钱, 不会有利息. 合约的杠杆其实就是用较少的钱强行与更多的对手对赌. 相应的, 合约的交易手续费费率与现货交易是不一样的(这取决于交易所), 并且计算手续费时也是按照杠杆后的总资金量计算, 高倍率的杠杆将产生非常大的手续费.也正是因为合约的杠杆不需要真实的借钱, 交易所提供的合约杠杆倍率非常之大. 例如 BTCUSDT 永续合约杠杆倍率最高可达 125 倍, 市场波动 0.8% 本金就会亏完(本 金 消 失 术).在那么高的杠杆倍率下, 为了避免合约价格被恶意"插针"而导致大量用户瞬间爆仓, 交易所会使用"标记价格"来作为合约的市价.标记价格是一段时间内成交的订单的平均价格再加上目前的现货价格用一个公式计算得到的值, 而非最新价格(最新被成交的合约的价格). 通常情况下标记价格比最新价格与现货价格更"迟钝", 它会相对平滑的变动避免有人恶意操控合约价格导致其他用户瞬间爆仓. 但是即使如此, 加密货币市场波动本身就很大, 高倍率杠杆依然经常出现瞬间爆仓的情况.合约交易有一对比较特别的概念是 maker(挂单方) 和 taker(吃单方). 如果一个订单在被挂到市场上的那一瞬间无法被成交(例如比目前市场价更差的限价委托), 这张订单将被留在市场上等待被交易, 此时挂单者就会成为 maker. maker 为订单池(盘口)增加了深度, 提高了市场流动性(如果无人挂单市场就会停滞). 而如果一个订单在挂出的瞬间就会被成交, 例如市价委托或者比市价更好的价格的限价委托, 那么这样的订单就会"消耗"一张相反方向的已挂在市场上的订单, 形象的描述为吃掉了订单. 挂出这样的订单的用户就是 taker. 在绝大部分交易所, 为了鼓励用户增加订单池深度, 提高市场流动性, maker 和 taker 的交易手续费是不一样的(通常 maker 只有 taker 的一半), 当交易量比较大时尽可能让自己成为 maker 可以节省不少手续费.但是 maker 并不是不使用市价委托就一定可以当的, 例如买入合约时已有对手在以好得多的价格卖出, 有可能导致刚挂出的单在与市价相差较大时就被成交, 这时身份依然是 taker. 又或者市场上 taker 众多, 导致 maker 供不应求, 此刻未能完成交易的 taker 的订单就会留在市场上等待被对手交易掉, 这样就成为了 maker.冷门币交易所会提供非常多的加密货币, 其中不乏一些闻所未闻的币种. 这些币通常交易容量很小, 二十四小时成交额可能只有几百万美元, 这样一来就变得十分容易操盘, 一旦有人用大量资金买入/卖出这种冷门币便可以瞬间大幅改变市价. 因此散户不推荐交易冷门币, 都是割韭菜. 当然, 也有可能相中一个有很大发展潜力的新币, 在最初价格还不高的时候买入了很多, 日后凭这种币大赚一笔, 这是比 125 倍杠杆更赌博的赌博.风险介于加密货币的特殊性, 加密货币的价格波动非常大. 这种赌博游戏不是你会所嫩模就是他下海干活. 与其亏到裤衩都不剩, 不如直接把钱送给我, 至少我还能对你说一声谢谢.作者：czp链接：https://www.hiczp.com/jia-mi-huo-bi/jia-mi-huo-bi-jiao-yi.html来源：博客著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。
HoshionBot搭建QQ群 机器人搭建项目go-cqhttp基于 Mirai 以及 MiraiGo 的 OneBot Golang 原生实现机器人框架HoshinoBotHoshinoBot: 基于 nonebot 框架，开源、无公害、非转基因的QQ机器人。部署环境Python3Golang部署 go-cqhttp官方文档git clone 项目目录结构 . ├── go-cqhttp 运行脚本 ├── config.yml 配置文件(主要修改的文件) ├── device.json 设备信息 ├── logs 日志 │ └── xx-xx-xx.log └── data 数据 ├── images │ └── xxxx.image └── db修改配置使用的反向代理端口是 universal: 'ws://127.0.0.1:8900/ws/'执行 go build 构建可执行文件执行 ./go-cqhttp 启动出现下面输出标识启动成功[2021-09-26 16:55:44] [INFO]: 当前版本:(devel) [2021-09-26 16:55:44] [INFO]: 用户交流群: 721829413 [2021-09-26 16:55:44] [INFO]: 将使用 device.json 内的设备信息运行Bot. [2021-09-26 16:55:44] [INFO]: Bot将在5秒后登录并开始信息处理, 按 Ctrl+C 取消. [2021-09-26 16:55:49] [INFO]: 开始尝试登录并同步消息... [2021-09-26 16:55:49] [INFO]: 使用协议: iPad [2021-09-26 16:55:49] [INFO]: Protocol -> connect to server: 61.181.203.156:8080 [2021-09-26 16:55:51] [INFO]: 登录成功 欢迎使用: 菜狗2号机 [2021-09-26 16:55:51] [INFO]: 开始加载好友列表... [2021-09-26 16:55:51] [INFO]: 共加载 2 个好友. [2021-09-26 16:55:51] [INFO]: 开始加载群列表... [2021-09-26 16:55:51] [INFO]: 共加载 1 个群. [2021-09-26 16:55:51] [INFO]: 信息数据库初始化完成. [2021-09-26 16:55:51] [INFO]: 资源初始化完成, 开始处理信息. [2021-09-26 16:55:51] [INFO]: CQ WebSocket 服务器已启动: 127.0.0.1:6700 [2021-09-26 16:55:51] [INFO]: アトリは、高性能ですから! [2021-09-26 16:55:51] [INFO]: 开始尝试连接到反向WebSocket Universal服务器: ws://127.0.0.1:8900/ws/ [2021-09-26 16:55:51] [INFO]: 正在检查更新.配置文件go-cqhttp配置文件account: # 账号相关 uin: 123456789 # 账号 password: '123456789' # 密码为空时使用扫码登录 encrypt: false # 是否开启密码加密 status: 0 # 在线状态 请参考 https:#github.com/Mrs4s/go-cqhttp/blob/dev/docs/config.md#在线状态 relogin: # 重连设置 disabled: false delay: 3 # 重连延迟, 单位秒 interval: 0 # 重连间隔 max-times: 0 # 最大重连次数, 0为无限制 use-sso-address: true heartbeat: disabled: false # 是否开启心跳事件上报 interval: 5 message: post-format: string ignore-invalid-cqcode: false force-fragment: false fix-url: false proxy-rewrite: '' report-self-message: false remove-reply-at: false extra-reply-data: false output: log-level: info # 日志等级 trace,debug,info,warn,error debug: false # 开启调试模式 # 默认中间件锚点 default-middlewares: &default access-token: '' filter: '' rate-limit: enabled: false # 是否启用限速 frequency: 1 # 令牌回复频率, 单位秒 bucket: 1 # 令牌桶大小 servers: - ws-reverse: disabled: false universal: 'ws://127.0.0.1:8900/ws/' # 主要修改的地方 reconnect-interval: 3000 middlewares: <<: *default # 引用默认中间件 database: # 数据库相关设置 leveldb: enable: true部署 HoshionBot克隆本仓库并安装依赖包git clone https://github.com/Ice-Cirno/HoshinoBot.git 目录结构 . ├── hoshino 主目录 │ ├── config 配置目录 复制 config_example 重命名 | │ └── __bot__.py 主要配置文件 │ ├── config_example │ └── modules 插件目录 └── res 资源目录 cd HoshinoBot python3 -m pip install -r requirements.txt编辑配置文件cp hoshino/config_example hoshino/config vim hoshino/config/__bot__.py运行botpython3 run.py私聊机器人发送在？，若机器人有回复，恭喜您！您已经成功搭建起HoshinoBot了。完整配置 Hoshino配置""" 这是一份实例配置文件 将其修改为你需要的配置，并将文件夹config_example重命名为config """ # hoshino监听的端口与ip HOST = '127.0.0.1' # 本地部署使用此条配置（QQ客户端和bot端运行在同一台计算机） # HOST = '0.0.0.0' # 开放公网访问使用此条配置（不安全） DEBUG = False # 调试模式 SUPERUSERS = [] # 填写超级用户的QQ号，可填多个用半角逗号","隔开 NICKNAME = r'Hoshino' # 机器人的昵称。呼叫昵称等同于@bot，可用元组配置多个昵称 COMMAND_START = {''} # 命令前缀（空字符串匹配任何消息） COMMAND_SEP = set() # 命令分隔符（hoshino不需要该特性，保持为set()即可） RES_PROTOCOL = 'file' RES_DIR = r'./res/' # 使用http协议时需填写，原则上该url应指向RES_DIR目录 RES_URL = 'http://127.0.0.1:5000/static/' '''------- CQHTTP -------''' IMAGE_PATH = "~/go/src/cqhttp/data/images" # cqhttp用这条,保持默认即可 WHITE_LIST = [] # 启用的模块 # 初次尝试部署时请先保持默认 # 如欲启用新模块，请认真阅读部署说明，逐个启用逐个配置 # 切忌一次性开启多个 MODULES_ON = { 'aichat', # 需要apikey，用前修改概率 'aircon', # 群空调 'anticoncurrency', # 反并发插件 'weather', # 天气插件 'what_to_eat', # 今天吃啥 }插件在modules目录下开发自己的插件将插件放入config/__bot__.py文件中的MODULES_ON中MODULES_ON = { 'anticoncurrency', # 反并发插件 'weather', # 天气插件 'what_to_eat', # 今天吃啥 }感谢咖啡佬: https://github.com/Ice-Cirno鱼: https://github.com/Mrs4s
将图片转为灰阶 计算汉明距离 判断是否相似from os import listdir from PIL import Image, ImageFile import pymysql ImageFile.LOAD_TRUNCATED_IMAGES = True conn = pymysql.connect( host='127.0.0.1', user='root', password='xxxxxx', database='media' ) class Pic: @classmethod def get_client(cls): return conn.cursor() # 计算汉明距离 @classmethod def hamming(cls, diff1=None, diff2=None): if diff2 is None: diff2 = [] if diff1 is None: diff1 = [] hamming_distance = 0 for i in range(len(diff1)): if diff1[i] != diff2[i]: hamming_distance += 1 return hamming_distance # 计算图片灰阶 @classmethod def dhash(cls, image, hash_size=8): # 调整为灰阶 image = image.convert('L').resize( (hash_size + 1, hash_size), Image.ANTIALIAS, ) pixels = list(image.getdata()) # 比较相邻像素。 difference = [] for row in range(hash_size): for col in range(hash_size): pixel_left = image.getpixel((col, row)) pixel_right = image.getpixel((col + 1, row)) difference.append(pixel_left > pixel_right) # 转换 decimal_value = 0 hex_string = [] for index, value in enumerate(difference): if value: decimal_value += 2 ** (index % 8) if (index % 8) == 7: hex_string.append(hex(decimal_value)[2:].rjust(2, '0')) decimal_value = 0 return ''.join(hex_string) if __name__ == '__main__': pic = Pic() dirName = "/media/" # 相册路径 allDiff = [] dirList = listdir(dirName) num = 0 for i in dirList: if i == ".DS_Store": continue try: im = Image.open(dirName + i) hash = pic.dhash(im) allDiff.append((i, hash)) num += 1 except: continue same_map = {} for i in range(len(allDiff)): for j in range(i + 1, len(allDiff)): if i != j: ans = pic.hamming(allDiff[i][1], allDiff[j][1]) if ans <= 5: # 判别的汉明距离 same_map[allDiff[i][0]] = allDiff[j][0] print(allDiff[i][0] + " => " + allDiff[j][0]) client = pic.get_client() same_list = [] for k, item in same_map.items(): same = [k, item] same_list.append(same) # 查询 sql = """insert into same(a,b) values(%s,%s);""" client.executemany(sql, same_list) conn.commit()
Go goroutine理解Go语言最大的特色就是从语言层面支持并发（Goroutine），Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine：主Goroutine。当程序启动时，它会自动创建。为了更好理解Goroutine，先讲一下线程和协程的概念线程（Thread）：有时被称为轻量级进程(Lightweight Process，LWP），是程序执行流的最小单元。一个标准的线程由线程ID，当前指令指针(PC），寄存器集合和堆栈组成。另外，线程是进程中的一个实体，是被系统独立调度和分派的基本单位，线程自己不拥有系统资源，只拥有一点儿在运行中必不可少的资源，但它可与同属一个进程的其它线程共享进程所拥有的全部资源。线程拥有自己独立的栈和共享的堆，共享堆，不共享栈，线程的切换一般也由操作系统调度。协程（coroutine）：又称微线程与子例程（或者称为函数）一样，协程（coroutine）也是一种程序组件。相对子例程而言，协程更为一般和灵活，但在实践中使用没有子例程那样广泛。和线程类似，共享堆，不共享栈，协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费，兼顾了多线程的优点，简化了高并发程序的复杂。Goroutine和其他语言的协程（coroutine）在使用方式上类似，但从字面意义上来看不同（一个是Goroutine，一个是coroutine），再就是协程是一种协作任务控制机制，在最简单的意义上，协程不是并发的，而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上。先给个简单实例func loop() { for i := 0; i < 10; i++ { fmt.Printf("%d ", i) } } func main() { go loop() // 启动一个goroutine loop() }GO并发的实现原理一、Go并发模型Go实现了两种并发形式。第一种是大家普遍认知的：多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的，也是Go语言推荐的：CSP（communicating sequential processes）并发模型。CSP并发模型是在1970年左右提出的概念，属于比较新的概念，不同于传统的多线程通过共享内存来通信，CSP讲究的是“以通信的方式来共享内存”。请记住下面这句话：DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.“不要以共享内存的方式来通信，相反，要通过通信来共享内存。”普通的线程并发模型，就是像Java、C++、或者Python，他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是，在访问共享数据（例如数组、Map、或者某个结构体或对象）的时候，通过锁来访问，因此，在很多时候，衍生出一种方便操作的数据结构，叫做“线程安全的数据结构”。例如Java提供的包”java.util.concurrent”中的数据结构。Go中也实现了传统的线程并发模型。Go的CSP并发模型，是通过goroutine和channel来实现的。goroutine 是Go语言中并发的执行单位。有点抽象，其实就是和传统概念上的”线程“类似，可以理解为”线程“。channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲，就是各个goroutine之间通信的”管道“，有点类似于Linux中的管道。生成一个goroutine的方式非常的简单：Go一下，就生成了。go f();通信机制channel也很方便，传数据用channel <- data，取数据用<-channel。在通信过程中，传数据channel <- data和取数据<-channel必然会成对出现，因为这边传，那边取，两个goroutine之间才会实现通信。而且不管传还是取，必阻塞，直到另外的goroutine传或者取为止。示例如下：package main import "fmt" func main() { messages := make(chan string) go func() { messages <- "ping" }() msg := <-messages fmt.Println(msg) }注意 main()本身也是运行了一个goroutine。messages:= make(chan int) 这样就声明了一个阻塞式的无缓冲的通道chan 是关键字 代表我要创建一个通道GO并发模型的实现原理我们先从线程讲起，无论语言层面何种并发模型，到了操作系统层面，一定是以线程的形态存在的。而操作系统根据资源访问权限的不同，体系架构可分为用户空间和内核空间；内核空间主要操作访问CPU资源、I/O资源、内存资源等硬件资源，为上层应用程序提供最基本的基础资源，用户空间呢就是上层应用程序的固定活动空间，用户空间不可以直接访问资源，必须通过“系统调用”、“库函数”或“Shell脚本”来调用内核空间提供的资源。我们现在的计算机语言，可以狭义的认为是一种“软件”，它们中所谓的“线程”，往往是用户态的线程，和操作系统本身内核态的线程（简称KSE），还是有区别的。线程模型的实现，可以分为以下几种方式：用户级线程模型如图所示，多个用户态的线程对应着一个内核线程，程序线程的创建、终止、切换或者同步等线程工作必须自身来完成。它可以做快速的上下文切换。缺点是不能有效利用多核CPU。内核级线程模型这种模型直接调用操作系统的内核线程，所有线程的创建、终止、切换、同步等操作，都由内核来完成。一个用户态的线程对应一个系统线程，它可以利用多核机制，但上下文切换需要消耗额外的资源。C++就是这种。两级线程模型这种模型是介于用户级线程模型和内核级线程模型之间的一种线程模型。这种模型的实现非常复杂，和内核级线程模型类似，一个进程中可以对应多个内核级线程，但是进程中的线程不和内核线程一一对应；这种线程模型会先创建多个内核级线程，然后用自身的用户级线程去对应创建的多个内核级线程，自身的用户级线程需要本身程序去调度，内核级的线程交给操作系统内核去调度。M个用户线程对应N个系统线程，缺点增加了调度器的实现难度。Go语言的线程模型就是一种特殊的两级线程模型（GPM调度模型）。Go线程实现模型MPGM指的是Machine，一个M直接关联了一个内核线程。由操作系统管理。P指的是”processor”，代表了M所需的上下文环境，也是处理用户级代码逻辑的处理器。它负责衔接M和G的调度上下文，将等待执行的G与M对接。G指的是Goroutine，其实本质上也是一种轻量级的线程。包括了调用栈，重要的调度信息，例如channel等。P的数量由环境变量中的GOMAXPROCS决定，通常来说它是和核心数对应，例如在4Core的服务器上回启动4个线程。G会有很多个，每个P会将Goroutine从一个就绪的队列中做Pop操作，为了减小锁的竞争，通常情况下每个P会负责一个队列。三者关系如下图所示：以上这个图讲的是两个线程(内核线程)的情况。一个M会对应一个内核线程，一个M也会连接一个上下文P，一个上下文P相当于一个“处理器”，一个上下文连接一个或者多个Goroutine。为了运行goroutine，线程必须保存上下文。上下文P(Processor)的数量在启动时设置为GOMAXPROCS环境变量的值或通过运行时函数GOMAXPROCS()。通常情况下，在程序执行期间不会更改。上下文数量固定意味着只有固定数量的线程在任何时候运行Go代码。我们可以使用它来调整Go进程到个人计算机的调用，例如4核PC在4个线程上运行Go代码。图中P正在执行的Goroutine为蓝色的；处于待执行状态的Goroutine为灰色的，灰色的Goroutine形成了一个队列runqueues。Go语言里，启动一个goroutine很容易：go function 就行，所以每有一个go语句被执行，runqueue队列就在其末尾加入一个goroutine，一旦上下文运行goroutine直到调度点，它会从其runqueue中弹出goroutine，设置堆栈和指令指针并开始运行goroutine。抛弃P(Processor)你可能会想，为什么一定需要一个上下文，我们能不能直接除去上下文，让Goroutine的runqueues挂到M上呢？答案是不行，需要上下文的目的，是让我们可以直接放开其他线程，当遇到内核线程阻塞的时候。一个很简单的例子就是系统调用sysall，一个线程肯定不能同时执行代码和系统调用被阻塞，这个时候，此线程M需要放弃当前的上下文环境P，以便可以让其他的Goroutine被调度执行。如上图左图所示，M0中的G0执行了syscall，然后就创建了一个M1(也有可能来自线程缓存)，（转向右图）然后M0丢弃了P，等待syscall的返回值，M1接受了P，将·继续执行Goroutine队列中的其他Goroutine。当系统调用syscall结束后，M0会“偷”一个上下文，如果不成功，M0就把它的Gouroutine G0放到一个全局的runqueue中，将自己置于线程缓存中并进入休眠状态。全局runqueue是各个P在运行完自己的本地的Goroutine runqueue后用来拉取新goroutine的地方。P也会周期性的检查这个全局runqueue上的goroutine，否则，全局runqueue上的goroutines可能得不到执行而饿死。均衡的分配工作按照以上的说法，上下文P会定期的检查全局的goroutine 队列中的goroutine，以便自己在消费掉自身Goroutine队列的时候有事可做。假如全局goroutine队列中的goroutine也没了呢？就从其他运行的中的P的runqueue里偷。每个P中的Goroutine不同导致他们运行的效率和时间也不同，在一个有很多P和M的环境中，不能让一个P跑完自身的Goroutine就没事可做了，因为或许其他的P有很长的goroutine队列要跑，得需要均衡。该如何解决呢？Go的做法倒也直接，从其他P中偷一半！Goroutine 小结优点：1、开销小POSIX的thread API虽然能够提供丰富的API，例如配置自己的CPU亲和性，申请资源等等，线程在得到了很多与进程相同的控制权的同时，开销也非常的大，在Goroutine中则不需这些额外的开销，所以一个Golang的程序中可以支持10w级别的Goroutine。每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少（goroutine：2KB ，线程：8MB）2、调度性能好在Golang的程序中，操作系统级别的线程调度，通常不会做出合适的调度决策。例如在GC时，内存必须要达到一个一致的状态。在Goroutine机制里，Golang可以控制Goroutine的调度，从而在一个合适的时间进行GC。在应用层模拟的线程，它避免了上下文切换的额外耗费，兼顾了多线程的优点。简化了高并发程序的复杂度。缺点：协程调度机制无法实现公平调度。
B+ Tree 原理1. 数据结构B Tree 指的是平衡树。平衡树是一颗查找树，并且所有叶子节点位于同一层。B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现，它具有 B Tree 的平衡性，并且通过顺序访问指针来提高区间查询的性能。在 B+ Tree 中，一个节点中的 key 从左到右非递减排列，如果某个指针的左右相邻 key 分别是 key$_i$ 和 key$_{i+1}$，且不为 null，则该指针指向节点的所有 key 大于等于 key$_i$ 且小于等于 key$_{i+1}$。2. 操作进行查找操作时，首先在根节点进行二分查找，找到一个 key 所在的指针，然后递归地在指针所指向的节点进行查找。直到查找到叶子节点，然后在叶子节点上进行二分查找，找出 key 所对应的 data。插入删除操作会破坏平衡树的平衡性，因此在进行插入删除操作之后，需要对树进行分裂、合并、旋转等操作来维护平衡性。3. 为什么使用B+树做索引文件系统及数据库系统普遍采用 B+ Tree 作为索引结构，这是因为使用 B+ 树访问磁盘数据有更高的性能。（一）B+ 树有更低的树高（二）磁盘访问原理1). 操作系统一般将内存和磁盘分割成固定大小的块，每一块称为一页，内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小，使得一次 I/O 就能完全载入一个节点。2). 如果数据不在同一个磁盘块上，那么通常需要移动制动手臂进行寻道，而制动手臂因为其物理结构导致了移动效率低下，从而增加磁盘数据读取时间。B+ 树相对于红黑树有更低的树高，进行寻道的次数与树高成正比，在同一个磁盘块上进行访问只需要很短的磁盘旋转时间，所以 B+ 树更适合磁盘数据的读取。（三）磁盘预读特性为了减少磁盘 I/O 操作，磁盘往往不是严格按需读取，而是每次都会预读。预读过程中，磁盘进行顺序读取，顺序读取不需要进行磁盘寻道，并且只需要很短的磁盘旋转时间，速度会非常快。并且可以利用预读特性，相邻的节点也能够被预先载入。MySQL 索引索引是在存储引擎层实现的，而不是在服务器层实现的，所以不同存储引擎具有不同的索引类型和实现。1. B+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。因为不再需要进行全表扫描，只需要对树进行搜索即可，所以查找速度快很多。因为 B+ Tree 的有序性，所以除了用于查找，还可以用于排序和分组。可以指定多个列作为索引列，多个索引列共同组成键。适用于全键值、键值范围和键前缀查找，其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找，则无法使用索引。InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录，这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方，所以一个表只能有一个聚簇索引。辅助索引的叶子节点的 data 域记录着主键的值，因此在使用辅助索引进行查找时，需要先查找到主键值，然后再到主索引中进行查找。2. 哈希索引哈希索引能以 O(1) 时间进行查找，但是失去了有序性：无法用于排序与分组；只支持精确查找，无法用于部分查找和范围查找。InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”，当某个索引值被使用的非常频繁时，会在 B+Tree 索引之上再创建一个哈希索引，这样就让 B+Tree 索引具有哈希索引的一些优点，比如快速的哈希查找。索引优化1. 独立的列在进行查询时，索引列不能是表达式的一部分，也不能是函数的参数，否则无法使用索引。例如下面的查询不能使用 actor_id 列的索引：select a_id from test WHERE a_id + 1 = 5;2. 联合索引在需要使用多个列作为条件进行查询时，使用多列索引比使用多个单列索引性能更好。例如下面的语句中，最好把 actor_id 和 film_id 设置为多列索引。select a_id, b_id from test where a_id = 1 and b_id = 1;3. 索引列的顺序让选择性最强的索引列放在前面。索引的选择性是指：不重复的索引值和记录总数的比值。最大值为 1，此时每个记录都有唯一的索引与其对应。选择性越高，每个记录的区分度越高，查询效率也越高。例如下面显示的结果中 customer_id 的选择性比 staff_id 更高，因此最好把 customer_id 列放在多列索引的前面。select count(distinct a_id)/count(*) as a_id_t, count(distinct b_id)/count(*) as b_id_t, count(*) as c from test;a_id_t : 0.0001 b_id_t : 0.0373 c : 16049索引的优点大大减少了服务器需要扫描的数据行数。帮助服务器避免进行排序和分组，以及避免创建临时表（B+Tree 索引是有序的，可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建，不需要排序和分组，也就不需要创建临时表）。将随机 I/O 变为顺序 I/O（B+Tree 索引是有序的，会将相邻的数据都存储在一起）。索引的使用条件对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效；对于中到大型的表，索引就非常有效；但是对于特大型的表，建立和维护索引的代价将会随之增长。这种情况下，需要用到一种技术可以直接区分出需要查询的一组数据，而不是一条记录一条记录地匹配，例如可以使用分区技术。
Mysql事务和MVCC谈到MySQL事务，必然离不开InnoDB和MVCC机制，同时，MVCC也是数据库面试中的杀手问题，写这篇总结的目的，就是为了让自己加深映像，这样面试就不会忘记了。在搜索时发现关于MVCC的文章真的是参差不齐(老子真的是零零散散看了三个月都迷迷糊糊)，所以这里集合了各家所言之后进行了自我总结，苦苦研究了许久，才得到的比较清晰的认知，这可能也是我目前最有深度的一篇博客了把，希望对我和看到的人都有所帮助，哈哈。MVCC: Multiversion Concurrency Control,翻译为多版本并发控制，其目标就是为了提高数据库在高并发场景下的性能。MVCC最大的优势：读不加锁，读写不冲突。在读多写少的场景下极大的增加了系统的并发性能在讲解MVCC之前我们需要先了解MySQL的基本架构，如下图所示：MySQL事务MySQL的事务是在存储引擎层实现的，在MySQL中，我们最常用的就是InnoDB和MyISAM，我们都知道，MYISAM并不支持事务，所以InnoDB实现了MVCC的事务并发处理机制，也是我们这篇文章的主要研究内容。可能我们都看到过，MVCC只在RC和RR下，为了分析这个问题，我们先回顾一下SQL标准事务隔离级别read uncommitted 读未提交： 一个事务还没提交时，它做的变更就能被别的事务看到。read committed 读提交：一个事务提交之后，它做的变更才会被其他事务看到。>repeatable read 可重复读：一个事务执行过程中看到的数据，总是跟这个事务在启动时看到的数据是一致的。在可重复读隔离级别下，未提交变更对其他事务也是不可见的。serializable 串行化 ：对于同一行记录，“写”会加“写锁”，“读”会加“读锁”。我们通过两个事务提交流程来说明事务隔离级别的具体效果:我们假设有一个表，仅有一个字段field:DROP TABLE IF EXISTS `mvcc_test`; CREATE TABLE `mvcc_test`( `field` INT)ENGINE=InnoDB; INSERT INTO `mvcc_test` VALUES(1); -- 插入一条数据如下的操作流程：根据事务隔离级别的定义，我们可以来推测，事务A提交前后，事务B的两次读取3和4分别读取的值：若事务B的隔离级别为 read uncommitted，事务B的两次读取都读取到了20，即修改后的值若事务B的隔离级别是read committed，那么，事务B的操作3读取到的值为1，而4读取到的值为20，因为4时事务A已经完成了提交若事务B的隔离级别是repeatable read或serializable，那么操作3和4读取的值都是1。MVCC的必要性MySQL中MYISAM并不支持事务，同样的， MVCC也就和他没有半毛钱关系了，InnoDB相比与MYISAM的提升就是对于行级锁的支持和对事务的支持，而应对高并发事务, MVCC 比单纯的加行锁更有效, 开销更小。但是单纯的并发也会带来十分严重的问题：Lost Update 更新丢失: 多个事务对同一行数据进行读取初值更新时，由于每个事务对其他事务都未感知，会造成最后的更新覆盖了其他事务所做的更新。dirty read 脏读: 事务一个正在对一条记录进行修改，在完成并提交前事务二也来读取该条记录，事务二读取了事务一修改但未提交的数据，如果事务一回滚，那么事务二读取到的数据就成了“脏”数据。non-repeatable read 不可重复读: 个事务在读取某些数据后的某个时间再次读取之前读取过的数据，发现读出的数据已经发生了改变或者删除，这种现象称为“不可重复读”phantom read 幻读: 个事务按相同的查询条件重新读取以前检索过的数据，发现其他事务插入了满足查询条件的新数据，这种现象称为“幻读”不可重复读与幻读的现象是比较接近的，也有人直接就说幻读就是不可重复读，我比较倾向与他两就是他两个： 不可重复读针对的是值的不同，幻读指的是数据条数的不同。同样的对于幻读，单纯的MVCC机制并不能解决幻读问题，InnoDB也是通过加间隙锁来防止幻读。从本质上来说，事务隔离级别就是系统并发能力和数据安全性间的妥协，我们在刚开始学习数据库时就在说: 隔离性越高，数据库的性能就越差，就是这个结果，只是我们当时只知其然罢了。解决并发带来的问题，最通常的就是加锁，但锁对于性能也是腰斩性的，所以MVCC就显得十分重要了。抄大佬的一句话： 在不同的隔离级别下，数据库通过 MVCC 和隔离级别，让事务之间并行操作遵循了某种规则，来保证单个事务内前后数据的一致性。InnoDB 下的 MVCC 实现原理在InnoDB中MVCC的实现通过两个重要的字段进行连接：DB_TRX_ID和DB_ROLL_PT，在多个事务并行操作某行数据的情况下，不同事务对该行数据的UPDATE会产生多个版本，数据库通过DB_TRX_ID来标记版本，然后用DB_ROLL_PT回滚指针将这些版本以先后顺序连接成一条 Undo Log 链1). InnoDB中每个事务有一个唯一的事务ID叫做 transaction id。在事务开始时向InnoDB事务系统申请得到，是按申请顺序严格递增的2). 每行数据是有多个版本的，每次事务更新数据时都会生成一个新的数据版本，并且把transaction id赋值给这个数据行的DB_TRX_IDDB_ROLL_PT: 回滚指针，7byte，指向当前记录的ROLLBACK SEGMENT 的undolog记录，通过这个指针获得之前版本的数据。该行记录上所有旧版本在 undolog 中都通过链表的形式组织。还有一个DB_ROW_ID(隐含id,6byte，由innodb自动产生)，我们可能听说过InnoDB下聚簇索引B+Tree的构造规则:如果声明了主键，InnoDB以用户指定的主键构建B+Tree，如果未声明主键，InnoDB 会自动生成一个隐藏主键，说的就是DB_ROW_ID。另外，每条记录的头信息（record header）里都有一个专门的bit（deleted_flag）来表示当前记录是否已经被删除我们通过图二的UPDATE(即操作2)来举例Undo log链的构建(假设第一行数据DB_ROW_ID=1)：1). 事务A对DB_ROW_ID=1这一行加排它锁2). 将修改行原本的值拷贝到Undo log中3). 修改目标值产生一个新版本，将DB_TRX_ID设为当前事务ID即100，将DB_ROLL_PT指向拷贝到Undo log中的旧版本记录4). 记录redo log， binlog最终生成的Undo log链如下图所示:相比与UPDATE，INSERT和DELETE都比较简单:INSERT: 产生一条新的记录，该记录的DB_TRX_ID为当前事务IDDELETE: 特殊的UPDATE，在DB_TRX_ID上记录下当前事务的ID，同时将delete_flag设为true，在执行commit时才进行删除操作MVCC的规则大概就是以上所述，那么它是如何实现高并发下RC和RR的隔离性呢，这就是在MVCC机制下基于生成的Undo log链和一致性视图ReadView来实现的。一致性视图的生成 ReadView要实现read committed在另一个事务提交之后其他事务可见和repeatable read在一个事务中SELECT操作一致，就是依靠ReadView，对于read uncommitted，直接读取最新值即可，而serializable采用加锁的策略通过牺牲并发能力而保证数据安全，因此只有RC和RR这两个级别需要在MVCC机制下通过ReadView来实现。在read committed级别下，readview会在事务中的每一个SELECT语句查询发送前生成（也可以在声明事务时显式声明START TRANSACTION WITH CONSISTENT SNAPSHOT），因此每次SELECT都可以获取到当前已提交事务和自己修改的最新版本。而在repeatable read级别下，每个事务只会在第一个SELECT语句查询发送前或显式声明处生成，其他查询操作都会基于这个ReadView，这样就保证了一个事务中的多次查询结果都是相同的，因为他们都是基于同一个ReadView下进行MVCC机制的查询操作。InnoDB为每一个事务构造了一个数组m_ids用于保存一致性视图生成瞬间当前所有活跃事务(开始但未提交事务)的ID，将数组中事务ID最小值记为低水位m_up_limit_id，当前系统中已创建事务ID最大值+1记为高水位m_low_limit_id，构成如图所示:一致性视图下查询操作的流程如下:1). 当查询发生时根据以上条件生成ReadView，该查询操作遍历Undo log链，根据当前被访问版本(可以理解为Undo log链中每一个记录即一个版本，遍历都是从最新版本向老版本遍历)的DB_TRX_ID，如果DB_TRX_ID小于m_up_limit_id,则该版本在ReadView生成前就已经完成提交，该版本可以被当前事务访问。DB_TRX_ID在绿色范围内的可以被访问2). 若被访问版本的DB_TRX_ID大于m_up_limit_id，说明该版本在ReadView生成之后才生成，因此该版本不能被访问，根据当前版本指向上一版本的指针DB_ROLL_PT访问上一个版本，继续判断。DB_TRX_ID在蓝色范围内的都不允许被访问3). 若被访问版本的DB_TRX_ID在[m_up_limit_id, m_low_limit_id)区间内，则判断DB_TRX_ID是否等于当前事务ID，等于则证明是当前事务做的修改，可以被访问，否则不可被访问, 继续向上寻找。只有DB_TRX_ID等于当前事务ID才允许访问橙色范围内的版本4). 最后，还要确保满足以上要求的可访问版本的数据的delete_flag不为true，否则查询到的就会是删除的数据。所以以上总结就是只有当前事务修改的未commit版本和所有已提交事务版本允许被访问。我想现在看文章的你应该是明白了(主要是说我自己)。一致性读和当前读前面说的都是查询相关，那么涉及到多个事务的查询同时还有更新操作时，MVCC机制如何保证在实现事务隔离级别的同时进行正确的数据更新操作，保证事务的正确性呢，我们可以看一个案例:DROP TABLE IF EXISTS `mvccs`; CREATE TABLE `mvccs`( `field` INT)ENGINE=InnoDB; INSERT INTO `mvccs` VALUES(1); -- 插入一条数据假设在所有事务开始前当前有一个活跃事务10，且这三个事务期间没有其他并发事务:1). 在操作1开始SELECT语句时，需要创建一致性视图，此时当前事务的一致性视图为[10, 100, 200，301), 事务100开始查询Undo log链，第一个查询到的版本为为事务200的操作4的更新操作， DB_TRX_ID在m_ids数组但并不等于当前事务ID， 不可被访问；2). 向上查询下一个即事务300在操作6时生成的版本，小于高水位m_up_limit_id，且不在m_ids中，处于已提交状态，因此可被访问；3). 综上在RR和RC下得到操作1查询的结果都是2那么操作5查询到的field的值是多少呢？在RR下，我们可以明确操作2和操作3查询field的值都是1，在RC下操作2为1，操作3的值为2，那么操作5的值呢？答案在RR和RC下都是是3，我一开始以为RR下是2，因为这里如果按照一致性读的规则，事务300在操作2时都未提交，对于事务200来说应该时不可见状态，你看我说的是不是好像很有道理的样子？上面的问题在于UPDATE操作都是读取当前读(current read)数据进行更新的，而不是一致性视图ReadView，因为如果读取的是ReadView，那么事务300的操作会丢失。当前读会读取记录中的最新数据，从而解决以上情形下的并发更新丢失问题。作者：SEVEN_12cd链接：https://www.jianshu.com/p/d67f0329d3bf来源：简书著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。