sql server cdc 清理_基于CDC技术的ElasticSearch索引同步机制

概述

ElasticSearch作为一个基于Lucene的搜索引擎被广泛应用于各种应用系统,比如电商、新闻类、咨询类网站。在使用ElasticSearch开发应用的过程中,一个非常重要的过程是将数据导入到ElasticSearch索引中建立文档。在一开始系统规模比较小时,我们可以使用logstash来同步索引。logstash的好处是开方量少,只要进行编写简单的索引模板和同步sql,就能快速搭建索引同步程序。但是随着应用数据规模的变大,索引变化变得非常频繁。logstash的缺点也随着暴露,包括(1)不支持删除,只能通过修改字段属性软删除,随着应用使用时间的增长,ElasticSearch中会留存大量的无用数据,拖慢搜索速度。(2)sql分页效率低,sql查询慢。logstash的分页逻辑是先有一个大的子查询,然后再从子查询中分页获取数据,因此效率低下,当数据库数据量大时,一个分页查询就需要几百秒。同步几千万数据可能需要1天时间。因此我们决定放弃使用logstash,而改用使用canal来搭建基于CDC技术的ElasticSearch索引同步机制。

系统架构设计

8de9be0decba3e9ea37faf95f18c3a84.png

如图所示,索引同步系统由几个部分组成,下面分点介绍。

(1)数据库

原始数据数据库

(2)Canal

Canal是阿里云开源的MySql数据库增量数据订阅和消费工具。它的实现原理是将自己伪装为一个MySQL slave,向MySql master发送dump协议;MySQL master收到dump请求,开始推送binary log给slave,canal解析binary log对象。

(3)Canal Client

Canal Client是自己实现的程序,通过从Canal Server中获取经过Canal解析之后的数据库binlog日志,做相应的业务逻辑处理。在本文介绍的基于CDC的索引同步系统中,Canal Client订阅搜索相关的数据库表的binlog日志,如果跟数据搜索相关的数据发生变化时,就向Rabbit发一条消息,表明数据发生变化了,通知同步Worker从MySQL同步数据到ES。

(4)RabbitMQ

消息队列,也可以选用Kafaka等其他消息队列,根据具体业务确定。

(5)索引同步Worker

Worker从消息队列中消费数据,根据消息从MySQL获取相应的数据并同步到ElasticSearch中。

Canal Client实现

Canal Client从Canal Server中获取binlog日志,并根据业务需求进行处理。以下通过一些关键代码介绍Canal Client的实现。

(1)在pom中添加Canal client的依赖。

<dependency><groupId>com.alibaba.otter</groupId><artifactId>canal.client</artifactId><version>1.1.0</version></dependency>

(2)初始化Canal连接

CanalConfig包含了Canal的配置信息。CanalConnector为canal-client包中的类,我们通过这个类来连接server,获取binlog,关闭server。该服务基于SpringBoot。因此init会在CanalClientInitializer bean被创建时被调用,preDestory会在服务关闭,CanClientInitializer被销毁时被调用。

@Component
@Slf4j
public class CanalClientInitializer {CanalConfig canalConfig;CanalConnector connector;CanalDataProcessor canalDataProcessor;public CanalClientInitializer(@Autowired CanalConfig canalConfig, @Autowired CanalDataProcessor canalDataProcessor) {this.canalConfig = canalConfig;this.canalDataProcessor = canalDataProcessor;}@PostConstructpublic void init() throws InterruptedException {connector = CanalConnectors.newSingleConnector(new InetSocketAddress(canalConfig.getIp(), canalConfig.getPort()), canalConfig.getDestination(), "", "");//建立连接connector.connect();//订阅相关的表connector.subscribe(canalConfig.getSyncTable());canalDataProcessor.process(connector);}@PreDestroypublic void preDestroy() {log.info("stop the canal client");canalDataProcessor.stopProcess();}}

(3)CanalDataProcessor获取并处理binlog

@Component
@Slf4j
public class CanalDataProcessor {boolean isRunning;RabbitTemplate rabbitTemplate;TableChangeProcessor tableChangeProcessor;public CanalDataProcessor(@Autowired RabbitTemplate rabbitTemplate, @Autowired TableChangeProcessor processor) {this.rabbitTemplate = rabbitTemplate;this.tableChangeProcessor = processor;}@Asyncpublic void process(CanalConnector connector) throws InterruptedException {isRunning = true;while (isRunning) {try {//获取消息Message message = connector.getWithoutAck(100, 10L, TimeUnit.SECONDS);//业务处理逻辑processMessage(message);//消息被成功执行,向Canal Server发送ack消息通知server该message已经被处理完成connector.ack(message.getId());} catch (Exception e) {log.error("wtf", e);//当消息没被成功处理完成时进行回滚,下次能够重新获取该Messageconnector.rollback();Thread.sleep(1000);}}connector.disconnect();}public void stopProcess() {isRunning = false;}private void processMessage(Message message) {for(Entry entry : message.getEntries()) {try {tableChangeProcessor.process(entry);} catch (Exception e) {log.error("wtf", e);continue;}}}
}

(4)TableChangeProcessor

TableChangeProcessor中为具体的业务逻辑,处理Message,获取跟搜索相关的数据变化,发送相应的消息到消息队列中。

注意点

(1)忽略搜索无关的数据字段变化,避免不必要的索引更新,降低服务器压力。如Products表中有一个product_weight表示商品重量发生了变化,但其实商品重量跟搜索无关,那就不要关心这个变化。

(2)对于搜索中不会出现的数据,不要写入到ES中,比如电商商品中的下架商品,另外,如果商品被下架,则要进行监听通知索引同步Worker从es中删除索引文档。这样能够降低ES中总的索引文档数量,提升搜索效率。

(3)要考虑Rabbit挂掉或者队列写满,消息无法写入的情况;首先应该在Rabbit发送消息时添加重试,其次应该在重试几次还是失败的情况下抛出异常,canal消息流回滚,下次还是能够获取到这个数据变化的Canal消息,避免数据变动的丢失。

(4)注意目前Canal只支持单Client。如果要实现高可用,则需要依赖于ZooKeeper,一个Client作为工作Client,其余Client作为冷备,当工作Client挂掉时,冷备Client监听到ZooKeeper数据变化,抢占锁成为工作Client。

Canal Worker实现

索引同步Worker从消息队列中获取Canal Client发送的跟搜索相关的数据库变化消息。举个例子,比如商品表中跟搜索相关的字段发生了变化,Canal Client会发送以下一条数据:

{"change_id": "694212527059369984","change_type": 1,   //商品发生变化"change_time": "1600741397"
}

在Worker中监听队列消息:

@Component
@Slf4j
public class ProductChangeQueueListener {@Autowired@Qualifier("snake")ObjectMapper om;@AutowiredChangeEventHandlerFactory changeEventHandlerFactory;@RabbitListener(queues = RabbitConfig.PRODUCT_QUEUE_NAME, containerFactory = "customRabbitListenerContainerFactory")public void onChange(Message message) {ChangeEvent event = parse(message);if(event == null) {return;}changeEventHandlerFactory.handle(event);}private ChangeEvent parse(Message message) {ChangeEvent event = null;try {event = om.readValue(new String(message.getBody()), ChangeEvent.class);} catch (Exception e) {log.error("同步失败,解析失败", e);}return event;}}

ChangeEventHandlerFactory为事件处理器的工厂类。以下为一个事件处理器的实现。它监听changeType为CHANGE_TYPE_OUT_PRODUCT的事件,从数据库中获取到变动的数据,构建ES的IndexRequest,并将Request存入到RequestBulkBuffer中,等待批量同步到ES中。有些同学可能会有疑问,为何不直接从Canal中获取数据,主要原因是Canal中只包含了单表数据,但是索引文档可能包含了多表的数据,因此还需要从MySQL获取数据。如果索引文档中只包含单表数据,可以考虑在ChangeEvent中包含修改之后的数据,索引同步Woker就不用再从MySql中再获取一遍数据,提升Worker工作效率。

@Component
@Slf4j
public class OutProductEventHandler implements ChangeEventHandler {@AutowiredProductDao productDao;@AutowiredRequestBulkBuffer buffer;@AutowiredOutProductChangeRequestBuilder builder;@Override@Retryablepublic boolean handle(ChangeEvent changeEvent) {if (!match(changeEvent)) {return false;}Tuple dataTuple = productDao.getProductWithStore(changeEvent.getChangeId());if (dataTuple == null) {return true;}Product product = dataTuple.get(QProduct.product);Store store = dataTuple.get(QStore.store);IndexRequest request = null;try {request = builder.convertToUpdateQuery(getTimestampNow(), product, store);} catch (Exception e) {log.error("wtf", e);}if (request == null) {return true;}buffer.add(request);return true;}@Overridepublic boolean match(ChangeEvent changeEvent) {return ChangeEvent.CHANGE_TYPE_OUT_PRODUCT == changeEvent.getChangeType();}
}

在上面的OutProductEventHandler类中,我们并不直接在该类中使用RestHighLevelClient将文档更新到ES索引,而是将IndexRequest暂存到RequestBulkBuffer中。RestBulkBuffer使用CircularFifoBuffer作为存储数据结构。

@Component
public class RequestBulkBuffer {CircularFifoBuffer buffer;public RequestBulkBuffer(CircularFifoBuffer buffer) {this.buffer = buffer;}public void add(DocWriteRequest<?> request) {buffer.add(request);}}

CircularFifoBuffer是一个经过改造的环形队列实现。允许多线程写,在我们这个应用场景中只支持也只需支持单线程读->处理->移除处理完的数据。当环形队列缓存满时,借助于semaphore,写入线程将会被阻塞,在后面的Worker如何防止数据丢失中,我们来阐述为什么要这么做。

/*** 允许多线程写* 只允许单线程->读->处理->移除*/
public class CircularFifoBuffer {private Logger logger = LoggerFactory.getLogger(CircularFifoBuffer.class.getName());private transient Object[] elements;private transient int start = 0;private transient int end = 0;private transient boolean full = false;private final int maxElements;private ReentrantLock addLock;private Semaphore semaphore;public CircularFifoBuffer(int size) {if (size <= 0) {throw new IllegalArgumentException("The size must be greater than 0");}elements = new Object[size];maxElements = elements.length;addLock = new ReentrantLock();semaphore = new Semaphore(size);}public int size() {int size = 0;if (end < start) {size = maxElements - start + end;} else if (end == start) {size = (full ? maxElements : 0);} else {size = end - start;}return size;}public boolean isEmpty() {return size() == 0;}public boolean isFull() {return size() == maxElements;}public int maxSize() {return maxElements;}public void clear() {full = false;start = 0;end = 0;Arrays.fill(elements, null);}public boolean add(Object element) {if (null == element) {throw new NullPointerException("Attempted to add null object to buffer");}addLock.lock();try {semaphore.acquire();} catch (Exception e) {logger.error("RingBuffer", "线程退出,添加失败");return false;}elements[end++] = element;if (end >= maxElements) {end = 0;}if (end == start) {full = true;}addLock.unlock();return true;}public Object get() {if (isEmpty()) {return null;}return elements[start];}public Object remove() {if (isEmpty()) {return null;}Object element = elements[start];if(null != element) {elements[start++] = null;if (start >= maxElements) {start = 0;}full = false;semaphore.release();}return element;}/*** @param size the max size of elements will return*/public Object[] get(int size) {int queueSize = size();if (queueSize == 0) { //emptyreturn new Object[0];}int realFetchSize =  queueSize >= size ? size : queueSize;if (end > start) {return Arrays.copyOfRange(elements, start, start + realFetchSize);} else {if (maxElements - start >= realFetchSize) {return Arrays.copyOfRange(elements, start, start + realFetchSize);} else {return ArrayUtils.addAll(Arrays.copyOfRange(elements, start, maxElements),Arrays.copyOfRange(elements, 0, realFetchSize - (maxElements - start)));}}}public Object[] getAll() {return get(size());}public Object[] remove(int size) {if(isEmpty()) {return new Object[0];}int queueSize = size();int realFetchSize = queueSize >= size ? size : queueSize;Object [] retArr = new Object[realFetchSize];for(int i=0;i<realFetchSize;i++) {retArr[i] = remove();}return retArr;}}

下面这个类为缓存的消费者,它循环从buffer中获取一定数据的数据,并使用RestHighLevelClient将数据批量同步到ES。在Worker启动时,会创建一个线程调用startConsume,在服务关闭时该线程结束。

@Slf4j
public class RequestBulkConsumer {private static final int DEFAULT_BULK_SIZE = 2000;private CircularFifoBuffer buffer;private EsBulkRequestService service;private boolean isRunning = false;private int bulkSize = DEFAULT_BULK_SIZE;public RequestBulkConsumer(CircularFifoBuffer buffer, RestHighLevelClient client) {this.buffer = buffer;this.service = new EsBulkRequestService(client);}public void setBulkSize(int size) {this.bulkSize = size;}public int getBulkSize() {return bulkSize;}public boolean isRunning() {return isRunning;}public void startConsume() {if(isRunning) {return;}isRunning = true;while(true) {if(!isRunning) {break;}Object [] items = buffer.get(bulkSize);if(items.length == 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {break;}} else {List<DocWriteRequest<?>> requests = convert(items);try {BulkResponse response = service.request(requests);processResponse(response);buffer.remove(items.length);if (items.length < bulkSize) {Thread.sleep(3000);}} catch (InterruptedException e) {break;} catch (IOException e) {log.error("wtf", e);} catch (Exception e) {log.error("wtf", e);buffer.remove(items.length);}}}}private List<DocWriteRequest<?>> convert(Object [] items) {return Stream.of(items).map(i -> {if(i instanceof DocWriteRequest) {return (DocWriteRequest<?>) i;} else {return null;}}).filter(Objects::nonNull).collect(Collectors.toList());}public void stop() {isRunning = false;}private void processResponse(BulkResponse bulkResponse) {BulkItemResponse [] itemResponseArr = bulkResponse.getItems();for(BulkItemResponse resp : itemResponseArr) {DocWriteResponse docWriteResponse = resp.getResponse();if(docWriteResponse instanceof IndexResponse) {IndexResponse indexResponse = (IndexResponse) docWriteResponse;if(indexResponse.getResult() != Result.CREATED && indexResponse.getResult() != Result.UPDATED) {if(indexResponse.status() == RestStatus.CONFLICT) {continue;} else {log.error("索引更新失败: {}, {}", indexResponse.getId(), resp.getFailureMessage());}}} else if(docWriteResponse instanceof DeleteResponse) {DeleteResponse deleteResponse = (DeleteResponse) docWriteResponse;if(deleteResponse.getResult() != Result.DELETED) {log.error("索引删除失败: {}, {}", deleteResponse.getId(), resp.getFailureMessage());}}}}
}

以下为Worker的主要几个类的代码。在索引同步系统中,高可用并不是最重要的,因为我们的搜索本身是一个准实时系统,只需要保证最终一致性就可以了,我们主要需要避免的是数据变更的丢失。以下说明在Worker中是如何避免数据丢失的。

避免数据丢失

(1)如果Rabbit挂掉,没关系,Canal Client那边在Rabbit挂掉期间无法消费binlog,会等待Rabbit重启之后再处理数据变化。Worker只要能做到Rabbit重启之后重连就行。

(2)如果MySQL挂掉,则Worker无法从数据库中获取数据,则消息处理失败,消息会堆积在Rabbit中。等MySQL重新上线之后,消息重新开始处理,数据也不会丢失。

(3)如果ES挂掉,则批量处理线程消费buffer中的数据时会失败,buffer会被生产者填满,由于CircularFifoBuffer在被填满时使用了信号量阻塞生产者线程,消息又会被堆积在Rabbit中,等待ES重新上线之后,消息重新开始处理,数据也不会丢失。

(4)如果Rabbit队列被写满,emmm,设置好在内存被占满时将消息写入硬盘然后搞一个大一点的硬盘吧,Rabbit默认应该就是这么做的。然后做好预警,当消息达到一定量时抓紧处理,一般来说可能性不是很大。

(5)版本冲突,如果商品表中某一条数据如商品A在同一秒内变化了两次,消息队列中有连续两条消息,又由于这两条消息可能在两个线程中被消费,由于网络,计算机性能等原因,先变的数据后被写入ES中,导致ES中数据和MySql数据不一致。因此我们在更新索引时使用ES的外部版本号。使用从MySQL中取数据时的时间戳作为版本号,只有当时间戳比当前版本号大或相等时才能变更文档,否则ES会报版本冲突错误。

private IndexRequest convertToUpdateQuery(Long timestamp, OutStoreProduct outStoreProduct) throws JsonProcessingException {IndexRequest indexRequest = new IndexRequest(indexName, "doc", outStoreProduct.getId());if(StringUtils.isEmpty(outStoreProduct.getTooEbaoProductId())) {log.error("商品 {} 的ebaoProductId为空,无法同步", outStoreProduct.getId());return null;}indexRequest.source(om.writeValueAsString(outStoreProduct), XContentType.JSON).versionType(VersionType.EXTERNAL_GTE).version(timestamp).routing(outStoreProduct.getTooEbaoProductId());return indexRequest;}

关于全量同步

以上只是实现了增量同步,在索引初始化时,我们需要做全量同步操作,将数据从数据库初始化到ES索引中。我们可以在Worker中写一个接口,该接口实现逻辑分批将数据同步任务发到消息队列中,其它worker收到消息后完成对应任务。比如我们可以发布每一个门店的数据同步任务,worker每收到一个消息,同步一个门店的数据。

总结

综上,本系统是一个近实时的能够保证ES和MySQL数据一致性的高效索引同步系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/244168.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql 8.0数据备份恢复_MySQL 8.0 增强逻辑备份恢复工具介绍-爱可生

作者&#xff1a;杨涛涛资深数据库专家&#xff0c;专研 MySQL 十余年。擅长 MySQL、PostgreSQL、MongoDB 等开源数据库相关的备份恢复、SQL 调优、监控运维、高可用架构设计等。目前任职于爱可生&#xff0c;为各大运营商及银行金融企业提供 MySQL 相关技术支持、MySQL 相关课…

android 字体竖直居中_问下弹性盒内不知道高度的时候想让字体垂直居中代码要怎么写...

[Asm] 纯文本查看 复制代码自适应圣杯布局* {margin: 0;padding: 0;}body,html {height: 100%;}body {display: flex;flex-direction: column;}.header {width: 100%;flex: 1;background-color: #dcdcdc;}.main {width: 100%;flex: 6;display: flex;}.left-container {flex: 1;…

mysql数据库优化清理_mysql 数据库优化整理

内连接 -- inner join内连接在不加on的情况下, 也是去求笛卡尔乘积. 不加on的用法并不推荐使用, 容易造成内存溢出的情况. 加on的时候, 在连表的时候, 就会对数据进行筛选, 以此来缩减有效数据范围。select * from A inner join B; select * from A,B; //交叉连接 -- 笛卡尔乘…

python38怎么用_Python基础练习实例38(数组操作)

题目&#xff1a;有一个已经排好序的数组。现输入一个数&#xff0c;要求按原来的从小到大顺序将它插入数组中。程序分析&#xff1a;首先判断此数是否大于最后一个数&#xff0c;然后再考虑插入中间的数的情况&#xff0c;插入后此元素之后的数&#xff0c;依次后移一个位置。…

python判断正数和负数教案_正数和负数 教学设计

《正数和负数 教学设计》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《正数和负数 教学设计(3页珍藏版)》请在人人文库网上搜索。1、1.1 正数与负数第一课时(一)概述课名是正数与负数&#xff0c;是义务教育课程标准实验教科书初中七年级的一堂数学课。本节课所需课…

修改pytho2安装插件的位置_office2016自定义安装以及修改安装位置

一、下载office2016专业增强版ed2k://|file|cn_office_professional_plus_2016_x86_x64_dvd_6969182.iso|2588266496|27EEA4FE4BB13CD0ECCDFC24167F9E01|/复制使用迅雷下载二、office2016部署工具部署工具地址​www.microsoft.com运行部署工具会生成setup.execonfiguration-Off…

vue 获取元素在浏览器的位置_前端开发JS获取页面元素的位置

1.网页的大小和浏览器窗口的大小一张网页的全部面积&#xff0c;就是它的大小。通常情况下&#xff0c;网页的大小由内容和document元素的clientHeight和clientWidth属性&#xff0c;就代表了网页的大小。function getViewport(){if (document.compatMode “BackCompat”){ret…

python 发送邮件不显示附件_python3发送邮件(无附件)

python3发送邮件代码&#xff1a;import smtplibfrom email.mime.text import MIMETextfrom email.utils import formatdate#设置服务器所需信息#163邮箱服务器地址mail_host smtp.163.com#163用户名mail_user h*****163.com#密码mail_pass h****password#邮件发送方邮箱地址…

小说是读者的艺术

小说的处境到了今天这个份上&#xff0c;该让我们的编辑和作家有点悔悟了&#xff0c;那就是光靠玩技巧和语言以及所有的花活唬不了读者&#xff0c;更救不了小说本身。我们知道一种艺术形式的存在很大程度上依赖于它的接受者的存在&#xff0c;所谓皮之不在&#xff0c;毛将存…

stright 在mysql_MySQL优化的奇技淫巧之STRAIGHT_JOIN

最近没怎么搞SQL优化&#xff0c;碰巧数据库被慢查询搞挂了&#xff0c;于是拿来练练手。问题通过「SHOW FULL PROCESSLIST」语句很容易就能查到问题SQL&#xff0c;如下&#xff1a;SELECT post.*FROM postINNER JOIN post_tag ON post.id post_tag.post_idWHERE post.status…

小说不“好看”,读者就给你“好看”

小说的病变和无可救药的衰竭是因为小说不好看 □主 持 人&#xff1a;兴 安 青年作家&#xff1a;丁 天 邱华栋 陆 涛 古清生 小说是什么&#xff1f;是“街谈巷议”———这是我们的老祖宗概括的&#xff0c;就是说它生于民间&#xff0c;是给更多的人看的&#x…

狗肉朋友

想想几年的圣诞聚会,朋友、哥们、同事、陌生人,每一年人走马灯似的变换,有的早在记忆中消失,如过眼烟云.而一直能保留下来的就那么几个朋友。 随着年龄的增长,自己在交往中纳新的能力和兴致越来越低,朋友的圈子越来越小,过去的那种结交天下豪杰的扩张心理没有了,守住故交,守住最…

mysql json坑_使用mysql innodb 使用5.7的json类型遇到的坑和解决办法

----------------------------------------------#查询JSON的某个字段select data -> ‘$.Host‘ from temp#创建虚拟列ALTER TABLE temp ADD host varchar(128) GENERATED ALWAYS AS (json_extract(data,‘$.Host‘)) VIRTUAL;#给虚拟列创建索引ALTER TABLE temp ADD INDEX…

恐怖小说之王——斯蒂芬·金 (转贴)

《宠物公墓》改编自斯蒂芬金的同名小说&#xff0c;在所有斯蒂芬金的恐怖小说里&#xff0c;恐怕就属这一部是最吓人的了。但斯蒂芬金的原著由于篇幅过长&#xff0c;难免有拖沓之感&#xff0c;当被改编成电影时&#xff0c;斯蒂芬金非常有效地压缩了与恐怖无关的枝节&#xf…

买了几张好碟

最喜欢的是《塔尔可夫斯基的全集》。以前有他零散的&#xff0c;几乎全了&#xff0c;可是看到整套的&#xff0c;包装又漂亮&#xff0c;声音又进化了5.1声道&#xff0c;确实没有理由不收啊。 《天下无贼》&#xff08;正版&#xff09;&#xff0c;《狂蟒之灾&#xff12;》…

mysql unix_timestamp 格式化_FROM_UNIXTIME 格式化MYSQL时间戳函数_MySQL

unix时间戳bitsCN.com函数&#xff1a;FROM_UNIXTIME作用&#xff1a;将MYSQL中以INT(11)存储的时间以”YYYY-MM-DD”格式来显示。语法&#xff1a;FROM_UNIXTIME(unix_timestamp,format)返回表示 Unix 时间标记的一个字符串&#xff0c;根据format字符串格式化。format可以包含…

酒喝高了,歌听多了

我常常忘记自己是蒙古人。昨天去了达尔汗蒙古风情餐吧&#xff0c;参加了蒙古的同乡会。AA制。好久没有参加这样的活动了&#xff0c;见到了些老面孔&#xff0c;大多是新面孔。有的胖了&#xff0c;有的老了&#xff0c;有的单身了&#xff0c;有的成双了&#xff0c;有的叫不…

抚摸斯蒂芬·金 (图)

<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />最近翻到一本美国恐怖小说大师斯蒂芬金的自传《抚摸恐怖——我的创作生涯》&#xff0c;珠海出版社2002年5月出版。书中披露了斯蒂芬金很多不为人知的写作经历以及对恐怖小说的看…

mysql设置定位慢查询_mysql优化——定位慢查询

1.定位慢查询1、show status 命令命令使用方式&#xff1a;show [session|global] status like slow_queries如果你不写 [session|global] 默认是session 会话&#xff0c;指取出当前窗口的执行&#xff0c;如果你想看所有(从mysql 启动到现在&#xff0c;则应该 global)执行s…

当我们年轻的时候 (转贴)

当我们年轻的时候 徐坤/文 这是一张十年前的照片。1996 年底&#xff0c;《小说月报》第7届百花奖发奖会在天津蓟县举行。获奖作者与编辑一应到达。左起&#xff1a;徐坤&#xff0c;刘醒龙&#xff0c;毕飞宇&#xff0c;李师东&#xff0c;李敬泽&#xff0c;兴安。 时值隆…