XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն

XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն

նախապատմությանը

Այդպես եղավ, որ սերվերը հարձակվեց փրկագնի վիրուսի կողմից, որը «բախտավոր պատահմամբ» մասամբ թողեց .ibd ֆայլերը (innodb աղյուսակների չմշակված տվյալների ֆայլերը) անձեռնմխելի, բայց միևնույն ժամանակ ամբողջությամբ ծածկագրեց .fpm ֆայլերը ( կառուցվածքային ֆայլեր): Այս դեպքում .idb-ը կարելի է բաժանել հետևյալի.

  • ենթակա է վերականգնման ստանդարտ գործիքների և ուղեցույցների միջոցով: Նման դեպքերի համար կա գերազանց դառնալ;
  • մասամբ կոդավորված աղյուսակներ. Հիմնականում դրանք մեծ աղյուսակներ են, որոնց համար (ինչպես ես հասկացա) հարձակվողները չունեին բավարար RAM ամբողջական կոդավորման համար;
  • Դե, լիովին կոդավորված աղյուսակներ, որոնք հնարավոր չէ վերականգնել:

Կարելի էր որոշել, թե որ տարբերակին են պատկանում աղյուսակները՝ պարզապես բացելով այն ցանկացած տեքստային խմբագրիչում ցանկալի կոդավորման տակ (իմ դեպքում դա UTF8 է) և պարզապես դիտելով ֆայլը տեքստային դաշտերի առկայության համար, օրինակ.

XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն

Բացի այդ, ֆայլի սկզբում դուք կարող եք դիտել մեծ թվով 0 բայթ, և վիրուսները, որոնք օգտագործում են բլոկի կոդավորման ալգորիթմը (ամենատարածվածը), սովորաբար ազդում են նաև դրանց վրա:
XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն

Իմ դեպքում հարձակվողները յուրաքանչյուր գաղտնագրված ֆայլի վերջում թողել են 4 բայթանոց տող (1, 0, 0, 0), ինչը հեշտացրել է առաջադրանքը։ Չվարակված ֆայլեր որոնելու համար սցենարը բավական էր.

def opened(path):
    files = os.listdir(path)
    for f in files:
        if os.path.isfile(path + f):
            yield path + f

for full_path in opened("C:somepath"):
    file = open(full_path, "rb")
    last_string = ""
    for line in file:
        last_string = line
        file.close()
    if (last_string[len(last_string) -4:len(last_string)]) != (1, 0, 0, 0):
        print(full_path)

Այսպիսով, պարզվեց, որ հայտնաբերվել են առաջին տիպին պատկանող ֆայլեր։ Երկրորդը շատ ձեռքի աշխատանք է ենթադրում, բայց գտածն արդեն բավական էր։ Ամեն ինչ լավ կլիներ, բայց դուք պետք է իմանաք բացարձակապես ճշգրիտ կառուցվածք և (իհարկե) դեպք առաջացավ, որ ես ստիպված էի աշխատել հաճախակի փոփոխվող սեղանի հետ: Ոչ ոք չէր հիշում՝ դաշտի տեսակը փոխվե՞լ է, թե՞ նոր սյունակ է ավելացվել։

Wilds City-ն, ցավոք, չէր կարող օգնել նման դեպքին, ինչի համար էլ գրվում է այս հոդվածը։

Անցեք կետին

Կա 3 ամիս առաջվա աղյուսակի կառուցվածք, որը չի համընկնում ներկայիսի հետ (հնարավոր է մեկ դաշտ և հնարավոր է ավելին): Սեղանի կառուցվածքը:

CREATE TABLE `table_1` (
    `id` INT (11),
    `date` DATETIME ,
    `description` TEXT ,
    `id_point` INT (11),
    `id_user` INT (11),
    `date_start` DATETIME ,
    `date_finish` DATETIME ,
    `photo` INT (1),
    `id_client` INT (11),
    `status` INT (1),
    `lead__time` TIME ,
    `sendstatus` TINYINT (4)
); 

այս դեպքում անհրաժեշտ է հանել.

  • id_point int (11);
  • id_user int (11);
  • date_start DATETIME;
  • date_finish DATETIME.

Վերականգնման համար օգտագործվում է .ibd ֆայլի բայթ առ բայթ վերլուծություն, որից հետո դրանք վերածվում են ավելի ընթեռնելի ձևի: Քանի որ մեզ անհրաժեշտը գտնելու համար մեզ անհրաժեշտ է միայն վերլուծել տվյալների տեսակները, ինչպիսիք են int-ը և datatime-ը, հոդվածը նկարագրելու է միայն դրանք, բայց երբեմն մենք կանդրադառնանք նաև տվյալների այլ տեսակների, որոնք կարող են օգնել նմանատիպ այլ միջադեպերի դեպքում:

Խնդիր 1DATETIME և TEXT տիպերով դաշտերը ունեին NULL արժեքներ, և դրանք պարզապես բաց են թողնվում ֆայլում, դրա պատճառով հնարավոր չեղավ որոշել իմ դեպքում վերականգնելու կառուցվածքը: Նոր սյունակներում լռելյայն արժեքը զրոյական էր, և գործարքի մի մասը կարող էր կորչել innodb_flush_log_at_trx_commit = 0 պարամետրի պատճառով, ուստի լրացուցիչ ժամանակ պետք է ծախսվի կառուցվածքը որոշելու համար:

Խնդիր 2Պետք է հաշվի առնել, որ DELETE-ի միջոցով ջնջված տողերը բոլորը կլինեն ibd ֆայլում, սակայն ALTER TABLE-ով դրանց կառուցվածքը չի թարմացվի։ Արդյունքում տվյալների կառուցվածքը կարող է տարբեր լինել ֆայլի սկզբից մինչև վերջ: Եթե ​​հաճախ եք օգտագործում OPTIMIZE TABLE-ը, ապա դժվար թե նման խնդրի հանդիպեք:

Խնդրում ենք նկատի ունենալ,, DBMS տարբերակը ազդում է տվյալների պահպանման ձևի վրա, և այս օրինակը կարող է չաշխատել այլ հիմնական տարբերակների համար: Իմ դեպքում օգտագործվել է mariadb 10.1.24 windows տարբերակը։ Նաև, չնայած mariadb-ում դուք աշխատում եք InnoDB աղյուսակների հետ, իրականում դրանք այդպես են XtraDB, որը բացառում է մեթոդի կիրառելիությունը InnoDB mysql-ի հետ։

Ֆայլի վերլուծություն

Python-ում տվյալների տեսակը բայթ () ցուցադրում է Յունիկոդի տվյալները սովորական թվերի փոխարեն: Թեև կարող եք ֆայլը դիտել այս ձևով, հարմարության համար դուք կարող եք բայթերը վերածել թվային ձևի՝ բայթային զանգվածը վերածելով սովորական զանգվածի (ցուցակ(օրինակ_բայթ_զանգված)): Ամեն դեպքում, երկու մեթոդներն էլ հարմար են վերլուծության համար։

Մի քանի ibd ֆայլեր նայելուց հետո կարող եք գտնել հետևյալը.

XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն

Ավելին, եթե ֆայլը բաժանեք այս հիմնաբառերով, դուք հիմնականում կստանաք տվյալների նույնիսկ բլոկներ: Որպես բաժանարար կօգտագործենք infimum-ը։

table = table.split("infimum".encode())

Հետաքրքիր դիտարկում. փոքր քանակությամբ տվյալներ ունեցող աղյուսակների համար infimum-ի և supremum-ի միջև կա բլոկի տողերի քանակի ցուցիչ:

XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն - թեստային սեղան 1-ին շարքով

XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն - թեստային սեղան 2 տողով

Տողերի զանգվածի աղյուսակը[0] կարելի է բաց թողնել: Այն նայելուց հետո ես դեռ չկարողացա գտնել չմշակված աղյուսակի տվյալները: Ամենայն հավանականությամբ, այս բլոկը օգտագործվում է ինդեքսների և բանալիների պահպանման համար:
Սկսած աղյուսակից[1]-ից և այն թարգմանելով թվային զանգվածի, արդեն կարող եք նկատել որոշ օրինաչափություններ, մասնավորապես.

XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն

Սրանք int արժեքներ են, որոնք պահվում են տողի մեջ: Առաջին բայթը ցույց է տալիս՝ թիվը դրական է, թե բացասական: Իմ դեպքում բոլոր թվերը դրական են։ Մնացած 3 բայթից կարող եք որոշել թիվը՝ օգտագործելով հետևյալ գործառույթը. Սցենար:

def find_int(val: str):  # example '128, 1, 2, 3'
    val = [int(v) for v in  val.split(", ")]
    result_int = val[1]*256**2 + val[2]*256*1 + val[3]
    return result_int

Օրինակ, 128, 0, 0, 1 = 1Կամ 128, 0, 75, 108 = 19308.
Աղյուսակն ուներ առաջնային բանալի՝ ավտոմատ աճով, և այն կարելի է գտնել նաև այստեղ

XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն

Համեմատելով փորձարկման աղյուսակների տվյալները՝ պարզվեց, որ DATETIME օբյեկտը բաղկացած է 5 բայթից և սկսվում է 153-ով (ամենայն հավանականությամբ, նշում է տարեկան միջակայքերը): Քանի որ DATTIME միջակայքը «1000-01-01» է մինչև «9999-12-31», ես կարծում եմ, որ բայթերի քանակը կարող է տարբեր լինել, բայց իմ դեպքում տվյալները ընկնում են 2016-ից մինչև 2019 թվականն ընկած ժամանակահատվածում, ուստի մենք կենթադրենք: այդ 5 բայթը բավական է:

Ժամանակն առանց վայրկյանների որոշելու համար գրվել են հետևյալ գործառույթները. Սցենար:

day_ = lambda x: x % 64 // 2  # {x,x,X,x,x }

def hour_(x1, x2):  # {x,x,X1,X2,x}
    if x1 % 2 == 0:
        return x2 // 16
    elif x1 % 2 == 1:
        return x2 // 16 + 16
    else:
        raise ValueError

min_ = lambda x1, x2: (x1 % 16) * 4 + (x2 // 64)  # {x,x,x,X1,X2}

Տարվա և ամսվա համար հնարավոր չէր ֆունկցիոնալ ֆունկցիա գրել, ուստի ստիպված էի կոտրել այն։ Սցենար:

ym_list = {'2016, 1': '153, 152, 64', '2016, 2': '153, 152, 128', 
           '2016, 3': '153, 152, 192', '2016, 4': '153, 153, 0',
           '2016, 5': '153, 153, 64', '2016, 6': '153, 153, 128', 
           '2016, 7': '153, 153, 192', '2016, 8': '153, 154, 0', 
           '2016, 9': '153, 154, 64', '2016, 10': '153, 154, 128', 
           '2016, 11': '153, 154, 192', '2016, 12': '153, 155, 0',
           '2017, 1': '153, 155, 128', '2017, 2': '153, 155, 192', 
           '2017, 3': '153, 156, 0', '2017, 4': '153, 156, 64',
           '2017, 5': '153, 156, 128', '2017, 6': '153, 156, 192',
           '2017, 7': '153, 157, 0', '2017, 8': '153, 157, 64',
           '2017, 9': '153, 157, 128', '2017, 10': '153, 157, 192', 
           '2017, 11': '153, 158, 0', '2017, 12': '153, 158, 64', 
           '2018, 1': '153, 158, 192', '2018, 2': '153, 159, 0',
           '2018, 3': '153, 159, 64', '2018, 4': '153, 159, 128', 
           '2018, 5': '153, 159, 192', '2018, 6': '153, 160, 0',
           '2018, 7': '153, 160, 64', '2018, 8': '153, 160, 128',
           '2018, 9': '153, 160, 192', '2018, 10': '153, 161, 0', 
           '2018, 11': '153, 161, 64', '2018, 12': '153, 161, 128',
           '2019, 1': '153, 162, 0', '2019, 2': '153, 162, 64', 
           '2019, 3': '153, 162, 128', '2019, 4': '153, 162, 192', 
           '2019, 5': '153, 163, 0', '2019, 6': '153, 163, 64',
           '2019, 7': '153, 163, 128', '2019, 8': '153, 163, 192',
           '2019, 9': '153, 164, 0', '2019, 10': '153, 164, 64', 
           '2019, 11': '153, 164, 128', '2019, 12': '153, 164, 192',
           '2020, 1': '153, 165, 64', '2020, 2': '153, 165, 128',
           '2020, 3': '153, 165, 192','2020, 4': '153, 166, 0', 
           '2020, 5': '153, 166, 64', '2020, 6': '153, 1, 128',
           '2020, 7': '153, 166, 192', '2020, 8': '153, 167, 0', 
           '2020, 9': '153, 167, 64','2020, 10': '153, 167, 128',
           '2020, 11': '153, 167, 192', '2020, 12': '153, 168, 0'}

def year_month(x1, x2):  # {x,X,X,x,x }

    for key, value in ym_list.items():
        key = [int(k) for k in key.replace("'", "").split(", ")]
        value = [int(v) for v in value.split(", ")]
        if x1 == value[1] and x2 // 64 == value[2] // 64:
            return key
    return 0, 0

Համոզված եմ, որ եթե դուք ժամանակ հատկացնեք, ապա այս թյուրիմացությունը կշտկվի։
Հաջորդը՝ ֆունկցիա, որը տողից վերադարձնում է datetime օբյեկտը: Սցենար:

def find_data_time(val:str):
    val = [int(v) for v in val.split(", ")]
    day = day_(val[2])
    hour = hour_(val[2], val[3])
    minutes = min_(val[3], val[4])
    year, month = year_month(val[1], val[2])
    return datetime(year, month, day, hour, minutes)

Հաջողվել է հայտնաբերել հաճախակի կրկնվող արժեքները int, int, datetime, datetime-ից XtraDB աղյուսակներից տվյալների վերականգնում առանց կառուցվածքային ֆայլի՝ օգտագործելով ibd ֆայլի բայթ առ բայթ վերլուծություն, կարծես սա այն է, ինչ ձեզ հարկավոր է: Ընդ որում, տողում նման հաջորդականությունը երկու անգամ չի կրկնվում։

Օգտագործելով կանոնավոր արտահայտություն, մենք գտնում ենք անհրաժեշտ տվյալները.

fined = re.findall(r'128, d*, d*, d*, 128, d*, d*, d*, 153, 1[6,5,4,3]d, d*, d*, d*, 153, 1[6,5,4,3]d, d*, d*, d*', int_array)

Խնդրում ենք նկատի ունենալ, որ այս արտահայտությունն օգտագործելով որոնելիս հնարավոր չի լինի անհրաժեշտ դաշտերում որոշել NULL արժեքները, բայց իմ դեպքում դա կարևոր չէ: Այնուհետև մենք անցնում ենք այն, ինչ գտանք մի օղակում: Սցենար:

result = []
for val in fined:
    pre_result = []
    bd_int  = re.findall(r"128, d*, d*, d*", val)
    bd_date= re.findall(r"(153, 1[6,5,4,3]d, d*, d*, d*)", val)
    for it in bd_int:
        pre_result.append(find_int(bd_int[it]))
    for bd in bd_date:
        pre_result.append(find_data_time(bd))
    result.append(pre_result)

Փաստորեն, դա բոլորն է, արդյունքի զանգվածի տվյալները մեզ անհրաժեշտ տվյալներն են: ###PS.###
Ես հասկանում եմ, որ այս մեթոդը ոչ բոլորի համար է հարմար, բայց հոդվածի հիմնական նպատակը ձեր բոլոր խնդիրները լուծելու փոխարեն գործողությունների արագացումն է: Կարծում եմ՝ ամենաճիշտ լուծումը կլինի ինքներդ սկսել սկզբնական կոդը ուսումնասիրել mariadb, սակայն սահմանափակ ժամանակի պատճառով ներկայիս մեթոդը թվում էր ամենաարագը։

Որոշ դեպքերում, ֆայլը վերլուծելուց հետո, դուք կկարողանաք որոշել մոտավոր կառուցվածքը և վերականգնել այն՝ օգտագործելով վերը նշված հղումների ստանդարտ մեթոդներից մեկը: Սա շատ ավելի ճիշտ կլինի և ավելի քիչ խնդիրներ կառաջացնի։

Source: www.habr.com

Добавить комментарий