본문으로 건너뛰기

MySQL에서 MariaDB 이관시 서브쿼리의 정렬이 바뀔 때

· 약 2분

@Rownum 변수를 사용해 페이징 쿼리를 구현했는데, MySQL 에서는 정상적으로 정렬이 되다가 Maria 로 이관 후에 정렬이 반대로 나오는 경우가 있다.

쿼리

SELECT SQL_CALC_FOUND_ROWS @RNUM:=@RNUM+1 AS ROWNUM, R.* FROM (
SELECT @RNUM:=0, Q.* FROM (

SELECT * FROM TABLE1
WHERE ID = '1'
ORDER BY REG DESC
) Q
) R
LIMIT 0,10

설명

Rownum 변수를 사용해 TABLE1 에서 최신 날짜 순으로 데이터를 가지고 온다.

해결

LIMIT 2^64-1를 서브쿼리 내에 추가해준다.

LIMIT 의 최댓값은 unsigned 64bit-1 이므로 해당 값을 날려준다.

쿼리

SELECT SQL_CALC_FOUND_ROWS @RNUM:=@RNUM+1 AS ROWNUM, R.* FROM (
SELECT @RNUM:=0, Q.* FROM (

SELECT * FROM TABLE1
WHERE ID = '1'
ORDER BY REG DESC

LIMIT 18446744073709551615
) Q
) R
LIMIT 0,10

원인

Optimizer 가 임시테이블을 만든 후 filesort 하기 때문에 발생한다.

MySQL 버전 및 설정 확인

· 약 1분

Database Migration 또는 Module 설치시 MySQL 버전을 자주 물어본다.

SELECT version();

SHOW variables LIKE 'datadir';

설명

첫번째 쿼리 실행시 버전이 나오고, 두번째 쿼리 실행시 설치 경로가 나온다.

MySQL Lock 해제

· 약 1분

잘못된 업데이트 쿼리를 돌리다가 테이블이 잠길 경우 Lock을 해제해줘야 한다.

show processlist

kill ${processid}

설명

show processlist 쿼리를 날리면 현재 database를 사용 중인 프로세스의 목록이 나온다. 목록에서 해당 쿼리를 찾은 뒤 process id를 가지고 kill 쿼리를 실행하면 된다.

HTML 태그 모두 제거

· 약 1분

스마트 에디터 같은 에디터를 쓸 대 내용을 텍스트로만 받아야할 경우가 있다.

var data = data.replace(/[<][^>]*[>]/g, "");

설명

< > 안의 태그들을 모두 빈값으로 치환한다.

JSON.parse unexpected type Error

· 약 2분

database 에 이미 json 으로 저장된 데이터를 가져와 JSON.parse 메소드로 JSON 데이터를 파싱하는 중에 값이 string 인데도 오류가 나는 경우가 있다.

로그를 확인해보면

{"data":"[{"name":"gracefullight"},{"name","daniel"}]"}

[] 배열 기호 앞뒤로 따움표가 들어가 있다. 이중 object 가 있다면 선언문 앞뒤로 따움표가 들어가 파싱 오류가 난다.

jQuery.ajax 의 dataType 을 json 으로 설정해 데이터를 받는데, 이미 json 형태의 string 이 넘어와 두번 치환이 된 것 같다.

다시 json 을 인식할 수 있게 치환해보자

소스

const jsonReplace = function (j) {
if (j) {
j = j
.replace(/"\[/g, "[")
.replace(/\]"/g, "]")
.replace(/"\{/g, "{")
.replace(/\}"/g, "}");
}
return j;
};

설명

해당 함수로 오류가 나는 json string 을 치환 후에 다시 JSON.parse 를 실행하면 된다.

여담

정규식을 활용해 replace 중첩을 피하게 짤 수 있을 것 같은데...

hasOwnProperty vs prototype.hasOwnProperty

· 약 2분

for-in 구문을 쓸 때 상속된 프로퍼티나 this 구문을 방지하기 위하여 보통 hasOwnProperty 로 체크한다.

for (prop in obj) {
// eslint-disable-next-line no-prototype-builtins
if (obj.hasOwnProperty(prop)) {
// code...
}
}

문제점

위와 같은 방식은 아래와 같이 hasOwnProperty 가 재정의 된 객체에서 오류를 발생할 수 있다.

소스

const obj = {
hasOwnProperty: function () {
return false;
},
data1: 1,
};

for (prop in obj) {
// obj.hasOwnProperty(prop) 의 결과는 항상 false
// eslint-disable-next-line no-prototype-builtins
if (obj.hasOwnProperty(prop)) {
// 이 구문은 실행되지 않는다.
}
}

prototype.hasOwnProperty

따라서 prototype 을 사용해 코딩해야한다.

소스

for (prop in obj) {
// 첫번째 방법
if ({}.hasOwnProperty.call(obj, prop)) {
// 체크
}

// 두번째 방법
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
// 체크
}
}

설명

첫번째 방법은 익명 Object 를 생성한 후 obj 와 prop 을 밀어넣는 방식이고 두번째 방법은 기본 Object 의 hasOwnProperty 를 가져와 obj 와 prop 을 밀어넣는 방식이다.

두번째 방법이 새(익명) Object 를 생성하지 않기에 이 방법을 사용하면 된다.

더 자세하게 알고 싶다면 mdn을 참조한다.

Throttle, Debounce Pattern

· 약 3분

javascript throttle 패턴과 debounce 패턴에 대해 알아보자.

매 ms 마다 한 번만 호출된다. mousemove, scroll 같은 이벤트로 호출되는 함수는 이벤트 발생시 무한정 호출되어 성능 저하를 가지고 오는데, 이를 방지할 수 있다.

모바일에서 스크롤링 더보기에 사용할 수 있다.

소스

const throttleFunction = (function () {
"use strict";

const timeWindow = 500; // 여기에 시간(ms)을 지정한다
let lastExecution = new Date(new Date().getTime() - timeWindow);
// ES6 이하일 경우 ...args에 호출할 parameter 만큼 준다 function(arg1, arg2...)
const throttleFunction = function (...args) {
// 여기에 로직을 구현한다
};

return function () {
if (lastExecution.getTime() + timeWindow <= new Date().getTime()) {
lastExecution = new Date();
return throttleFunction.apply(this, arguments);
}
};
})();

// 사용법
throttleFunction(param1, param2);

Debounce

마지막 호출로부터 ms 후에 함수를 한번 호출한다. 지연된 호출을 할 수 있게 해주는데, 호출이 반복되는 동안은 실행을 방지하고, 호출이 멈춘 뒤 지정한 ms 후에 함수를 실행해 성능 저하를 막을 수 있다.

검색 자동완성 기능에 적합하다.

소스

const debounceFunction = (function () {
"use strict";

const timeWindow = 500; // 여기에 시간(ms)을 지정한다
let timeout;
// ES6 이하일 경우 ...args에 호출할 parameter 만큼 준다 function(arg1, arg2...)
const debounceFunction = function (...args) {
// 여기에 로직을 구현한다
};

return function () {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const context = this;
const args = arguments;

clearTimeout(timeout);
timeout = setTimeout(function () {
debounceFunction.apply(context, args);
}, timeWindow);
};
})();

// 사용법
debounceFunction(param1, param2);

여담

즐겨 사용하는 sublime text package 인 javascript patterns에서 참조했다.

lodash를 사용한다면 debounce, throttle로 더 쉽게 사용할 수 있다.

UMD Patterns

· 약 3분

UMD (Universal Module Definition) Pattern 이란 AMD (requireJS), CommonJS (node), 일반 browser 환경에서 통합해서 쓸 수 있는 javascript module pattern 이다.

jQuery, moment, D3 등의 라이브러리에서도 해당 패턴을 사용한다.

새로 만들 예제의 모듈명은 module1 이고 jQuery 에 종속적이라고 가정한다.

(function (global, factory) {
if (typeof define === "function" && define.amd) {
// AMD 환경
define(["exports", "jquery"], factory);
} else if (
typeof exports === "object" &&
typeof exports.nodeName !== "string"
) {
// CommonJS 환경
factory(exports, require("jquery"));
} else {
// 일반 브라우저
factory((global.module1 = global.module1 || {}), global.$);
}
})(this, function (exports, $) {
// module1의 private 기능 구현

// exports에 public function을 붙혀주면 된다.
exports.action = function () {};
});

종속되는 모듈을 추가하려면, AMD 는 배열안에, commonJS 와 일반은 파라미터로 추가한 뒤 12 번째 줄의 콜백함수 안에 원하는 변수로 받으면 된다.

예제 - 기본형

(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["exports"], factory);
} else if (
typeof exports === "object" &&
typeof exports.nodeName !== "string"
) {
factory(exports);
} else {
factory((global.module1 = global.module1 || {}));
}
})(this, function (exports) {
exports.action = function () {};
});

즉시실행 함수에 window 객체와 함수를 파라미터로 보내서 전역 객체를 생성하는 방식이다.

예제 - 3 항연산자 사용

(function (global, factory) {
typeof exports === "object" && typeof module !== "undefined"
? factory(exports)
: typeof define === "function" && define.amd
? define(["exports"], factory)
: factory((global.module1 = global.module1 || {}));
})(this, function (exports) {
exports.action = function () {};
});

설명

window 객체 안에 모듈이 선언되므로, module1.action(); 으로 바로 호출해 사용하면 된다.

더 자세한 설명을 보고싶다면 umd를 참조하면 된다.

jsonp function

· 약 2분

jsonp (json with padding)은 다른 서버의 데이터를 내 브라우저에서 호출하기 위해 사용한다. client 에서 callback 함수명과 전송할 값을 같이 보내어 server 에서 callback 함수 안에 data 를 담아 반환하는 방식이다.

전송할 값을 보낼 때, script 태그를 사용한다.

function jsonp(url, callback) {
const callbackName = "jsonp_" + Math.round(100000 * Math.random());
window[callbackName] = function (data) {
delete window[callbackName];
document.body.removeChild(script);
callback(data);
};

const script = document.createElement("script");
script.src =
url + (url.indexOf("?") >= 0 ? "&" : "?") + "callback=" + callbackName;
document.body.appendChild(script);
}

stackoverflow 에서 가져온 소스인데 현재 출처를 찾을 수 없다.

설명

임의의 함수명으로 callback 함수를 생성한 뒤 script 태그를 생성해 server 와 통신한다.

예제

jsonp("http://aaa.com?data=" + data, function (result) {
console.log(result);
});

여담

jQuery 가 있다면 jQuery ajax의 dataType 을 jsonp 로 설정해주면 되고, 요새는 CORS 를 적용해 그냥 호출하는 방식으로 가는 추세다.